@@ -6,6 +6,7 @@ use build_helper::ci::CiEnv;
6
6
use build_helper:: git:: get_git_modified_files;
7
7
use ignore:: WalkBuilder ;
8
8
use std:: collections:: VecDeque ;
9
+ use std:: io:: Read ;
9
10
use std:: path:: { Path , PathBuf } ;
10
11
use std:: process:: { Command , Stdio } ;
11
12
use std:: sync:: mpsc:: SyncSender ;
@@ -96,11 +97,185 @@ fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, Str
96
97
get_git_modified_files ( & build. config . git_config ( ) , Some ( & build. config . src ) , & vec ! [ "rs" ] )
97
98
}
98
99
100
+ fn get_modified_c_family_files ( build : & Builder < ' _ > ) -> Result < Option < Vec < String > > , String > {
101
+ get_git_modified_files (
102
+ & build. config . git_config ( ) ,
103
+ Some ( & build. config . src ) ,
104
+ & vec ! [ "cpp" , "c" , "hpp" , "h" ] ,
105
+ )
106
+ }
107
+
99
108
#[ derive( serde_derive:: Deserialize ) ]
100
109
struct RustfmtConfig {
101
110
ignore : Vec < String > ,
102
111
}
103
112
113
+ #[ derive( Debug , PartialEq ) ]
114
+ pub enum DiffLine {
115
+ Context ( String ) ,
116
+ Expected ( String ) ,
117
+ Resulting ( String ) ,
118
+ }
119
+
120
+ #[ derive( Debug , PartialEq ) ]
121
+ pub struct Mismatch {
122
+ pub line_number : u32 ,
123
+ pub lines : Vec < DiffLine > ,
124
+ }
125
+
126
+ impl Mismatch {
127
+ fn new ( line_number : u32 ) -> Mismatch {
128
+ Mismatch { line_number, lines : Vec :: new ( ) }
129
+ }
130
+ }
131
+
132
+ // Produces a diff between the expected output and actual output.
133
+ pub fn make_diff ( expected : & str , actual : & str , context_size : usize ) -> Vec < Mismatch > {
134
+ // FIXME: Can we stop copying this code?
135
+ let mut line_number = 1 ;
136
+ let mut context_queue: VecDeque < & str > = VecDeque :: with_capacity ( context_size) ;
137
+ let mut lines_since_mismatch = context_size + 1 ;
138
+ let mut results = Vec :: new ( ) ;
139
+ let mut mismatch = Mismatch :: new ( 0 ) ;
140
+
141
+ for result in diff:: lines ( expected, actual) {
142
+ match result {
143
+ diff:: Result :: Left ( str) => {
144
+ if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
145
+ results. push ( mismatch) ;
146
+ mismatch = Mismatch :: new ( line_number - context_queue. len ( ) as u32 ) ;
147
+ }
148
+
149
+ while let Some ( line) = context_queue. pop_front ( ) {
150
+ mismatch. lines . push ( DiffLine :: Context ( line. to_owned ( ) ) ) ;
151
+ }
152
+
153
+ mismatch. lines . push ( DiffLine :: Expected ( str. to_owned ( ) ) ) ;
154
+ line_number += 1 ;
155
+ lines_since_mismatch = 0 ;
156
+ }
157
+ diff:: Result :: Right ( str) => {
158
+ if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
159
+ results. push ( mismatch) ;
160
+ mismatch = Mismatch :: new ( line_number - context_queue. len ( ) as u32 ) ;
161
+ }
162
+
163
+ while let Some ( line) = context_queue. pop_front ( ) {
164
+ mismatch. lines . push ( DiffLine :: Context ( line. to_owned ( ) ) ) ;
165
+ }
166
+
167
+ mismatch. lines . push ( DiffLine :: Resulting ( str. to_owned ( ) ) ) ;
168
+ lines_since_mismatch = 0 ;
169
+ }
170
+ diff:: Result :: Both ( str, _) => {
171
+ if context_queue. len ( ) >= context_size {
172
+ let _ = context_queue. pop_front ( ) ;
173
+ }
174
+
175
+ if lines_since_mismatch < context_size {
176
+ mismatch. lines . push ( DiffLine :: Context ( str. to_owned ( ) ) ) ;
177
+ } else if context_size > 0 {
178
+ context_queue. push_back ( str) ;
179
+ }
180
+
181
+ line_number += 1 ;
182
+ lines_since_mismatch += 1 ;
183
+ }
184
+ }
185
+ }
186
+
187
+ results. push ( mismatch) ;
188
+ results. remove ( 0 ) ;
189
+
190
+ results
191
+ }
192
+
193
+ #[ derive( serde_derive:: Deserialize ) ]
194
+ struct ClangFormatConfig {
195
+ ignore : Vec < String > ,
196
+ }
197
+
198
+ fn get_clang_format ( ) -> Option < PathBuf > {
199
+ std:: env:: var_os ( "PATH" ) . and_then ( |paths| {
200
+ std:: env:: split_paths ( & paths)
201
+ . filter_map ( |dir| {
202
+ let full_path = dir. join ( "clang-format" ) ;
203
+ if full_path. is_file ( ) { Some ( full_path) } else { None }
204
+ } )
205
+ . next ( )
206
+ } )
207
+ }
208
+
209
+ fn clang_format (
210
+ src : & Path ,
211
+ clang_format : & Path ,
212
+ path : & Path ,
213
+ check : bool ,
214
+ ) -> impl FnMut ( bool ) -> bool {
215
+ let mut cmd = Command :: new ( clang_format) ;
216
+ let clang_form_config = src. join ( ".clang-format" ) ;
217
+ // avoid the submodule config paths from coming into play,
218
+ // we only allow a single global config for the workspace for now
219
+ cmd. arg ( "--style" ) . arg ( format ! ( "file:{}" , clang_form_config. display( ) ) ) ;
220
+ if !check {
221
+ cmd. arg ( "-i" ) ;
222
+ }
223
+ cmd. arg ( path) ;
224
+ let cmd_debug = format ! ( "{cmd:?}" ) ;
225
+ let mut cmd = cmd. stdout ( Stdio :: piped ( ) ) . spawn ( ) . expect ( "running clang-format" ) ;
226
+ let path = path. to_path_buf ( ) ;
227
+ // poor man's async: return a closure that'll wait for clang-format's completion
228
+ move |block : bool | -> bool {
229
+ // let mut cmd = cmd; // Move `cmd` into the closure
230
+ if !block {
231
+ match cmd. try_wait ( ) {
232
+ Ok ( Some ( _) ) => { }
233
+ _ => return false ,
234
+ }
235
+ }
236
+ let mut expected = String :: new ( ) ;
237
+ if check {
238
+ if let Some ( stdout) = & mut cmd. stdout {
239
+ stdout. read_to_string ( & mut expected) . unwrap ( ) ;
240
+ }
241
+ }
242
+ let status = cmd. wait ( ) . unwrap ( ) ;
243
+ if !status. success ( ) {
244
+ eprintln ! (
245
+ "Running `{}` failed.\n If you're running `tidy`, \
246
+ try again with `--bless`. Or, if you just want to format \
247
+ code, run `./x.py fmt` instead.",
248
+ cmd_debug,
249
+ ) ;
250
+ crate :: exit!( 1 ) ;
251
+ }
252
+ if check {
253
+ let actual = std:: fs:: read_to_string ( & path) . expect ( "Failed" ) ;
254
+ if expected != actual {
255
+ for result in make_diff ( & expected, & actual, 3 ) {
256
+ println ! ( "Diff in {} at line {}:" , path. display( ) , result. line_number) ;
257
+ for line in result. lines {
258
+ match line {
259
+ DiffLine :: Expected ( str) => println ! ( "-{}" , str ) ,
260
+ DiffLine :: Context ( str) => println ! ( " {}" , str ) ,
261
+ DiffLine :: Resulting ( str) => println ! ( "+{}" , str ) ,
262
+ }
263
+ }
264
+ }
265
+ let cmd_debug_diff = format ! ( "{} | diff -u {} -" , cmd_debug, path. display( ) ) ;
266
+ eprintln ! (
267
+ "Running `{}` failed.\n If you're running `tidy`, \
268
+ try again with `--bless`. Or, if you just want to format \
269
+ code, run `./x.py fmt` instead.",
270
+ cmd_debug_diff,
271
+ ) ;
272
+ crate :: exit!( 1 ) ;
273
+ }
274
+ }
275
+ true
276
+ }
277
+ }
278
+
104
279
// Prints output describing a collection of paths, with lines such as "formatted modified file
105
280
// foo/bar/baz" or "skipped 20 untracked files".
106
281
fn print_paths ( verb : & str , adjective : Option < & str > , paths : & [ String ] ) {
@@ -134,6 +309,23 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
134
309
let mut builder = ignore:: types:: TypesBuilder :: new ( ) ;
135
310
builder. add_defaults ( ) ;
136
311
builder. select ( "rust" ) ;
312
+
313
+ let mut c_family_builder = ignore:: types:: TypesBuilder :: new ( ) ;
314
+ c_family_builder. add_defaults ( ) ;
315
+ c_family_builder. select ( "c" ) ;
316
+ c_family_builder. select ( "cpp" ) ;
317
+
318
+ // TODO: Allow to configure the path of clang-format?
319
+ let clang_format_path = get_clang_format ( ) ;
320
+ let clang_format_available = clang_format_path. is_some ( ) ;
321
+ // TODO: Check the clang-format version.
322
+ if !clang_format_available && CiEnv :: is_ci ( ) {
323
+ eprintln ! ( "fmt error: Can't find `clang-format`." ) ;
324
+ crate :: exit!( 1 ) ;
325
+ }
326
+ let mut c_family_override_builder = ignore:: overrides:: OverrideBuilder :: new ( & build. src ) ;
327
+ let mut run_clang_format = clang_format_available;
328
+
137
329
let matcher = builder. build ( ) . unwrap ( ) ;
138
330
let rustfmt_config = build. src . join ( "rustfmt.toml" ) ;
139
331
if !rustfmt_config. exists ( ) {
@@ -156,8 +348,29 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
156
348
crate :: exit!( 1 ) ;
157
349
} else {
158
350
override_builder. add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
351
+ c_family_override_builder. add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
159
352
}
160
353
}
354
+
355
+ let clang_format_config = build. src . join ( "clang-format.toml" ) ;
356
+ if !clang_format_config. exists ( ) {
357
+ eprintln ! ( "fmt error: Not running formatting checks; clang-format.toml does not exist." ) ;
358
+ eprintln ! ( "fmt error: This may happen in distributed tarballs." ) ;
359
+ return ;
360
+ }
361
+ let clang_format_config = t ! ( std:: fs:: read_to_string( & clang_format_config) ) ;
362
+ let clang_format_config: ClangFormatConfig = t ! ( toml:: from_str( & clang_format_config) ) ;
363
+ for ignore in clang_format_config. ignore {
364
+ if ignore. starts_with ( '!' ) {
365
+ eprintln ! (
366
+ "fmt error: `!`-prefixed entries are not supported in clang-format.toml, sorry"
367
+ ) ;
368
+ crate :: exit!( 1 ) ;
369
+ } else {
370
+ c_family_override_builder. add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
371
+ }
372
+ }
373
+
161
374
let git_available = match Command :: new ( "git" )
162
375
. arg ( "--version" )
163
376
. stdout ( Stdio :: null ( ) )
@@ -208,6 +421,9 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
208
421
// against anything like `compiler/rustc_foo/src/foo.rs`,
209
422
// preventing the latter from being formatted.
210
423
override_builder. add ( & format ! ( "!/{untracked_path}" ) ) . expect ( & untracked_path) ;
424
+ c_family_override_builder
425
+ . add ( & format ! ( "!/{untracked_path}" ) )
426
+ . expect ( & untracked_path) ;
211
427
}
212
428
if !all {
213
429
adjective = Some ( "modified" ) ;
@@ -224,6 +440,28 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
224
440
eprintln ! ( "fmt warning: Falling back to formatting all files." ) ;
225
441
}
226
442
}
443
+ match get_modified_c_family_files ( build) {
444
+ Ok ( Some ( files) ) => {
445
+ if !files. is_empty ( ) && !clang_format_available {
446
+ eprintln ! (
447
+ "fmt warning: Modified C++ files but couldn't find the available clang-format."
448
+ ) ;
449
+ }
450
+ run_clang_format = run_clang_format && !files. is_empty ( ) ;
451
+ for file in files {
452
+ c_family_override_builder. add ( & format ! ( "/{file}" ) ) . expect ( & file) ;
453
+ }
454
+ }
455
+ Ok ( None ) => {
456
+ eprintln ! ( "no check" ) ;
457
+ run_clang_format = false ;
458
+ }
459
+ Err ( err) => {
460
+ eprintln ! ( "fmt warning: Something went wrong running git commands:" ) ;
461
+ eprintln ! ( "fmt warning: {err}" ) ;
462
+ eprintln ! ( "fmt warning: Falling back to formatting all files." ) ;
463
+ }
464
+ }
227
465
}
228
466
} else {
229
467
eprintln ! ( "fmt: warning: Not in git tree. Skipping git-aware format checks" ) ;
@@ -241,6 +479,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
241
479
assert ! ( rustfmt_path. exists( ) , "{}" , rustfmt_path. display( ) ) ;
242
480
let src = build. src . clone ( ) ;
243
481
let ( tx, rx) : ( SyncSender < PathBuf > , _ ) = std:: sync:: mpsc:: sync_channel ( 128 ) ;
482
+ let ( c_family_tx, c_family_rx) : ( SyncSender < PathBuf > , _ ) = std:: sync:: mpsc:: sync_channel ( 128 ) ;
244
483
let walker = WalkBuilder :: new ( src. clone ( ) ) . types ( matcher) . overrides ( override_) . build_parallel ( ) ;
245
484
246
485
// There is a lot of blocking involved in spawning a child process and reading files to format.
@@ -303,6 +542,72 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
303
542
ignore:: WalkState :: Continue
304
543
} )
305
544
} ) ;
545
+
546
+ if run_clang_format {
547
+ if let Some ( clang_format_path) = clang_format_path {
548
+ let src = build. src . clone ( ) ;
549
+ let c_family_walker = {
550
+ let c_family_override = c_family_override_builder. build ( ) . unwrap ( ) ;
551
+ let c_family_matcher = c_family_builder. build ( ) . unwrap ( ) ;
552
+ WalkBuilder :: new ( src. clone ( ) )
553
+ . types ( c_family_matcher)
554
+ . overrides ( c_family_override)
555
+ . build_parallel ( )
556
+ } ;
557
+
558
+ let c_family_thread = std:: thread:: spawn ( move || {
559
+ let mut children = VecDeque :: new ( ) ;
560
+ while let Ok ( path) = c_family_rx. recv ( ) {
561
+ let child = clang_format ( & src, & clang_format_path, & path, check) ;
562
+ children. push_back ( child) ;
563
+
564
+ // Poll completion before waiting.
565
+ for i in ( 0 ..children. len ( ) ) . rev ( ) {
566
+ if children[ i] ( false ) {
567
+ children. swap_remove_back ( i) ;
568
+ break ;
569
+ }
570
+ }
571
+
572
+ if children. len ( ) >= max_processes {
573
+ // Await oldest child.
574
+ children. pop_front ( ) . unwrap ( ) ( true ) ;
575
+ }
576
+ }
577
+
578
+ // Await remaining children.
579
+ for mut child in children {
580
+ child ( true ) ;
581
+ }
582
+ } ) ;
583
+
584
+ c_family_walker. run ( || {
585
+ let tx = c_family_tx. clone ( ) ;
586
+ Box :: new ( move |entry| {
587
+ let cwd = std:: env:: current_dir ( ) ;
588
+ let entry = t ! ( entry) ;
589
+ if entry. file_type ( ) . map_or ( false , |t| t. is_file ( ) ) {
590
+ formatted_paths_ref. lock ( ) . unwrap ( ) . push ( {
591
+ // `into_path` produces an absolute path. Try to strip `cwd` to get a shorter
592
+ // relative path.
593
+ let mut path = entry. clone ( ) . into_path ( ) ;
594
+ if let Ok ( cwd) = cwd {
595
+ if let Ok ( path2) = path. strip_prefix ( cwd) {
596
+ path = path2. to_path_buf ( ) ;
597
+ }
598
+ }
599
+ path. display ( ) . to_string ( )
600
+ } ) ;
601
+ t ! ( tx. send( entry. into_path( ) ) ) ;
602
+ }
603
+ ignore:: WalkState :: Continue
604
+ } )
605
+ } ) ;
606
+ drop ( c_family_tx) ;
607
+ c_family_thread. join ( ) . unwrap ( ) ;
608
+ }
609
+ }
610
+
306
611
let mut paths = formatted_paths. into_inner ( ) . unwrap ( ) ;
307
612
paths. sort ( ) ;
308
613
print_paths ( if check { "checked" } else { "formatted" } , adjective, & paths) ;
0 commit comments