17
17
//! 3. Print the output of the given command. If it fails and `TIDY_PRINT_DIFF`
18
18
//! is set, rerun the tool to print a suggestion diff (for e.g. CI)
19
19
20
+ use std:: collections:: VecDeque ;
20
21
use std:: ffi:: OsStr ;
21
22
use std:: fmt;
22
23
use std:: fs;
@@ -39,6 +40,85 @@ const BLACK_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "black.to
39
40
const RUFF_CACH_PATH : & [ & str ] = & [ "cache" , "ruff_cache" ] ;
40
41
const PIP_REQ_PATH : & [ & str ] = & [ "src" , "tools" , "tidy" , "config" , "requirements.txt" ] ;
41
42
43
+ #[ derive( Debug , PartialEq ) ]
44
+ pub enum DiffLine {
45
+ Context ( String ) ,
46
+ Expected ( String ) ,
47
+ Resulting ( String ) ,
48
+ }
49
+
50
+ #[ derive( Debug , PartialEq ) ]
51
+ pub struct Mismatch {
52
+ pub line_number : u32 ,
53
+ pub lines : Vec < DiffLine > ,
54
+ }
55
+
56
+ impl Mismatch {
57
+ fn new ( line_number : u32 ) -> Mismatch {
58
+ Mismatch { line_number, lines : Vec :: new ( ) }
59
+ }
60
+ }
61
+
62
+ // Produces a diff between the expected output and actual output.
63
+ pub fn make_diff ( expected : & str , actual : & str , context_size : usize ) -> Vec < Mismatch > {
64
+ let mut line_number = 1 ;
65
+ let mut context_queue: VecDeque < & str > = VecDeque :: with_capacity ( context_size) ;
66
+ let mut lines_since_mismatch = context_size + 1 ;
67
+ let mut results = Vec :: new ( ) ;
68
+ let mut mismatch = Mismatch :: new ( 0 ) ;
69
+
70
+ for result in diff:: lines ( expected, actual) {
71
+ match result {
72
+ diff:: Result :: Left ( str) => {
73
+ if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
74
+ results. push ( mismatch) ;
75
+ mismatch = Mismatch :: new ( line_number - context_queue. len ( ) as u32 ) ;
76
+ }
77
+
78
+ while let Some ( line) = context_queue. pop_front ( ) {
79
+ mismatch. lines . push ( DiffLine :: Context ( line. to_owned ( ) ) ) ;
80
+ }
81
+
82
+ mismatch. lines . push ( DiffLine :: Expected ( str. to_owned ( ) ) ) ;
83
+ line_number += 1 ;
84
+ lines_since_mismatch = 0 ;
85
+ }
86
+ diff:: Result :: Right ( str) => {
87
+ if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
88
+ results. push ( mismatch) ;
89
+ mismatch = Mismatch :: new ( line_number - context_queue. len ( ) as u32 ) ;
90
+ }
91
+
92
+ while let Some ( line) = context_queue. pop_front ( ) {
93
+ mismatch. lines . push ( DiffLine :: Context ( line. to_owned ( ) ) ) ;
94
+ }
95
+
96
+ mismatch. lines . push ( DiffLine :: Resulting ( str. to_owned ( ) ) ) ;
97
+ lines_since_mismatch = 0 ;
98
+ }
99
+ diff:: Result :: Both ( str, _) => {
100
+ if context_queue. len ( ) >= context_size {
101
+ let _ = context_queue. pop_front ( ) ;
102
+ }
103
+
104
+ if lines_since_mismatch < context_size {
105
+ mismatch. lines . push ( DiffLine :: Context ( str. to_owned ( ) ) ) ;
106
+ } else if context_size > 0 {
107
+ context_queue. push_back ( str) ;
108
+ }
109
+
110
+ line_number += 1 ;
111
+ lines_since_mismatch += 1 ;
112
+ }
113
+ }
114
+ }
115
+
116
+ results. push ( mismatch) ;
117
+ results. remove ( 0 ) ;
118
+
119
+ results
120
+ }
121
+
42
122
pub fn check (
43
123
root_path : & Path ,
44
124
outdir : & Path ,
@@ -73,6 +153,8 @@ fn check_impl(
73
153
let python_fmt = lint_args. contains ( & "py:fmt" ) || python_all;
74
154
let shell_all = lint_args. contains ( & "shell" ) ;
75
155
let shell_lint = lint_args. contains ( & "shell:lint" ) || shell_all;
156
+ let clang_all = lint_args. contains ( & "clang" ) ;
157
+ let clang_fmt = lint_args. contains ( & "clang:fmt" ) || clang_all;
76
158
77
159
let mut py_path = None ;
78
160
@@ -81,7 +163,7 @@ fn check_impl(
81
163
. map ( OsStr :: new)
82
164
. partition ( |arg| arg. to_str ( ) . is_some_and ( |s| s. starts_with ( '-' ) ) ) ;
83
165
84
- if python_lint || python_fmt {
166
+ if python_lint || python_fmt || clang_fmt {
85
167
let venv_path = outdir. join ( "venv" ) ;
86
168
let mut reqs_path = root_path. to_owned ( ) ;
87
169
reqs_path. extend ( PIP_REQ_PATH ) ;
@@ -111,13 +193,13 @@ fn check_impl(
111
193
112
194
let mut args = merge_args ( & cfg_args_ruff, & file_args_ruff) ;
113
195
args. insert ( 0 , "check" . as_ref ( ) ) ;
114
- let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , "ruff" , & args) ;
196
+ let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
115
197
116
198
if res. is_err ( ) && show_diff {
117
199
eprintln ! ( "\n python linting failed! Printing diff suggestions:" ) ;
118
200
119
201
args. insert ( 1 , "--diff" . as_ref ( ) ) ;
120
- let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , "ruff" , & args) ;
202
+ let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
121
203
}
122
204
// Rethrow error
123
205
let _ = res?;
@@ -144,13 +226,78 @@ fn check_impl(
144
226
}
145
227
146
228
let mut args = merge_args ( & cfg_args_black, & file_args_black) ;
147
- let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , "black" , & args) ;
229
+ let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "black" , & args) ;
148
230
149
231
if res. is_err ( ) && show_diff {
150
232
eprintln ! ( "\n python formatting does not match! Printing diff:" ) ;
151
233
152
234
args. insert ( 0 , "--diff" . as_ref ( ) ) ;
153
- let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , "black" , & args) ;
235
+ let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "black" , & args) ;
236
+ }
237
+ // Rethrow error
238
+ let _ = res?;
239
+ }
240
+
241
+ if clang_fmt {
242
+ let mut cfg_args_clang_format = cfg_args. clone ( ) ;
243
+ let mut file_args_clang_format = file_args. clone ( ) ;
244
+ let config_path = root_path. join ( ".clang-format" ) ;
245
+ let config_file_arg = format ! ( "file:{}" , config_path. display( ) ) ;
246
+ cfg_args_clang_format. extend ( & [ "--style" . as_ref ( ) , config_file_arg. as_ref ( ) ] ) ;
247
+ if bless {
248
+ eprintln ! ( "formatting C++ files" ) ;
249
+ cfg_args_clang_format. push ( "-i" . as_ref ( ) ) ;
250
+ } else {
251
+ eprintln ! ( "checking C++ file formatting" ) ;
252
+ cfg_args_clang_format. extend ( & [ "--dry-run" . as_ref ( ) , "--Werror" . as_ref ( ) ] ) ;
253
+ }
254
+ let files;
255
+ if file_args_clang_format. is_empty ( ) {
256
+ let llvm_wrapper = root_path. join ( "compiler/rustc_llvm/llvm-wrapper" ) ;
257
+ files = find_with_extension (
258
+ root_path,
259
+ Some ( llvm_wrapper. as_path ( ) ) ,
260
+ & [ OsStr :: new ( "h" ) , OsStr :: new ( "cpp" ) ] ,
261
+ ) ?;
262
+ file_args_clang_format. extend ( files. iter ( ) . map ( |p| p. as_os_str ( ) ) ) ;
263
+ }
264
+ let args = merge_args ( & cfg_args_clang_format, & file_args_clang_format) ;
265
+ let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , false , None , "clang-format" , & args) ;
266
+
267
+ if res. is_err ( ) && show_diff {
268
+ eprintln ! ( "\n clang-format linting failed! Printing diff suggestions:" ) ;
269
+
270
+ let mut cfg_args_clang_format_diff = cfg_args. clone ( ) ;
271
+ cfg_args_clang_format_diff. extend ( & [ "--style" . as_ref ( ) , config_file_arg. as_ref ( ) ] ) ;
272
+ for file in file_args_clang_format {
273
+ let mut expected = String :: new ( ) ;
274
+ let mut diff_args = cfg_args_clang_format_diff. clone ( ) ;
275
+ diff_args. push ( file) ;
276
+ let _ = py_runner (
277
+ py_path. as_ref ( ) . unwrap ( ) ,
278
+ false ,
279
+ Some ( & mut expected) ,
280
+ "clang-format" ,
281
+ & diff_args,
282
+ ) ;
283
+ if expected. is_empty ( ) {
284
+ eprintln ! ( "failed to obtain the expected content" ) ;
285
+ continue ;
286
+ }
287
+ let actual = std:: fs:: read_to_string ( file) . expect ( "failed to read the C++ file" ) ;
288
+ if expected != actual {
289
+ for result in make_diff ( & expected, & actual, 3 ) {
290
+ eprintln ! ( "Diff in {:?} at line {}:" , file, result. line_number) ;
291
+ for line in result. lines {
292
+ match line {
293
+ DiffLine :: Expected ( str) => eprintln ! ( "-{}" , str ) ,
294
+ DiffLine :: Context ( str) => eprintln ! ( " {}" , str ) ,
295
+ DiffLine :: Resulting ( str) => eprintln ! ( "+{}" , str ) ,
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
154
301
}
155
302
// Rethrow error
156
303
let _ = res?;
@@ -162,7 +309,7 @@ fn check_impl(
162
309
let mut file_args_shc = file_args. clone ( ) ;
163
310
let files;
164
311
if file_args_shc. is_empty ( ) {
165
- files = find_with_extension ( root_path, "sh" ) ?;
312
+ files = find_with_extension ( root_path, None , & [ OsStr :: new ( "sh" ) ] ) ?;
166
313
file_args_shc. extend ( files. iter ( ) . map ( |p| p. as_os_str ( ) ) ) ;
167
314
}
168
315
@@ -181,8 +328,29 @@ fn merge_args<'a>(cfg_args: &[&'a OsStr], file_args: &[&'a OsStr]) -> Vec<&'a Os
181
328
}
182
329
183
330
/// Run a python command with given arguments. `py_path` should be a virtualenv.
184
- fn py_runner ( py_path : & Path , bin : & ' static str , args : & [ & OsStr ] ) -> Result < ( ) , Error > {
185
- let status = Command :: new ( py_path) . arg ( "-m" ) . arg ( bin) . args ( args) . status ( ) ?;
331
+ fn py_runner (
332
+ py_path : & Path ,
333
+ as_module : bool ,
334
+ stdout : Option < & mut String > ,
335
+ bin : & ' static str ,
336
+ args : & [ & OsStr ] ,
337
+ ) -> Result < ( ) , Error > {
338
+ let mut cmd = Command :: new ( py_path) ;
339
+ if as_module {
340
+ cmd. arg ( "-m" ) . arg ( bin) . args ( args) ;
341
+ } else {
342
+ let bin_path = py_path. with_file_name ( bin) ;
343
+ cmd. arg ( bin_path) . args ( args) ;
344
+ }
345
+ let status = if let Some ( stdout) = stdout {
346
+ let output = cmd. output ( ) ?;
347
+ if let Ok ( s) = std:: str:: from_utf8 ( & output. stdout ) {
348
+ stdout. push_str ( s) ;
349
+ }
350
+ output. status
351
+ } else {
352
+ cmd. status ( ) ?
353
+ } ;
186
354
if status. success ( ) { Ok ( ( ) ) } else { Err ( Error :: FailedCheck ( bin) ) }
187
355
}
188
356
@@ -357,7 +525,11 @@ fn shellcheck_runner(args: &[&OsStr]) -> Result<(), Error> {
357
525
}
358
526
359
527
/// Check git for tracked files matching an extension
360
- fn find_with_extension ( root_path : & Path , extension : & str ) -> Result < Vec < PathBuf > , Error > {
528
+ fn find_with_extension (
529
+ root_path : & Path ,
530
+ find_dir : Option < & Path > ,
531
+ extensions : & [ & OsStr ] ,
532
+ ) -> Result < Vec < PathBuf > , Error > {
361
533
// Untracked files show up for short status and are indicated with a leading `?`
362
534
// -C changes git to be as if run from that directory
363
535
let stat_output =
@@ -368,15 +540,25 @@ fn find_with_extension(root_path: &Path, extension: &str) -> Result<Vec<PathBuf>
368
540
}
369
541
370
542
let mut output = Vec :: new ( ) ;
371
- let binding = Command :: new ( "git" ) . arg ( "-C" ) . arg ( root_path) . args ( [ "ls-files" ] ) . output ( ) ?;
543
+ let binding = {
544
+ let mut command = Command :: new ( "git" ) ;
545
+ command. arg ( "-C" ) . arg ( root_path) . args ( [ "ls-files" ] ) ;
546
+ if let Some ( find_dir) = find_dir {
547
+ command. arg ( find_dir) ;
548
+ }
549
+ command. output ( ) ?
550
+ } ;
372
551
let tracked = String :: from_utf8_lossy ( & binding. stdout ) ;
373
552
374
553
for line in tracked. lines ( ) {
375
554
let line = line. trim ( ) ;
376
555
let path = Path :: new ( line) ;
377
556
378
- if path. extension ( ) == Some ( OsStr :: new ( extension) ) {
379
- output. push ( path. to_owned ( ) ) ;
557
+ let Some ( ref extension) = path. extension ( ) else {
558
+ continue ;
559
+ } ;
560
+ if extensions. contains ( extension) {
561
+ output. push ( root_path. join ( path) ) ;
380
562
}
381
563
}
382
564
0 commit comments