Skip to content

Commit 2293743

Browse files
committed
Improve speed of cargo dev fmt.
1 parent f00c58b commit 2293743

File tree

3 files changed

+93
-86
lines changed

3 files changed

+93
-86
lines changed

clippy_dev/src/fmt.rs

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::utils::{
2-
ClippyInfo, ErrAction, FileUpdater, UpdateMode, UpdateStatus, panic_action, run_with_args_split, run_with_output,
2+
ErrAction, FileUpdater, UpdateMode, UpdateStatus, panic_action, run_with_output, split_args_for_threads,
33
};
44
use itertools::Itertools;
55
use rustc_lexer::{TokenKind, tokenize};
@@ -260,50 +260,37 @@ fn fmt_syms(update_mode: UpdateMode) {
260260
);
261261
}
262262

263-
fn run_rustfmt(clippy: &ClippyInfo, update_mode: UpdateMode) {
263+
fn run_rustfmt(update_mode: UpdateMode) {
264264
let mut rustfmt_path = String::from_utf8(run_with_output(
265265
"rustup which rustfmt",
266266
Command::new("rustup").args(["which", "rustfmt"]),
267267
))
268268
.expect("invalid rustfmt path");
269269
rustfmt_path.truncate(rustfmt_path.trim_end().len());
270270

271-
let mut cargo_path = String::from_utf8(run_with_output(
272-
"rustup which cargo",
273-
Command::new("rustup").args(["which", "cargo"]),
274-
))
275-
.expect("invalid cargo path");
276-
cargo_path.truncate(cargo_path.trim_end().len());
277-
278-
// Start all format jobs first before waiting on the results.
279-
let mut children = Vec::with_capacity(16);
280-
for &path in &[
281-
".",
282-
"clippy_config",
283-
"clippy_dev",
284-
"clippy_lints",
285-
"clippy_lints_internal",
286-
"clippy_utils",
287-
"rustc_tools_util",
288-
"lintcheck",
289-
] {
290-
let mut cmd = Command::new(&cargo_path);
291-
cmd.current_dir(clippy.path.join(path))
292-
.args(["fmt"])
293-
.env("RUSTFMT", &rustfmt_path)
294-
.stdout(Stdio::null())
295-
.stdin(Stdio::null())
296-
.stderr(Stdio::piped());
297-
if update_mode.is_check() {
298-
cmd.arg("--check");
299-
}
300-
match cmd.spawn() {
301-
Ok(x) => children.push(("cargo fmt", x)),
302-
Err(ref e) => panic_action(&e, ErrAction::Run, "cargo fmt".as_ref()),
303-
}
304-
}
271+
let args: Vec<_> = WalkDir::new(".")
272+
.into_iter()
273+
.filter_entry(|e|
274+
// Skip traversing some of the larger directories.
275+
e.path()
276+
.as_os_str()
277+
.as_encoded_bytes()
278+
.get(2..)
279+
.is_none_or(|x| x != "target".as_bytes() && x != ".git".as_bytes())
280+
&& e.path().file_name().is_none_or(|x| x != "skip_rustfmt"))
281+
// e.path().file_name().is_none_or(|x| x != "skip_rustfmt"))
282+
.filter_map(|e| {
283+
let e = e.expect("error reading `tests`");
284+
e.path()
285+
.as_os_str()
286+
.as_encoded_bytes()
287+
.ends_with(b".rs")
288+
.then(|| e.into_path().into_os_string())
289+
})
290+
.collect();
305291

306-
run_with_args_split(
292+
let mut children: Vec<_> = split_args_for_threads(
293+
32,
307294
|| {
308295
let mut cmd = Command::new(&rustfmt_path);
309296
if update_mode.is_check() {
@@ -312,27 +299,23 @@ fn run_rustfmt(clippy: &ClippyInfo, update_mode: UpdateMode) {
312299
cmd.stdout(Stdio::null())
313300
.stdin(Stdio::null())
314301
.stderr(Stdio::piped())
315-
.args(["--config", "show_parse_errors=false"]);
302+
.args([
303+
"--config",
304+
"show_parse_errors=false",
305+
"--unstable-features",
306+
"--skip-children",
307+
]);
316308
cmd
317309
},
318-
|cmd| match cmd.spawn() {
319-
Ok(x) => children.push(("rustfmt", x)),
320-
Err(ref e) => panic_action(&e, ErrAction::Run, "rustfmt".as_ref()),
321-
},
322-
WalkDir::new("tests")
323-
.into_iter()
324-
.filter_entry(|p| p.path().file_name().is_none_or(|x| x != "skip_rustfmt"))
325-
.filter_map(|e| {
326-
let e = e.expect("error reading `tests`");
327-
e.path()
328-
.as_os_str()
329-
.as_encoded_bytes()
330-
.ends_with(b".rs")
331-
.then(|| e.into_path().into_os_string())
332-
}),
333-
);
310+
args.iter(),
311+
)
312+
.map(|mut cmd| match cmd.spawn() {
313+
Ok(x) => x,
314+
Err(ref e) => panic_action(&e, ErrAction::Run, "rustfmt".as_ref()),
315+
})
316+
.collect();
334317

335-
for (name, child) in &mut children {
318+
for child in &mut children {
336319
match child.wait() {
337320
Ok(status) => match (update_mode, status.exit_ok()) {
338321
(UpdateMode::Check | UpdateMode::Change, Ok(())) => {},
@@ -353,25 +336,17 @@ fn run_rustfmt(clippy: &ClippyInfo, update_mode: UpdateMode) {
353336
{
354337
eprintln!("{s}");
355338
}
356-
panic_action(&e, ErrAction::Run, name.as_ref());
339+
panic_action(&e, ErrAction::Run, "rustfmt".as_ref());
357340
},
358341
},
359-
Err(ref e) => panic_action(e, ErrAction::Run, name.as_ref()),
342+
Err(ref e) => panic_action(e, ErrAction::Run, "rustfmt".as_ref()),
360343
}
361344
}
362345
}
363346

364347
// the "main" function of cargo dev fmt
365-
pub fn run(clippy: &ClippyInfo, update_mode: UpdateMode) {
366-
if clippy.has_intellij_hook {
367-
eprintln!(
368-
"error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\
369-
Not formatting because that would format the local repo as well!\n\
370-
Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`."
371-
);
372-
return;
373-
}
374-
run_rustfmt(clippy, update_mode);
348+
pub fn run(update_mode: UpdateMode) {
349+
run_rustfmt(update_mode);
375350
fmt_syms(update_mode);
376351
if let Err(e) = fmt_conf(update_mode.is_check()) {
377352
e.display();

clippy_dev/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn main() {
2626
allow_staged,
2727
allow_no_vcs,
2828
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
29-
DevCommand::Fmt { check } => fmt::run(&clippy, utils::UpdateMode::from_check(check)),
29+
DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
3030
DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
3131
DevCommand::NewLint {
3232
pass,

clippy_dev/src/utils.rs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use core::ops::Range;
33
use core::slice;
44
use core::str::FromStr;
55
use rustc_lexer::{self as lexer, FrontmatterAllowed};
6-
use std::env;
76
use std::ffi::OsStr;
87
use std::fs::{self, OpenOptions};
98
use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
109
use std::path::{Path, PathBuf};
1110
use std::process::{self, Command, ExitStatus, Stdio};
11+
use std::{env, thread};
1212

1313
#[cfg(not(windows))]
1414
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
@@ -682,25 +682,57 @@ pub fn run_with_output(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) ->
682682
f(path.as_ref(), cmd)
683683
}
684684

685-
pub fn run_with_args_split(
686-
mut make_cmd: impl FnMut() -> Command,
687-
mut run_cmd: impl FnMut(&mut Command),
688-
args: impl Iterator<Item: AsRef<OsStr>>,
689-
) {
690-
let mut cmd = make_cmd();
691-
let mut len = 0;
692-
for arg in args {
693-
len += arg.as_ref().len();
694-
cmd.arg(arg);
695-
// Very conservative limit
696-
if len > 10000 {
697-
run_cmd(&mut cmd);
698-
cmd = make_cmd();
699-
len = 0;
685+
/// Splits an argument list across multiple `Command` invocations.
686+
///
687+
/// The argument list will be split into a number of batches based on
688+
/// `thread::available_parallelism`, with `min_batch_size` setting a lower bound on the size of each
689+
/// batch.
690+
///
691+
/// If the size of the arguments would exceed the system limit additional batches will be created.
692+
pub fn split_args_for_threads(
693+
min_batch_size: usize,
694+
make_cmd: impl FnMut() -> Command,
695+
args: impl ExactSizeIterator<Item: AsRef<OsStr>>,
696+
) -> impl Iterator<Item = Command> {
697+
struct Iter<F, I> {
698+
make_cmd: F,
699+
args: I,
700+
batch_size: usize,
701+
thread_count: usize,
702+
}
703+
impl<F, I> Iterator for Iter<F, I>
704+
where
705+
F: FnMut() -> Command,
706+
I: ExactSizeIterator<Item: AsRef<OsStr>>,
707+
{
708+
type Item = Command;
709+
fn next(&mut self) -> Option<Self::Item> {
710+
if self.thread_count > 1 {
711+
self.thread_count -= 1;
712+
}
713+
let mut cmd = (self.make_cmd)();
714+
let mut cmd_len = 0usize;
715+
for arg in self.args.by_ref().take(self.batch_size) {
716+
cmd.arg(arg.as_ref());
717+
cmd_len += arg.as_ref().len();
718+
cmd_len += 8;
719+
720+
// Windows has a command length limit of 32767; stop before we hit that.
721+
if cmd_len > 30000 {
722+
self.batch_size = (self.args.len() / self.thread_count).max(32);
723+
break;
724+
}
725+
}
726+
(cmd_len != 0).then_some(cmd)
700727
}
701728
}
702-
if len != 0 {
703-
run_cmd(&mut cmd);
729+
let thread_count = thread::available_parallelism().map_or(1, |x| x.get());
730+
let batch_size = (args.len() / thread_count).max(min_batch_size);
731+
Iter {
732+
make_cmd,
733+
args,
734+
batch_size,
735+
thread_count,
704736
}
705737
}
706738

0 commit comments

Comments
 (0)