@@ -13,6 +13,7 @@ use anyhow::{anyhow, Context, Result};
13
13
use git2:: { DiffOptions , Patch } ;
14
14
// non-std crates
15
15
use lenient_semver;
16
+ use regex:: Regex ;
16
17
use semver:: Version ;
17
18
use tokio:: task:: JoinSet ;
18
19
use which:: { which, which_in} ;
@@ -135,6 +136,28 @@ fn analyze_single_file(
135
136
Ok ( ( file. name . clone ( ) , logs) )
136
137
}
137
138
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`, then 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"(?i)version\s*([\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
+
138
161
/// Runs clang-tidy and/or clang-format and returns the parsed output from each.
139
162
///
140
163
/// If `tidy_checks` is `"-*"` then clang-tidy is not executed.
@@ -144,30 +167,29 @@ pub async fn capture_clang_tools_output(
144
167
version : & str ,
145
168
clang_params : & mut ClangParams ,
146
169
rest_api_client : & impl RestApiClient ,
147
- ) -> Result < ( ) > {
170
+ ) -> Result < ClangVersions > {
171
+ let mut clang_versions = ClangVersions :: default ( ) ;
148
172
// find the executable paths for clang-tidy and/or clang-format and show version
149
173
// info as debugging output.
150
174
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) ;
160
183
} ;
161
184
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) ;
171
193
} ;
172
194
173
195
// parse database (if provided) to match filenames when parsing clang-tidy's stdout
@@ -199,7 +221,7 @@ pub async fn capture_clang_tools_output(
199
221
rest_api_client. end_log_group ( ) ;
200
222
}
201
223
}
202
- Ok ( ( ) )
224
+ Ok ( clang_versions )
203
225
}
204
226
205
227
/// A struct to describe a single suggestion in a pull_request review.
@@ -221,7 +243,7 @@ pub struct ReviewComments {
221
243
///
222
244
/// This differs from `comments.len()` because some suggestions may
223
245
/// not fit within the file's diff.
224
- pub tool_total : [ u32 ; 2 ] ,
246
+ pub tool_total : [ Option < u32 > ; 2 ] ,
225
247
/// A list of comment suggestions to be posted.
226
248
///
227
249
/// These suggestions are guaranteed to fit in the file's diff.
@@ -234,11 +256,28 @@ pub struct ReviewComments {
234
256
}
235
257
236
258
impl ReviewComments {
237
- pub fn summarize ( & self ) -> String {
259
+ pub fn summarize ( & self , clang_versions : & ClangVersions ) -> String {
238
260
let mut body = format ! ( "{COMMENT_MARKER}## Cpp-linter Review\n " ) ;
239
261
for t in 0u8 ..=1 {
240
262
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
+ let tool_total = if let Some ( total) = self . tool_total [ t as usize ] {
270
+ total
271
+ } else {
272
+ // review was not requested from this tool or the tool was not used at all
273
+ continue ;
274
+ } ;
275
+
276
+ // If the tool's version is unknown, then we don't need to output this line.
277
+ // NOTE: If the tool was invoked at all, then the tool's version shall be known.
278
+ if let Some ( ver_str) = tool_version {
279
+ body. push_str ( format ! ( "### Used {tool_name} {ver_str}\n " ) . as_str ( ) ) ;
280
+ }
242
281
for comment in & self . comments {
243
282
if comment
244
283
. suggestion
@@ -248,11 +287,10 @@ impl ReviewComments {
248
287
}
249
288
}
250
289
251
- if total != self . tool_total [ t as usize ] {
290
+ if total != tool_total {
252
291
body. push_str (
253
292
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 " ,
256
294
)
257
295
. as_str ( ) ,
258
296
) ;
@@ -351,6 +389,7 @@ pub trait MakeSuggestions {
351
389
. with_context ( || format ! ( "Failed to convert patch to string: {file_name}" ) ) ?
352
390
. as_str ( ) ,
353
391
) ;
392
+ review_comments. tool_total [ is_tidy_tool as usize ] . get_or_insert ( 0 ) ;
354
393
if summary_only {
355
394
return Ok ( ( ) ) ;
356
395
}
@@ -408,7 +447,9 @@ pub trait MakeSuggestions {
408
447
review_comments. comments . push ( comment) ;
409
448
}
410
449
}
411
- review_comments. tool_total [ is_tidy_tool as usize ] += hunks_in_patch;
450
+ review_comments. tool_total [ is_tidy_tool as usize ] = Some (
451
+ review_comments. tool_total [ is_tidy_tool as usize ] . unwrap_or_default ( ) + hunks_in_patch,
452
+ ) ;
412
453
Ok ( ( ) )
413
454
}
414
455
}
0 commit comments