]> git.proxmox.com Git - rustc.git/blobdiff - src/bootstrap/format.rs
New upstream version 1.61.0+dfsg1
[rustc.git] / src / bootstrap / format.rs
index 0ae9f9712d569bb2dc55e65462c7d437ddf1552e..10b846e6db2087e67181242b2346663c09b4548e 100644 (file)
@@ -1,33 +1,39 @@
 //! Runs rustfmt on the repository.
 
+use crate::util::{output, t};
 use crate::Build;
-use build_helper::{output, t};
 use ignore::WalkBuilder;
-use std::path::Path;
+use std::collections::VecDeque;
+use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
+use std::sync::mpsc::SyncSender;
 
-fn rustfmt(src: &Path, rustfmt: &Path, path: &Path, check: bool) {
+fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut() {
     let mut cmd = Command::new(&rustfmt);
     // avoid the submodule config paths from coming into play,
     // we only allow a single global config for the workspace for now
     cmd.arg("--config-path").arg(&src.canonicalize().unwrap());
-    cmd.arg("--edition").arg("2018");
+    cmd.arg("--edition").arg("2021");
     cmd.arg("--unstable-features");
     cmd.arg("--skip-children");
     if check {
         cmd.arg("--check");
     }
-    cmd.arg(&path);
+    cmd.args(paths);
     let cmd_debug = format!("{:?}", cmd);
-    let status = cmd.status().expect("executing rustfmt");
-    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.",
-            cmd_debug,
-        );
-        std::process::exit(1);
+    let mut cmd = cmd.spawn().expect("running rustfmt");
+    // poor man's async: return a closure that'll wait for rustfmt's completion
+    move || {
+        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.",
+                cmd_debug,
+            );
+            std::process::exit(1);
+        }
     }
 }
 
@@ -36,7 +42,7 @@ struct RustfmtConfig {
     ignore: Vec<String>,
 }
 
-pub fn format(build: &Build, check: bool) {
+pub fn format(build: &Build, check: bool, paths: &[PathBuf]) {
     if build.config.dry_run {
         return;
     }
@@ -91,7 +97,12 @@ pub fn format(build: &Build, check: bool) {
                 });
             for untracked_path in untracked_paths {
                 eprintln!("skip untracked path {} during rustfmt invocations", untracked_path);
-                ignore_fmt.add(&format!("!{}", untracked_path)).expect(&untracked_path);
+                // 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.
+                ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
             }
         } else {
             eprintln!("Not in git tree. Skipping git-aware format checks");
@@ -101,19 +112,69 @@ pub fn format(build: &Build, check: bool) {
     }
     let ignore_fmt = ignore_fmt.build().unwrap();
 
-    let rustfmt_path = build.config.initial_rustfmt.as_ref().unwrap_or_else(|| {
-        eprintln!("./x.py fmt is not supported on this channel");
-        std::process::exit(1);
+    let rustfmt_path = build
+        .config
+        .initial_rustfmt
+        .as_ref()
+        .unwrap_or_else(|| {
+            eprintln!("./x.py fmt is not supported on this channel");
+            std::process::exit(1);
+        })
+        .to_path_buf();
+    let src = build.src.clone();
+    let (tx, rx): (SyncSender<PathBuf>, _) = std::sync::mpsc::sync_channel(128);
+    let walker = match paths.get(0) {
+        Some(first) => {
+            let mut walker = WalkBuilder::new(first);
+            for path in &paths[1..] {
+                walker.add(path);
+            }
+            walker
+        }
+        None => WalkBuilder::new(src.clone()),
+    }
+    .types(matcher)
+    .overrides(ignore_fmt)
+    .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
+    let max_processes = build.jobs() as usize * 2;
+
+    // spawn child processes on a separate thread so we can batch entries we have received from ignore
+    let thread = std::thread::spawn(move || {
+        let mut children = VecDeque::new();
+        while let Ok(path) = rx.recv() {
+            // try getting a few more paths from the channel to amortize the overhead of spawning processes
+            let paths: Vec<_> = rx.try_iter().take(7).chain(std::iter::once(path)).collect();
+
+            let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check);
+            children.push_back(child);
+
+            if children.len() >= max_processes {
+                // await oldest child
+                children.pop_front().unwrap()();
+            }
+        }
+
+        // await remaining children
+        for mut child in children {
+            child();
+        }
     });
-    let src = &build.src;
-    let walker = WalkBuilder::new(src).types(matcher).overrides(ignore_fmt).build_parallel();
+
     walker.run(|| {
+        let tx = tx.clone();
         Box::new(move |entry| {
             let entry = t!(entry);
             if entry.file_type().map_or(false, |t| t.is_file()) {
-                rustfmt(src, &rustfmt_path, &entry.path(), check);
+                t!(tx.send(entry.into_path()));
             }
             ignore::WalkState::Continue
         })
     });
+
+    drop(tx);
+
+    thread.join().unwrap();
 }