@@ -13,6 +13,7 @@ use anyhow::{anyhow, Context, Result};
1313use git2:: { DiffOptions , Patch } ;
1414// non-std crates
1515use lenient_semver;
16+ use regex:: Regex ;
1617use semver:: Version ;
1718use tokio:: task:: JoinSet ;
1819use which:: { which, which_in} ;
@@ -135,6 +136,28 @@ fn analyze_single_file(
135136 Ok ( ( file. name . clone ( ) , logs) )
136137}
137138
139+ /// A struct to contain the version numbers of the clang-tools used
140+ #[ derive( Default ) ]
141+ pub struct ClangVersions {
142+ /// The clang-format version used.
143+ pub format_version : Option < String > ,
144+
145+ /// The clang-tidy version used.
146+ pub tidy_version : Option < String > ,
147+ }
148+
149+ /// Run `clang-tool --version`, the extract and return the version number.
150+ fn capture_clang_version ( clang_tool : & PathBuf ) -> Result < String > {
151+ let output = Command :: new ( clang_tool) . arg ( "--version" ) . output ( ) ?;
152+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
153+ let version_pattern = Regex :: new ( r"version\s*(\d+\.\d+\.\d+)" ) . unwrap ( ) ;
154+ let captures = version_pattern. captures ( & stdout) . ok_or ( anyhow ! (
155+ "Failed to find version number in `{} --version` output" ,
156+ clang_tool. to_string_lossy( )
157+ ) ) ?;
158+ Ok ( captures. get ( 1 ) . unwrap ( ) . as_str ( ) . to_string ( ) )
159+ }
160+
138161/// Runs clang-tidy and/or clang-format and returns the parsed output from each.
139162///
140163/// If `tidy_checks` is `"-*"` then clang-tidy is not executed.
@@ -144,30 +167,29 @@ pub async fn capture_clang_tools_output(
144167 version : & str ,
145168 clang_params : & mut ClangParams ,
146169 rest_api_client : & impl RestApiClient ,
147- ) -> Result < ( ) > {
170+ ) -> Result < ClangVersions > {
171+ let mut clang_versions = ClangVersions :: default ( ) ;
148172 // find the executable paths for clang-tidy and/or clang-format and show version
149173 // info as debugging output.
150174 if clang_params. tidy_checks != "-*" {
151- clang_params. clang_tidy_command = {
152- let cmd = get_clang_tool_exe ( "clang-tidy" , version) ?;
153- log:: debug!(
154- "{} --version\n {}" ,
155- & cmd. to_string_lossy( ) ,
156- String :: from_utf8_lossy( & Command :: new( & cmd) . arg( "--version" ) . output( ) ?. stdout)
157- ) ;
158- Some ( cmd)
159- }
175+ let exe_path = get_clang_tool_exe ( "clang-tidy" , version) ?;
176+ let version_found = capture_clang_version ( & exe_path) ?;
177+ log:: debug!(
178+ "{} --version: v{version_found}" ,
179+ & exe_path. to_string_lossy( )
180+ ) ;
181+ clang_versions. tidy_version = Some ( version_found) ;
182+ clang_params. clang_tidy_command = Some ( exe_path) ;
160183 } ;
161184 if !clang_params. style . is_empty ( ) {
162- clang_params. clang_format_command = {
163- let cmd = get_clang_tool_exe ( "clang-format" , version) ?;
164- log:: debug!(
165- "{} --version\n {}" ,
166- & cmd. to_string_lossy( ) ,
167- String :: from_utf8_lossy( & Command :: new( & cmd) . arg( "--version" ) . output( ) ?. stdout)
168- ) ;
169- Some ( cmd)
170- }
185+ let exe_path = get_clang_tool_exe ( "clang-format" , version) ?;
186+ let version_found = capture_clang_version ( & exe_path) ?;
187+ log:: debug!(
188+ "{} --version: v{version_found}" ,
189+ & exe_path. to_string_lossy( )
190+ ) ;
191+ clang_versions. format_version = Some ( version_found) ;
192+ clang_params. clang_format_command = Some ( exe_path) ;
171193 } ;
172194
173195 // parse database (if provided) to match filenames when parsing clang-tidy's stdout
@@ -199,7 +221,7 @@ pub async fn capture_clang_tools_output(
199221 rest_api_client. end_log_group ( ) ;
200222 }
201223 }
202- Ok ( ( ) )
224+ Ok ( clang_versions )
203225}
204226
205227/// A struct to describe a single suggestion in a pull_request review.
@@ -221,7 +243,7 @@ pub struct ReviewComments {
221243 ///
222244 /// This differs from `comments.len()` because some suggestions may
223245 /// not fit within the file's diff.
224- pub tool_total : [ u32 ; 2 ] ,
246+ pub tool_total : [ Option < u32 > ; 2 ] ,
225247 /// A list of comment suggestions to be posted.
226248 ///
227249 /// These suggestions are guaranteed to fit in the file's diff.
@@ -234,11 +256,26 @@ pub struct ReviewComments {
234256}
235257
236258impl ReviewComments {
237- pub fn summarize ( & self ) -> String {
259+ pub fn summarize ( & self , clang_versions : & ClangVersions ) -> String {
238260 let mut body = format ! ( "{COMMENT_MARKER}## Cpp-linter Review\n " ) ;
239261 for t in 0u8 ..=1 {
240262 let mut total = 0 ;
241- let tool_name = if t == 0 { "clang-format" } else { "clang-tidy" } ;
263+ let ( tool_name, tool_version) = if t == 0 {
264+ ( "clang-format" , clang_versions. format_version . as_ref ( ) )
265+ } else {
266+ ( "clang-tidy" , clang_versions. tidy_version . as_ref ( ) )
267+ } ;
268+
269+ if self . tool_total [ t as usize ] . is_none ( ) {
270+ // review was not requested from this tool or the tool was not used at all
271+ continue ;
272+ }
273+
274+ // If the tool's version is unknown, then we don't need to output this line.
275+ // NOTE: If the tool was invoked at all, then the tool's version shall be known.
276+ if let Some ( ver_str) = tool_version {
277+ body. push_str ( format ! ( "### Used {tool_name} {ver_str}\n " ) . as_str ( ) ) ;
278+ }
242279 for comment in & self . comments {
243280 if comment
244281 . suggestion
@@ -248,11 +285,12 @@ impl ReviewComments {
248285 }
249286 }
250287
251- if total != self . tool_total [ t as usize ] {
288+ // at this point tool_total is Some() value; unwrap() is safe here
289+ let tool_total = self . tool_total [ t as usize ] . unwrap ( ) ;
290+ if total != tool_total {
252291 body. push_str (
253292 format ! (
254- "\n Only {} out of {} {tool_name} concerns fit within this pull request's diff.\n " ,
255- self . tool_total[ t as usize ] , total
293+ "\n Only {total} out of {tool_total} {tool_name} concerns fit within this pull request's diff.\n " ,
256294 )
257295 . as_str ( ) ,
258296 ) ;
@@ -351,6 +389,9 @@ pub trait MakeSuggestions {
351389 . with_context ( || format ! ( "Failed to convert patch to string: {file_name}" ) ) ?
352390 . as_str ( ) ,
353391 ) ;
392+ if review_comments. tool_total [ is_tidy_tool as usize ] . is_none ( ) {
393+ review_comments. tool_total [ is_tidy_tool as usize ] = Some ( 0 ) ;
394+ }
354395 if summary_only {
355396 return Ok ( ( ) ) ;
356397 }
@@ -408,7 +449,9 @@ pub trait MakeSuggestions {
408449 review_comments. comments . push ( comment) ;
409450 }
410451 }
411- review_comments. tool_total [ is_tidy_tool as usize ] += hunks_in_patch;
452+ review_comments. tool_total [ is_tidy_tool as usize ] = Some (
453+ review_comments. tool_total [ is_tidy_tool as usize ] . unwrap_or_default ( ) + hunks_in_patch,
454+ ) ;
412455 Ok ( ( ) )
413456 }
414457}
0 commit comments