Skip to content

Commit c62fa82

Browse files
authored
Rollup merge of rust-lang#125699 - nnethercote:streamline-rustfmt, r=GuillaumeGomez
Streamline `x fmt` and improve its output - Removes the ability to pass paths to `x fmt`, because it's complicated and not useful, and adds `--all`. - Improves `x fmt` output. - Improves `x fmt`'s internal code. r? ``@GuillaumeGomez``
2 parents c2c8e90 + 4dec0a0 commit c62fa82

File tree

8 files changed

+106
-115
lines changed

8 files changed

+106
-115
lines changed

src/bootstrap/src/core/build_steps/format.rs

+87-110
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::collections::VecDeque;
99
use std::path::{Path, PathBuf};
1010
use std::process::{Command, Stdio};
1111
use std::sync::mpsc::SyncSender;
12+
use std::sync::Mutex;
1213

1314
fn 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.\nIf 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.\nIf 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

src/bootstrap/src/core/build_steps/test.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,13 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to
11401140
);
11411141
crate::exit!(1);
11421142
}
1143-
crate::core::build_steps::format::format(builder, !builder.config.cmd.bless(), &[]);
1143+
let all = false;
1144+
crate::core::build_steps::format::format(
1145+
builder,
1146+
!builder.config.cmd.bless(),
1147+
all,
1148+
&[],
1149+
);
11441150
}
11451151

11461152
builder.info("tidy check");

src/bootstrap/src/core/config/flags.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ pub enum Subcommand {
284284
name = "fmt",
285285
long_about = "\n
286286
Arguments:
287-
This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
288-
fails if it is not. For example:
287+
This subcommand optionally accepts a `--check` flag which succeeds if
288+
formatting is correct and fails if it is not. For example:
289289
./x.py fmt
290290
./x.py fmt --check"
291291
)]
@@ -294,6 +294,10 @@ pub enum Subcommand {
294294
/// check formatting instead of applying
295295
#[arg(long)]
296296
check: bool,
297+
298+
/// apply to all appropriate files, not just those that have been modified
299+
#[arg(long)]
300+
all: bool,
297301
},
298302
#[command(aliases = ["d"], long_about = "\n
299303
Arguments:

src/bootstrap/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -660,10 +660,11 @@ impl Build {
660660

661661
// hardcoded subcommands
662662
match &self.config.cmd {
663-
Subcommand::Format { check } => {
663+
Subcommand::Format { check, all } => {
664664
return core::build_steps::format::format(
665665
&builder::Builder::new(self),
666666
*check,
667+
*all,
667668
&self.config.paths,
668669
);
669670
}

src/etc/completions/x.py.fish

+1
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-profile-use -d 'us
216216
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
217217
complete -c x.py -n "__fish_seen_subcommand_from fmt" -l set -d 'override options in config.toml' -r -f
218218
complete -c x.py -n "__fish_seen_subcommand_from fmt" -l check -d 'check formatting instead of applying'
219+
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'
219220
complete -c x.py -n "__fish_seen_subcommand_from fmt" -s v -l verbose -d 'use verbose output (-vv for very verbose)'
220221
complete -c x.py -n "__fish_seen_subcommand_from fmt" -s i -l incremental -d 'use incremental compilation'
221222
complete -c x.py -n "__fish_seen_subcommand_from fmt" -l include-default-paths -d 'include default paths in addition to the provided ones'

src/etc/completions/x.py.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
275275
[CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive')
276276
[CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml')
277277
[CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'check formatting instead of applying')
278+
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'apply to all appropriate files, not just those that have been modified')
278279
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)')
279280
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)')
280281
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'use incremental compilation')

src/etc/completions/x.py.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ _x.py() {
10771077
return 0
10781078
;;
10791079
x.py__fmt)
1080-
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]..."
1080+
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]..."
10811081
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
10821082
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
10831083
return 0

src/etc/completions/x.py.zsh

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ _arguments "${_arguments_options[@]}" \
271271
'*--reproducible-artifact=[Additional reproducible artifacts that should be added to the reproducible artifacts archive]:REPRODUCIBLE_ARTIFACT: ' \
272272
'*--set=[override options in config.toml]:section.option=value:( )' \
273273
'--check[check formatting instead of applying]' \
274+
'--all[apply to all appropriate files, not just those that have been modified]' \
274275
'*-v[use verbose output (-vv for very verbose)]' \
275276
'*--verbose[use verbose output (-vv for very verbose)]' \
276277
'-i[use incremental compilation]' \

0 commit comments

Comments
 (0)