1717//! 3. Print the output of the given command. If it fails and `TIDY_PRINT_DIFF`
1818//! is set, rerun the tool to print a suggestion diff (for e.g. CI)
1919
20+ use std:: collections:: VecDeque ;
2021use std:: ffi:: OsStr ;
2122use std:: fmt;
2223use std:: fs;
@@ -39,6 +40,85 @@ const BLACK_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "black.to
3940const RUFF_CACH_PATH : & [ & str ] = & [ "cache" , "ruff_cache" ] ;
4041const PIP_REQ_PATH : & [ & str ] = & [ "src" , "tools" , "tidy" , "config" , "requirements.txt" ] ;
4142
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+
42122pub fn check (
43123 root_path : & Path ,
44124 outdir : & Path ,
@@ -73,6 +153,8 @@ fn check_impl(
73153 let python_fmt = lint_args. contains ( & "py:fmt" ) || python_all;
74154 let shell_all = lint_args. contains ( & "shell" ) ;
75155 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;
76158
77159 let mut py_path = None ;
78160
@@ -81,7 +163,7 @@ fn check_impl(
81163 . map ( OsStr :: new)
82164 . partition ( |arg| arg. to_str ( ) . is_some_and ( |s| s. starts_with ( '-' ) ) ) ;
83165
84- if python_lint || python_fmt {
166+ if python_lint || python_fmt || clang_fmt {
85167 let venv_path = outdir. join ( "venv" ) ;
86168 let mut reqs_path = root_path. to_owned ( ) ;
87169 reqs_path. extend ( PIP_REQ_PATH ) ;
@@ -111,13 +193,13 @@ fn check_impl(
111193
112194 let mut args = merge_args ( & cfg_args_ruff, & file_args_ruff) ;
113195 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) ;
115197
116198 if res. is_err ( ) && show_diff {
117199 eprintln ! ( "\n python linting failed! Printing diff suggestions:" ) ;
118200
119201 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) ;
121203 }
122204 // Rethrow error
123205 let _ = res?;
@@ -144,13 +226,78 @@ fn check_impl(
144226 }
145227
146228 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) ;
148230
149231 if res. is_err ( ) && show_diff {
150232 eprintln ! ( "\n python formatting does not match! Printing diff:" ) ;
151233
152234 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+ }
154301 }
155302 // Rethrow error
156303 let _ = res?;
@@ -162,7 +309,7 @@ fn check_impl(
162309 let mut file_args_shc = file_args. clone ( ) ;
163310 let files;
164311 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" ) ] ) ?;
166313 file_args_shc. extend ( files. iter ( ) . map ( |p| p. as_os_str ( ) ) ) ;
167314 }
168315
@@ -181,8 +328,29 @@ fn merge_args<'a>(cfg_args: &[&'a OsStr], file_args: &[&'a OsStr]) -> Vec<&'a Os
181328}
182329
183330/// 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+ } ;
186354 if status. success ( ) { Ok ( ( ) ) } else { Err ( Error :: FailedCheck ( bin) ) }
187355}
188356
@@ -357,7 +525,11 @@ fn shellcheck_runner(args: &[&OsStr]) -> Result<(), Error> {
357525}
358526
359527/// 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 > {
361533 // Untracked files show up for short status and are indicated with a leading `?`
362534 // -C changes git to be as if run from that directory
363535 let stat_output =
@@ -368,14 +540,24 @@ fn find_with_extension(root_path: &Path, extension: &str) -> Result<Vec<PathBuf>
368540 }
369541
370542 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+ } ;
372551 let tracked = String :: from_utf8_lossy ( & binding. stdout ) ;
373552
374553 for line in tracked. lines ( ) {
375554 let line = line. trim ( ) ;
376555 let path = Path :: new ( line) ;
377556
378- if path. extension ( ) == Some ( OsStr :: new ( extension) ) {
557+ let Some ( ref extension) = path. extension ( ) else {
558+ continue ;
559+ } ;
560+ if extensions. contains ( extension) {
379561 output. push ( path. to_owned ( ) ) ;
380562 }
381563 }
0 commit comments