From 5cf198d0d6dc64b9c255d1eaec48f0fd5802e47f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 28 May 2024 10:17:59 +1000 Subject: [PATCH 1/5] Remove path choice from `x fmt` and add `--all` option. By default, `x fmt` formats/checks modified files. But it also lets you choose one or more paths instead. This adds significant complexity to `x fmt`. Explicit paths are specified via `WalkBuilder::add` rather than `OverrideBuilder::add`. The `ignore` library is not simple, and predicting the interactions between the two mechanisms is difficult. Here's a particularly interesting case. - You can request a path P that is excluded by the `ignore` list in the `rustfmt.toml`. E.g. `x fmt tests/ui/` or `x fmt tests/ui/bitwise.rs`. - `x fmt` will add P to the walker (via `WalkBuilder::add`), traverse it (paying no attention to the `ignore` list from the `rustfmt.toml` file, due to the different mechanism), and call `rustfmt` on every `.rs` file within it. - `rustfmt` will do nothing to those `.rs` files, because it *also* reads `rustfmt.toml` and sees that they match the `ignore` list! It took me *ages* to debug and understand this behaviour. Not good! `x fmt` even lets you name a path below the current directory. This was intended to let you do things like `x fmt std` that mirror things like `x test std`. This works by looking for `std` and finding `library/std`, and then formatting that. Unfortuantely, this motivating case now gives an error. When support was added in #107944, `library/std` was the only directory named `std`. Since then, `tests/ui/std` was added, and so `x fmt std` now gives an error. In general, explicit paths don't seem particularly useful. The only two cases `x fmt` really needs are: - format/check the files I have modified (99% of uses) - format/check all files (While respecting the `ignore` list in `rustfmt.toml`, of course.) So this commit moves to that model. `x fmt` will now give an error if given an explicit path. `x fmt` now also supports a `--all` option. (And running with `GITHUB_ACTIONS=true` also causes everything to be formatted/checked, as before.) Much simpler! --- src/bootstrap/src/core/build_steps/format.rs | 70 +++++--------------- src/bootstrap/src/core/build_steps/test.rs | 8 ++- src/bootstrap/src/core/config/flags.rs | 8 ++- src/bootstrap/src/lib.rs | 3 +- src/etc/completions/x.py.fish | 1 + src/etc/completions/x.py.ps1 | 1 + src/etc/completions/x.py.sh | 2 +- src/etc/completions/x.py.zsh | 1 + 8 files changed, 34 insertions(+), 60 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index 44f575b51da24..209249b7bd438 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -97,10 +97,21 @@ struct RustfmtConfig { ignore: Vec, } -pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { +pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { + if !paths.is_empty() { + eprintln!("path arguments are not accepted"); + crate::exit!(1); + }; if build.config.dry_run() { return; } + + // By default, we only check modified files locally to speed up runtime. Exceptions are if + // `--all` is specified or we are in CI. We check all files in CI to avoid bugs in + // `get_modified_rs_files` letting regressions slip through; we also care about CI time less + // since this is still very fast compared to building the compiler. + let all = all || CiEnv::is_ci(); + let mut builder = ignore::types::TypesBuilder::new(); builder.add_defaults(); builder.select("rust"); @@ -175,11 +186,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { untracked_count += 1; fmt_override.add(&format!("!/{untracked_path}")).expect(untracked_path); } - // Only check modified files locally to speed up runtime. We still check all files in - // CI to avoid bugs in `get_modified_rs_files` letting regressions slip through; we - // also care about CI time less since this is still very fast compared to building the - // compiler. - if !CiEnv::is_ci() && paths.is_empty() { + if !all { match get_modified_rs_files(build) { Ok(Some(files)) => { if files.len() <= 10 { @@ -233,55 +240,8 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); let src = build.src.clone(); let (tx, rx): (SyncSender, _) = std::sync::mpsc::sync_channel(128); - let walker = match paths.first() { - Some(first) => { - let find_shortcut_candidates = |p: &PathBuf| { - let mut candidates = Vec::new(); - for entry in - WalkBuilder::new(src.clone()).max_depth(Some(3)).build().map_while(Result::ok) - { - if let Some(dir_name) = p.file_name() { - if entry.path().is_dir() && entry.file_name() == dir_name { - candidates.push(entry.into_path()); - } - } - } - candidates - }; - - // Only try to look for shortcut candidates for single component paths like - // `std` and not for e.g. relative paths like `../library/std`. - let should_look_for_shortcut_dir = |p: &PathBuf| p.components().count() == 1; - - let mut walker = if should_look_for_shortcut_dir(first) { - if let [single_candidate] = &find_shortcut_candidates(first)[..] { - WalkBuilder::new(single_candidate) - } else { - WalkBuilder::new(first) - } - } else { - WalkBuilder::new(src.join(first)) - }; - - for path in &paths[1..] { - if should_look_for_shortcut_dir(path) { - if let [single_candidate] = &find_shortcut_candidates(path)[..] { - walker.add(single_candidate); - } else { - walker.add(path); - } - } else { - walker.add(src.join(path)); - } - } - - walker - } - None => WalkBuilder::new(src.clone()), - } - .types(matcher) - .overrides(fmt_override) - .build_parallel(); + let walker = + WalkBuilder::new(src.clone()).types(matcher).overrides(fmt_override).build_parallel(); // There is a lot of blocking involved in spawning a child process and reading files to format. // Spawn more processes than available concurrency to keep the CPU busy. diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 360bd3840d456..2158868636242 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1140,7 +1140,13 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to ); crate::exit!(1); } - crate::core::build_steps::format::format(builder, !builder.config.cmd.bless(), &[]); + let all = false; + crate::core::build_steps::format::format( + builder, + !builder.config.cmd.bless(), + all, + &[], + ); } builder.info("tidy check"); diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index f4ed7e76fba11..83def0c6df0c5 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -284,8 +284,8 @@ pub enum Subcommand { name = "fmt", long_about = "\n Arguments: - This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and - fails if it is not. For example: + This subcommand optionally accepts a `--check` flag which succeeds if + formatting is correct and fails if it is not. For example: ./x.py fmt ./x.py fmt --check" )] @@ -294,6 +294,10 @@ pub enum Subcommand { /// check formatting instead of applying #[arg(long)] check: bool, + + /// apply to all appropriate files, not just those that have been modified + #[arg(long)] + all: bool, }, #[command(aliases = ["d"], long_about = "\n Arguments: diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 52c94465cd33d..8312885915c53 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -660,10 +660,11 @@ impl Build { // hardcoded subcommands match &self.config.cmd { - Subcommand::Format { check } => { + Subcommand::Format { check, all } => { return core::build_steps::format::format( &builder::Builder::new(self), *check, + *all, &self.config.paths, ); } diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish index 40a25f13fcbdd..7343f3147eed3 100644 --- a/src/etc/completions/x.py.fish +++ b/src/etc/completions/x.py.fish @@ -216,6 +216,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-profile-use -d 'us complete -c x.py -n "__fish_seen_subcommand_from fmt" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from fmt" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from fmt" -l check -d 'check formatting instead of applying' +complete -c x.py -n "__fish_seen_subcommand_from fmt" -l all -d 'apply to all appropriate files, not just those that have been modified' complete -c x.py -n "__fish_seen_subcommand_from fmt" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from fmt" -s i -l incremental -d 'use incremental compilation' complete -c x.py -n "__fish_seen_subcommand_from fmt" -l include-default-paths -d 'include default paths in addition to the provided ones' diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1 index f3d1d372c7337..d9adb1778f2f8 100644 --- a/src/etc/completions/x.py.ps1 +++ b/src/etc/completions/x.py.ps1 @@ -275,6 +275,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'check formatting instead of applying') + [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'apply to all appropriate files, not just those that have been modified') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'use incremental compilation') diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh index 82cacb52ffeef..6cb9e95c8c102 100644 --- a/src/etc/completions/x.py.sh +++ b/src/etc/completions/x.py.sh @@ -1077,7 +1077,7 @@ _x.py() { return 0 ;; x.py__fmt) - opts="-v -i -j -h --check --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --check --all --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh index 12e96dbd40a31..24ddd1c4b7ccb 100644 --- a/src/etc/completions/x.py.zsh +++ b/src/etc/completions/x.py.zsh @@ -271,6 +271,7 @@ _arguments "${_arguments_options[@]}" \ '*--reproducible-artifact=[Additional reproducible artifacts that should be added to the reproducible artifacts archive]:REPRODUCIBLE_ARTIFACT: ' \ '*--set=[override options in config.toml]:section.option=value:( )' \ '--check[check formatting instead of applying]' \ +'--all[apply to all appropriate files, not just those that have been modified]' \ '*-v[use verbose output (-vv for very verbose)]' \ '*--verbose[use verbose output (-vv for very verbose)]' \ '-i[use incremental compilation]' \ From e98740aa0daa288b44925864a487fab7d4c536d5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 May 2024 13:06:42 +1000 Subject: [PATCH 2/5] Clarify `x fmt` messages. - Precede them all with `fmt` so it's clear where they are coming from. - Use `error:` and `warning:` when appropriate. - Print warnings to stderr instead of stdout --- src/bootstrap/src/core/build_steps/format.rs | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index 209249b7bd438..f3e8d765dba6a 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -35,9 +35,9 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F let status = cmd.wait().unwrap(); if !status.success() { eprintln!( - "Running `{}` failed.\nIf you're running `tidy`, \ - try again with `--bless`. Or, if you just want to format \ - code, run `./x.py fmt` instead.", + "fmt error: Running `{}` failed.\nIf you're running `tidy`, \ + try again with `--bless`. Or, if you just want to format \ + code, run `./x.py fmt` instead.", cmd_debug, ); crate::exit!(1); @@ -99,7 +99,7 @@ struct RustfmtConfig { pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { if !paths.is_empty() { - eprintln!("path arguments are not accepted"); + eprintln!("fmt error: path arguments are not accepted"); crate::exit!(1); }; if build.config.dry_run() { @@ -118,8 +118,8 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { let matcher = builder.build().unwrap(); let rustfmt_config = build.src.join("rustfmt.toml"); if !rustfmt_config.exists() { - eprintln!("Not running formatting checks; rustfmt.toml does not exist."); - eprintln!("This may happen in distributed tarballs."); + eprintln!("fmt error: Not running formatting checks; rustfmt.toml does not exist."); + eprintln!("fmt error: This may happen in distributed tarballs."); return; } let rustfmt_config = t!(std::fs::read_to_string(&rustfmt_config)); @@ -133,7 +133,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { // any files that aren't explicitly mentioned. No bueno! Maybe there's a way to combine // explicit whitelisted entries and traversal of unmentioned files, but for now just // forbid such entries. - eprintln!("`!`-prefixed entries are not supported in rustfmt.toml, sorry"); + eprintln!("fmt error: `!`-prefixed entries are not supported in rustfmt.toml, sorry"); crate::exit!(1); } else { fmt_override.add(&format!("!{ignore}")).expect(&ignore); @@ -177,7 +177,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { ); let mut untracked_count = 0; for untracked_path in untracked_paths { - println!("skip untracked path {untracked_path} during rustfmt invocations"); + println!("fmt: skip untracked path {untracked_path} during rustfmt invocations"); // The leading `/` makes it an exact match against the // repository root, rather than a glob. Without that, if you // have `foo.rs` in the repository root it will also match @@ -191,7 +191,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { Ok(Some(files)) => { if files.len() <= 10 { for file in &files { - println!("formatting modified file {file}"); + println!("fmt: formatting modified file {file}"); } } else { let pluralized = |count| if count > 1 { "files" } else { "file" }; @@ -205,7 +205,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { ) }; println!( - "formatting {} modified {}{}", + "fmt: formatting {} modified {}{}", files.len(), pluralized(files.len()), untracked_msg @@ -217,24 +217,23 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { } Ok(None) => {} Err(err) => { - println!( - "WARN: Something went wrong when running git commands:\n{err}\n\ - Falling back to formatting all files." - ); + eprintln!("fmt warning: Something went wrong running git commands:"); + eprintln!("fmt warning: {err}"); + eprintln!("fmt warning: Falling back to formatting all files."); } } } } else { - println!("Not in git tree. Skipping git-aware format checks"); + eprintln!("fmt: warning: Not in git tree. Skipping git-aware format checks"); } } else { - println!("Could not find usable git. Skipping git-aware format checks"); + eprintln!("fmt: warning: Could not find usable git. Skipping git-aware format checks"); } let fmt_override = fmt_override.build().unwrap(); let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| { - eprintln!("./x.py fmt is not supported on this channel"); + eprintln!("fmt error: `x fmt` is not supported on this channel"); crate::exit!(1); }); assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); From 3d5d6d222084db76ac152b6c813a3bc177bec8ce Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 May 2024 13:18:37 +1000 Subject: [PATCH 3/5] Adjust `x fmt` printed output. Currently, `x fmt` can print two lists of files. - The untracked files that are skipped. Always done if within a git repo. - The modified files that are formatted. But if you run with `--all` (or with `GITHUB_ACTIONS=true`) it doesn't print anything about which files are formatted. This commit increases consistency. - The formatted/checked files are now always printed. And it makes it clear why a file was formatted, e.g. with "modified". - It uses the same code for both untracked files and formatted/checked files. This means that now if there are a lot of untracked files just the number will be printed, which is like the old behaviour for modified files. Example output: ``` fmt: skipped 31 untracked files fmt: formatted modified file compiler/rustc_mir_transform/src/instsimplify.rs fmt: formatted modified file compiler/rustc_mir_transform/src/validate.rs fmt: formatted modified file library/core/src/ptr/metadata.rs fmt: formatted modified file src/bootstrap/src/core/build_steps/format.rs ``` or (with `--all`): ``` fmt: checked 3148 files ``` --- src/bootstrap/src/core/build_steps/format.rs | 74 ++++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index f3e8d765dba6a..aca1b82f530f4 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -9,6 +9,7 @@ use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::mpsc::SyncSender; +use std::sync::Mutex; fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut(bool) -> bool { let mut cmd = Command::new(rustfmt); @@ -97,6 +98,21 @@ struct RustfmtConfig { ignore: Vec, } +// Prints output describing a collection of paths, with lines such as "formatted modified file +// foo/bar/baz" or "skipped 20 untracked files". +fn print_paths(verb: &str, adjective: Option<&str>, paths: &[String]) { + let len = paths.len(); + let adjective = + if let Some(adjective) = adjective { format!("{adjective} ") } else { String::new() }; + if len <= 10 { + for path in paths { + println!("fmt: {verb} {adjective}file {path}"); + } + } else { + println!("fmt: {verb} {len} {adjective}files"); + } +} + pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { if !paths.is_empty() { eprintln!("fmt error: path arguments are not accepted"); @@ -149,6 +165,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { Err(_) => false, }; + let mut adjective = None; if git_available { let in_working_tree = match build .config @@ -172,45 +189,27 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { .arg("-z") .arg("--untracked-files=normal"), ); - let untracked_paths = untracked_paths_output.split_terminator('\0').filter_map( - |entry| entry.strip_prefix("?? "), // returns None if the prefix doesn't match - ); - let mut untracked_count = 0; + let untracked_paths: Vec<_> = untracked_paths_output + .split_terminator('\0') + .filter_map( + |entry| entry.strip_prefix("?? "), // returns None if the prefix doesn't match + ) + .map(|x| x.to_string()) + .collect(); + print_paths("skipped", Some("untracked"), &untracked_paths); + for untracked_path in untracked_paths { - println!("fmt: skip untracked path {untracked_path} during rustfmt invocations"); // The leading `/` makes it an exact match against the // repository root, rather than a glob. Without that, if you // have `foo.rs` in the repository root it will also match // against anything like `compiler/rustc_foo/src/foo.rs`, // preventing the latter from being formatted. - untracked_count += 1; - fmt_override.add(&format!("!/{untracked_path}")).expect(untracked_path); + fmt_override.add(&format!("!/{untracked_path}")).expect(&untracked_path); } if !all { + adjective = Some("modified"); match get_modified_rs_files(build) { Ok(Some(files)) => { - if files.len() <= 10 { - for file in &files { - println!("fmt: formatting modified file {file}"); - } - } else { - let pluralized = |count| if count > 1 { "files" } else { "file" }; - let untracked_msg = if untracked_count == 0 { - "".to_string() - } else { - format!( - ", skipped {} untracked {}", - untracked_count, - pluralized(untracked_count), - ) - }; - println!( - "fmt: formatting {} modified {}{}", - files.len(), - pluralized(files.len()), - untracked_msg - ); - } for file in files { fmt_override.add(&format!("/{file}")).expect(&file); } @@ -278,16 +277,33 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { } }); + let formatted_paths = Mutex::new(Vec::new()); + let formatted_paths_ref = &formatted_paths; walker.run(|| { let tx = tx.clone(); Box::new(move |entry| { + let cwd = std::env::current_dir(); let entry = t!(entry); if entry.file_type().map_or(false, |t| t.is_file()) { + formatted_paths_ref.lock().unwrap().push({ + // `into_path` produces an absolute path. Try to strip `cwd` to get a shorter + // relative path. + let mut path = entry.clone().into_path(); + if let Ok(cwd) = cwd { + if let Ok(path2) = path.strip_prefix(cwd) { + path = path2.to_path_buf(); + } + } + path.display().to_string() + }); t!(tx.send(entry.into_path())); } ignore::WalkState::Continue }) }); + let mut paths = formatted_paths.into_inner().unwrap(); + paths.sort(); + print_paths(if check { "checked" } else { "formatted" }, adjective, &paths); drop(tx); From a22cfccca2a80d15f6b015697e0ada9379d16f73 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 May 2024 13:24:05 +1000 Subject: [PATCH 4/5] Rename `fmt_override`. It's a weird name, the `fmt_` prefix seems unnecessary. --- src/bootstrap/src/core/build_steps/format.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index aca1b82f530f4..58342f2d902b4 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -140,11 +140,11 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { } let rustfmt_config = t!(std::fs::read_to_string(&rustfmt_config)); let rustfmt_config: RustfmtConfig = t!(toml::from_str(&rustfmt_config)); - let mut fmt_override = ignore::overrides::OverrideBuilder::new(&build.src); + let mut override_builder = ignore::overrides::OverrideBuilder::new(&build.src); for ignore in rustfmt_config.ignore { if ignore.starts_with('!') { - // A `!`-prefixed entry could be added as a whitelisted entry in `fmt_override`, i.e. - // strip the `!` prefix. But as soon as whitelisted entries are added, an + // A `!`-prefixed entry could be added as a whitelisted entry in `override_builder`, + // i.e. strip the `!` prefix. But as soon as whitelisted entries are added, an // `OverrideBuilder` will only traverse those whitelisted entries, and won't traverse // any files that aren't explicitly mentioned. No bueno! Maybe there's a way to combine // explicit whitelisted entries and traversal of unmentioned files, but for now just @@ -152,7 +152,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { eprintln!("fmt error: `!`-prefixed entries are not supported in rustfmt.toml, sorry"); crate::exit!(1); } else { - fmt_override.add(&format!("!{ignore}")).expect(&ignore); + override_builder.add(&format!("!{ignore}")).expect(&ignore); } } let git_available = match Command::new("git") @@ -204,14 +204,14 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { // have `foo.rs` in the repository root it will also match // against anything like `compiler/rustc_foo/src/foo.rs`, // preventing the latter from being formatted. - fmt_override.add(&format!("!/{untracked_path}")).expect(&untracked_path); + override_builder.add(&format!("!/{untracked_path}")).expect(&untracked_path); } if !all { adjective = Some("modified"); match get_modified_rs_files(build) { Ok(Some(files)) => { for file in files { - fmt_override.add(&format!("/{file}")).expect(&file); + override_builder.add(&format!("/{file}")).expect(&file); } } Ok(None) => {} @@ -229,7 +229,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { eprintln!("fmt: warning: Could not find usable git. Skipping git-aware format checks"); } - let fmt_override = fmt_override.build().unwrap(); + let override_ = override_builder.build().unwrap(); // `override` is a reserved keyword let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| { eprintln!("fmt error: `x fmt` is not supported on this channel"); @@ -238,8 +238,7 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) { assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); let src = build.src.clone(); let (tx, rx): (SyncSender, _) = std::sync::mpsc::sync_channel(128); - let walker = - WalkBuilder::new(src.clone()).types(matcher).overrides(fmt_override).build_parallel(); + let walker = WalkBuilder::new(src.clone()).types(matcher).overrides(override_).build_parallel(); // There is a lot of blocking involved in spawning a child process and reading files to format. // Spawn more processes than available concurrency to keep the CPU busy. From 4dec0a0e9938c0762b6bfcce8b611d5096e32880 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 May 2024 15:36:12 +1000 Subject: [PATCH 5/5] Clarify the closure in `rustfmt`. - Avoid calling `try_wait` followed immediately by `wait`. - Make the match exhaustive. - Improve the comment. --- src/bootstrap/src/core/build_steps/format.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs index 58342f2d902b4..601e4e55e0946 100644 --- a/src/bootstrap/src/core/build_steps/format.rs +++ b/src/bootstrap/src/core/build_steps/format.rs @@ -25,16 +25,19 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F cmd.args(paths); let cmd_debug = format!("{cmd:?}"); let mut cmd = cmd.spawn().expect("running rustfmt"); - // Poor man's async: return a closure that'll wait for rustfmt's completion. + // Poor man's async: return a closure that might wait for rustfmt's completion (depending on + // the value of the `block` argument). move |block: bool| -> bool { - if !block { + let status = if !block { match cmd.try_wait() { - Ok(Some(_)) => {} - _ => return false, + Ok(Some(status)) => Ok(status), + Ok(None) => return false, + Err(err) => Err(err), } - } - let status = cmd.wait().unwrap(); - if !status.success() { + } else { + cmd.wait() + }; + if !status.unwrap().success() { eprintln!( "fmt error: Running `{}` failed.\nIf you're running `tidy`, \ try again with `--bless`. Or, if you just want to format \