@@ -9,6 +9,7 @@ use std::collections::VecDeque;
99use std:: path:: { Path , PathBuf } ;
1010use std:: process:: { Command , Stdio } ;
1111use std:: sync:: mpsc:: SyncSender ;
12+ use std:: sync:: Mutex ;
1213
1314fn rustfmt ( src : & Path , rustfmt : & Path , paths : & [ PathBuf ] , check : bool ) -> impl FnMut ( bool ) -> bool {
1415 let mut cmd = Command :: new ( rustfmt) ;
@@ -24,20 +25,23 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
2425 cmd. args ( paths) ;
2526 let cmd_debug = format ! ( "{cmd:?}" ) ;
2627 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).
2830 move |block : bool | -> bool {
29- if !block {
31+ let status = if !block {
3032 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) ,
3336 }
34- }
35- let status = cmd. wait ( ) . unwrap ( ) ;
36- if !status. success ( ) {
37+ } else {
38+ cmd. wait ( )
39+ } ;
40+ if !status. unwrap ( ) . success ( ) {
3741 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.",
4145 cmd_debug,
4246 ) ;
4347 crate :: exit!( 1 ) ;
@@ -97,35 +101,61 @@ struct RustfmtConfig {
97101 ignore : Vec < String > ,
98102}
99103
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+ } ;
101124 if build. config . dry_run ( ) {
102125 return ;
103126 }
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+
104134 let mut builder = ignore:: types:: TypesBuilder :: new ( ) ;
105135 builder. add_defaults ( ) ;
106136 builder. select ( "rust" ) ;
107137 let matcher = builder. build ( ) . unwrap ( ) ;
108138 let rustfmt_config = build. src . join ( "rustfmt.toml" ) ;
109139 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." ) ;
112142 return ;
113143 }
114144 let rustfmt_config = t ! ( std:: fs:: read_to_string( & rustfmt_config) ) ;
115145 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 ) ;
117147 for ignore in rustfmt_config. ignore {
118148 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
121151 // `OverrideBuilder` will only traverse those whitelisted entries, and won't traverse
122152 // any files that aren't explicitly mentioned. No bueno! Maybe there's a way to combine
123153 // explicit whitelisted entries and traversal of unmentioned files, but for now just
124154 // 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" ) ;
126156 crate :: exit!( 1 ) ;
127157 } else {
128- fmt_override . add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
158+ override_builder . add ( & format ! ( "!{ignore}" ) ) . expect ( & ignore) ;
129159 }
130160 }
131161 let git_available = match Command :: new ( "git" )
@@ -138,6 +168,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
138168 Err ( _) => false ,
139169 } ;
140170
171+ let mut adjective = None ;
141172 if git_available {
142173 let in_working_tree = match build
143174 . config
@@ -161,127 +192,56 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
161192 . arg ( "-z" )
162193 . arg ( "--untracked-files=normal" ) ,
163194 ) ;
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+
168204 for untracked_path in untracked_paths {
169- println ! ( "skip untracked path {untracked_path} during rustfmt invocations" ) ;
170205 // The leading `/` makes it an exact match against the
171206 // repository root, rather than a glob. Without that, if you
172207 // have `foo.rs` in the repository root it will also match
173208 // against anything like `compiler/rustc_foo/src/foo.rs`,
174209 // 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) ;
177211 }
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" ) ;
183214 match get_modified_rs_files ( build) {
184215 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- }
207216 for file in files {
208- fmt_override . add ( & format ! ( "/{file}" ) ) . expect ( & file) ;
217+ override_builder . add ( & format ! ( "/{file}" ) ) . expect ( & file) ;
209218 }
210219 }
211220 Ok ( None ) => { }
212221 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." ) ;
217225 }
218226 }
219227 }
220228 } 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" ) ;
222230 }
223231 } 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" ) ;
225233 }
226234
227- let fmt_override = fmt_override . build ( ) . unwrap ( ) ;
235+ let override_ = override_builder . build ( ) . unwrap ( ) ; // `override` is a reserved keyword
228236
229237 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" ) ;
231239 crate :: exit!( 1 ) ;
232240 } ) ;
233241 assert ! ( rustfmt_path. exists( ) , "{}" , rustfmt_path. display( ) ) ;
234242 let src = build. src . clone ( ) ;
235243 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 ( ) ;
285245
286246 // There is a lot of blocking involved in spawning a child process and reading files to format.
287247 // Spawn more processes than available concurrency to keep the CPU busy.
@@ -319,16 +279,33 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
319279 }
320280 } ) ;
321281
282+ let formatted_paths = Mutex :: new ( Vec :: new ( ) ) ;
283+ let formatted_paths_ref = & formatted_paths;
322284 walker. run ( || {
323285 let tx = tx. clone ( ) ;
324286 Box :: new ( move |entry| {
287+ let cwd = std:: env:: current_dir ( ) ;
325288 let entry = t ! ( entry) ;
326289 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+ } ) ;
327301 t ! ( tx. send( entry. into_path( ) ) ) ;
328302 }
329303 ignore:: WalkState :: Continue
330304 } )
331305 } ) ;
306+ let mut paths = formatted_paths. into_inner ( ) . unwrap ( ) ;
307+ paths. sort ( ) ;
308+ print_paths ( if check { "checked" } else { "formatted" } , adjective, & paths) ;
332309
333310 drop ( tx) ;
334311
0 commit comments