@@ -9,6 +9,7 @@ use std::collections::VecDeque;
9
9
use std:: path:: { Path , PathBuf } ;
10
10
use std:: process:: { Command , Stdio } ;
11
11
use std:: sync:: mpsc:: SyncSender ;
12
+ use std:: sync:: Mutex ;
12
13
13
14
fn rustfmt ( src : & Path , rustfmt : & Path , paths : & [ PathBuf ] , check : bool ) -> impl FnMut ( bool ) -> bool {
14
15
let mut cmd = Command :: new ( rustfmt) ;
@@ -24,20 +25,23 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
24
25
cmd. args ( paths) ;
25
26
let cmd_debug = format ! ( "{cmd:?}" ) ;
26
27
let mut cmd = cmd. spawn ( ) . expect ( "running rustfmt" ) ;
27
- // Poor man's async: return a closure that'll wait for rustfmt's completion.
28
+ // Poor man's async: return a closure that might wait for rustfmt's completion (depending on
29
+ // the value of the `block` argument).
28
30
move |block : bool | -> bool {
29
- if !block {
31
+ let status = if !block {
30
32
match cmd. try_wait ( ) {
31
- Ok ( Some ( _) ) => { }
32
- _ => return false ,
33
+ Ok ( Some ( status) ) => Ok ( status) ,
34
+ Ok ( None ) => return false ,
35
+ Err ( err) => Err ( err) ,
33
36
}
34
- }
35
- let status = cmd. wait ( ) . unwrap ( ) ;
36
- if !status. success ( ) {
37
+ } else {
38
+ cmd. wait ( )
39
+ } ;
40
+ if !status. unwrap ( ) . success ( ) {
37
41
eprintln ! (
38
- "Running `{}` failed.\n If you're running `tidy`, \
39
- try again with `--bless`. Or, if you just want to format \
40
- code, run `./x.py fmt` instead.",
42
+ "fmt error: Running `{}` failed.\n If you're running `tidy`, \
43
+ try again with `--bless`. Or, if you just want to format \
44
+ code, run `./x.py fmt` instead.",
41
45
cmd_debug,
42
46
) ;
43
47
crate :: exit!( 1 ) ;
@@ -97,35 +101,61 @@ struct RustfmtConfig {
97
101
ignore : Vec < String > ,
98
102
}
99
103
100
- pub fn format ( build : & Builder < ' _ > , check : bool , paths : & [ PathBuf ] ) {
104
+ // Prints output describing a collection of paths, with lines such as "formatted modified file
105
+ // foo/bar/baz" or "skipped 20 untracked files".
106
+ fn print_paths ( verb : & str , adjective : Option < & str > , paths : & [ String ] ) {
107
+ let len = paths. len ( ) ;
108
+ let adjective =
109
+ if let Some ( adjective) = adjective { format ! ( "{adjective} " ) } else { String :: new ( ) } ;
110
+ if len <= 10 {
111
+ for path in paths {
112
+ println ! ( "fmt: {verb} {adjective}file {path}" ) ;
113
+ }
114
+ } else {
115
+ println ! ( "fmt: {verb} {len} {adjective}files" ) ;
116
+ }
117
+ }
118
+
119
+ pub fn format ( build : & Builder < ' _ > , check : bool , all : bool , paths : & [ PathBuf ] ) {
120
+ if !paths. is_empty ( ) {
121
+ eprintln ! ( "fmt error: path arguments are not accepted" ) ;
122
+ crate :: exit!( 1 ) ;
123
+ } ;
101
124
if build. config . dry_run ( ) {
102
125
return ;
103
126
}
127
+
128
+ // By default, we only check modified files locally to speed up runtime. Exceptions are if
129
+ // `--all` is specified or we are in CI. We check all files in CI to avoid bugs in
130
+ // `get_modified_rs_files` letting regressions slip through; we also care about CI time less
131
+ // since this is still very fast compared to building the compiler.
132
+ let all = all || CiEnv :: is_ci ( ) ;
133
+
104
134
let mut builder = ignore:: types:: TypesBuilder :: new ( ) ;
105
135
builder. add_defaults ( ) ;
106
136
builder. select ( "rust" ) ;
107
137
let matcher = builder. build ( ) . unwrap ( ) ;
108
138
let rustfmt_config = build. src . join ( "rustfmt.toml" ) ;
109
139
if !rustfmt_config. exists ( ) {
110
- eprintln ! ( "Not running formatting checks; rustfmt.toml does not exist." ) ;
111
- eprintln ! ( "This may happen in distributed tarballs." ) ;
140
+ eprintln ! ( "fmt error: Not running formatting checks; rustfmt.toml does not exist." ) ;
141
+ eprintln ! ( "fmt error: This may happen in distributed tarballs." ) ;
112
142
return ;
113
143
}
114
144
let rustfmt_config = t ! ( std:: fs:: read_to_string( & rustfmt_config) ) ;
115
145
let rustfmt_config: RustfmtConfig = t ! ( toml:: from_str( & rustfmt_config) ) ;
116
- let mut fmt_override = ignore:: overrides:: OverrideBuilder :: new ( & build. src ) ;
146
+ let mut override_builder = ignore:: overrides:: OverrideBuilder :: new ( & build. src ) ;
117
147
for ignore in rustfmt_config. ignore {
118
148
if ignore. starts_with ( '!' ) {
119
- // A `!`-prefixed entry could be added as a whitelisted entry in `fmt_override`, i.e.
120
- // strip the `!` prefix. But as soon as whitelisted entries are added, an
149
+ // A `!`-prefixed entry could be added as a whitelisted entry in `override_builder`,
150
+ // i.e. strip the `!` prefix. But as soon as whitelisted entries are added, an
121
151
// `OverrideBuilder` will only traverse those whitelisted entries, and won't traverse
122
152
// any files that aren't explicitly mentioned. No bueno! Maybe there's a way to combine
123
153
// explicit whitelisted entries and traversal of unmentioned files, but for now just
124
154
// forbid such entries.
125
- eprintln ! ( "`!`-prefixed entries are not supported in rustfmt.toml, sorry" ) ;
155
+ eprintln ! ( "fmt error: `!`-prefixed entries are not supported in rustfmt.toml, sorry" ) ;
126
156
crate :: exit!( 1 ) ;
127
157
} else {
128
- fmt_override . add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
158
+ override_builder . add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
129
159
}
130
160
}
131
161
let git_available = match Command :: new ( "git" )
@@ -138,6 +168,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
138
168
Err ( _) => false ,
139
169
} ;
140
170
171
+ let mut adjective = None ;
141
172
if git_available {
142
173
let in_working_tree = match build
143
174
. config
@@ -161,127 +192,56 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
161
192
. arg ( "-z" )
162
193
. arg ( "--untracked-files=normal" ) ,
163
194
) ;
164
- let untracked_paths = untracked_paths_output. split_terminator ( '\0' ) . filter_map (
165
- |entry| entry. strip_prefix ( "?? " ) , // returns None if the prefix doesn't match
166
- ) ;
167
- let mut untracked_count = 0 ;
195
+ let untracked_paths: Vec < _ > = untracked_paths_output
196
+ . split_terminator ( '\0' )
197
+ . filter_map (
198
+ |entry| entry. strip_prefix ( "?? " ) , // returns None if the prefix doesn't match
199
+ )
200
+ . map ( |x| x. to_string ( ) )
201
+ . collect ( ) ;
202
+ print_paths ( "skipped" , Some ( "untracked" ) , & untracked_paths) ;
203
+
168
204
for untracked_path in untracked_paths {
169
- println ! ( "skip untracked path {untracked_path} during rustfmt invocations" ) ;
170
205
// The leading `/` makes it an exact match against the
171
206
// repository root, rather than a glob. Without that, if you
172
207
// have `foo.rs` in the repository root it will also match
173
208
// against anything like `compiler/rustc_foo/src/foo.rs`,
174
209
// preventing the latter from being formatted.
175
- untracked_count += 1 ;
176
- fmt_override. add ( & format ! ( "!/{untracked_path}" ) ) . expect ( untracked_path) ;
210
+ override_builder. add ( & format ! ( "!/{untracked_path}" ) ) . expect ( & untracked_path) ;
177
211
}
178
- // Only check modified files locally to speed up runtime. We still check all files in
179
- // CI to avoid bugs in `get_modified_rs_files` letting regressions slip through; we
180
- // also care about CI time less since this is still very fast compared to building the
181
- // compiler.
182
- if !CiEnv :: is_ci ( ) && paths. is_empty ( ) {
212
+ if !all {
213
+ adjective = Some ( "modified" ) ;
183
214
match get_modified_rs_files ( build) {
184
215
Ok ( Some ( files) ) => {
185
- if files. len ( ) <= 10 {
186
- for file in & files {
187
- println ! ( "formatting modified file {file}" ) ;
188
- }
189
- } else {
190
- let pluralized = |count| if count > 1 { "files" } else { "file" } ;
191
- let untracked_msg = if untracked_count == 0 {
192
- "" . to_string ( )
193
- } else {
194
- format ! (
195
- ", skipped {} untracked {}" ,
196
- untracked_count,
197
- pluralized( untracked_count) ,
198
- )
199
- } ;
200
- println ! (
201
- "formatting {} modified {}{}" ,
202
- files. len( ) ,
203
- pluralized( files. len( ) ) ,
204
- untracked_msg
205
- ) ;
206
- }
207
216
for file in files {
208
- fmt_override . add ( & format ! ( "/{file}" ) ) . expect ( & file) ;
217
+ override_builder . add ( & format ! ( "/{file}" ) ) . expect ( & file) ;
209
218
}
210
219
}
211
220
Ok ( None ) => { }
212
221
Err ( err) => {
213
- println ! (
214
- "WARN: Something went wrong when running git commands:\n {err}\n \
215
- Falling back to formatting all files."
216
- ) ;
222
+ eprintln ! ( "fmt warning: Something went wrong running git commands:" ) ;
223
+ eprintln ! ( "fmt warning: {err}" ) ;
224
+ eprintln ! ( "fmt warning: Falling back to formatting all files." ) ;
217
225
}
218
226
}
219
227
}
220
228
} else {
221
- println ! ( "Not in git tree. Skipping git-aware format checks" ) ;
229
+ eprintln ! ( "fmt: warning: Not in git tree. Skipping git-aware format checks" ) ;
222
230
}
223
231
} else {
224
- println ! ( "Could not find usable git. Skipping git-aware format checks" ) ;
232
+ eprintln ! ( "fmt: warning: Could not find usable git. Skipping git-aware format checks" ) ;
225
233
}
226
234
227
- let fmt_override = fmt_override . build ( ) . unwrap ( ) ;
235
+ let override_ = override_builder . build ( ) . unwrap ( ) ; // `override` is a reserved keyword
228
236
229
237
let rustfmt_path = build. initial_rustfmt ( ) . unwrap_or_else ( || {
230
- eprintln ! ( "./x.py fmt is not supported on this channel" ) ;
238
+ eprintln ! ( "fmt error: `x fmt` is not supported on this channel" ) ;
231
239
crate :: exit!( 1 ) ;
232
240
} ) ;
233
241
assert ! ( rustfmt_path. exists( ) , "{}" , rustfmt_path. display( ) ) ;
234
242
let src = build. src . clone ( ) ;
235
243
let ( tx, rx) : ( SyncSender < PathBuf > , _ ) = std:: sync:: mpsc:: sync_channel ( 128 ) ;
236
- let walker = match paths. first ( ) {
237
- Some ( first) => {
238
- let find_shortcut_candidates = |p : & PathBuf | {
239
- let mut candidates = Vec :: new ( ) ;
240
- for entry in
241
- WalkBuilder :: new ( src. clone ( ) ) . max_depth ( Some ( 3 ) ) . build ( ) . map_while ( Result :: ok)
242
- {
243
- if let Some ( dir_name) = p. file_name ( ) {
244
- if entry. path ( ) . is_dir ( ) && entry. file_name ( ) == dir_name {
245
- candidates. push ( entry. into_path ( ) ) ;
246
- }
247
- }
248
- }
249
- candidates
250
- } ;
251
-
252
- // Only try to look for shortcut candidates for single component paths like
253
- // `std` and not for e.g. relative paths like `../library/std`.
254
- let should_look_for_shortcut_dir = |p : & PathBuf | p. components ( ) . count ( ) == 1 ;
255
-
256
- let mut walker = if should_look_for_shortcut_dir ( first) {
257
- if let [ single_candidate] = & find_shortcut_candidates ( first) [ ..] {
258
- WalkBuilder :: new ( single_candidate)
259
- } else {
260
- WalkBuilder :: new ( first)
261
- }
262
- } else {
263
- WalkBuilder :: new ( src. join ( first) )
264
- } ;
265
-
266
- for path in & paths[ 1 ..] {
267
- if should_look_for_shortcut_dir ( path) {
268
- if let [ single_candidate] = & find_shortcut_candidates ( path) [ ..] {
269
- walker. add ( single_candidate) ;
270
- } else {
271
- walker. add ( path) ;
272
- }
273
- } else {
274
- walker. add ( src. join ( path) ) ;
275
- }
276
- }
277
-
278
- walker
279
- }
280
- None => WalkBuilder :: new ( src. clone ( ) ) ,
281
- }
282
- . types ( matcher)
283
- . overrides ( fmt_override)
284
- . build_parallel ( ) ;
244
+ let walker = WalkBuilder :: new ( src. clone ( ) ) . types ( matcher) . overrides ( override_) . build_parallel ( ) ;
285
245
286
246
// There is a lot of blocking involved in spawning a child process and reading files to format.
287
247
// Spawn more processes than available concurrency to keep the CPU busy.
@@ -319,16 +279,33 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
319
279
}
320
280
} ) ;
321
281
282
+ let formatted_paths = Mutex :: new ( Vec :: new ( ) ) ;
283
+ let formatted_paths_ref = & formatted_paths;
322
284
walker. run ( || {
323
285
let tx = tx. clone ( ) ;
324
286
Box :: new ( move |entry| {
287
+ let cwd = std:: env:: current_dir ( ) ;
325
288
let entry = t ! ( entry) ;
326
289
if entry. file_type ( ) . map_or ( false , |t| t. is_file ( ) ) {
290
+ formatted_paths_ref. lock ( ) . unwrap ( ) . push ( {
291
+ // `into_path` produces an absolute path. Try to strip `cwd` to get a shorter
292
+ // relative path.
293
+ let mut path = entry. clone ( ) . into_path ( ) ;
294
+ if let Ok ( cwd) = cwd {
295
+ if let Ok ( path2) = path. strip_prefix ( cwd) {
296
+ path = path2. to_path_buf ( ) ;
297
+ }
298
+ }
299
+ path. display ( ) . to_string ( )
300
+ } ) ;
327
301
t ! ( tx. send( entry. into_path( ) ) ) ;
328
302
}
329
303
ignore:: WalkState :: Continue
330
304
} )
331
305
} ) ;
306
+ let mut paths = formatted_paths. into_inner ( ) . unwrap ( ) ;
307
+ paths. sort ( ) ;
308
+ print_paths ( if check { "checked" } else { "formatted" } , adjective, & paths) ;
332
309
333
310
drop ( tx) ;
334
311
0 commit comments