summaryrefslogtreecommitdiffstats
path: root/src/bootstrap/format.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bootstrap/format.rs')
-rw-r--r--src/bootstrap/format.rs106
1 files changed, 101 insertions, 5 deletions
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index 5e7264fe7..bfc57a85c 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -1,14 +1,15 @@
//! Runs rustfmt on the repository.
use crate::builder::Builder;
-use crate::util::{output, t};
+use crate::util::{output, output_result, program_out_of_date, t};
+use build_helper::git::updated_master_branch;
use ignore::WalkBuilder;
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, paths: &[PathBuf], check: bool) -> impl FnMut() {
+fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut(bool) -> bool {
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
@@ -23,7 +24,13 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
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
- move || {
+ move |block: bool| -> bool {
+ if !block {
+ match cmd.try_wait() {
+ Ok(Some(_)) => {}
+ _ => return false,
+ }
+ }
let status = cmd.wait().unwrap();
if !status.success() {
eprintln!(
@@ -34,7 +41,62 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
);
crate::detail_exit(1);
}
+ true
+ }
+}
+
+fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, PathBuf)> {
+ let stamp_file = build.out.join("rustfmt.stamp");
+
+ let mut cmd = Command::new(match build.initial_rustfmt() {
+ Some(p) => p,
+ None => return None,
+ });
+ cmd.arg("--version");
+ let output = match cmd.output() {
+ Ok(status) => status,
+ Err(_) => return None,
+ };
+ if !output.status.success() {
+ return None;
+ }
+ Some((String::from_utf8(output.stdout).unwrap(), stamp_file))
+}
+
+/// Return whether the format cache can be reused.
+fn verify_rustfmt_version(build: &Builder<'_>) -> bool {
+ let Some((version, stamp_file)) = get_rustfmt_version(build) else {return false;};
+ !program_out_of_date(&stamp_file, &version)
+}
+
+/// Updates the last rustfmt version used
+fn update_rustfmt_version(build: &Builder<'_>) {
+ let Some((version, stamp_file)) = get_rustfmt_version(build) else {return;};
+ t!(std::fs::write(stamp_file, version))
+}
+
+/// Returns the Rust files modified between the `merge-base` of HEAD and
+/// rust-lang/master and what is now on the disk.
+///
+/// Returns `None` if all files should be formatted.
+fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, String> {
+ let Ok(updated_master) = updated_master_branch(Some(&build.config.src)) else { return Ok(None); };
+
+ if !verify_rustfmt_version(build) {
+ return Ok(None);
}
+
+ let merge_base =
+ output_result(build.config.git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
+ Ok(Some(
+ output_result(
+ build.config.git().arg("diff-index").arg("--name-only").arg(merge_base.trim()),
+ )?
+ .lines()
+ .map(|s| s.trim().to_owned())
+ .filter(|f| Path::new(f).extension().map_or(false, |ext| ext == "rs"))
+ .collect(),
+ ))
}
#[derive(serde::Deserialize)]
@@ -71,6 +133,9 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
Ok(status) => status.success(),
Err(_) => false,
};
+
+ let mut paths = paths.to_vec();
+
if git_available {
let in_working_tree = match build
.config
@@ -103,12 +168,32 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
// preventing the latter from being formatted.
ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
}
+ if !check && paths.is_empty() {
+ match get_modified_rs_files(build) {
+ Ok(Some(files)) => {
+ for file in files {
+ println!("formatting modified file {file}");
+ ignore_fmt.add(&format!("/{file}")).expect(&file);
+ }
+ }
+ Ok(None) => {}
+ Err(err) => {
+ println!(
+ "WARN: Something went wrong when running git commands:\n{err}\n\
+ Falling back to formatting all files."
+ );
+ // Something went wrong when getting the version. Just format all the files.
+ paths.push(".".into());
+ }
+ }
+ }
} else {
println!("Not in git tree. Skipping git-aware format checks");
}
} else {
println!("Could not find usable git. Skipping git-aware format checks");
}
+
let ignore_fmt = ignore_fmt.build().unwrap();
let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| {
@@ -146,15 +231,23 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check);
children.push_back(child);
+ // poll completion before waiting
+ for i in (0..children.len()).rev() {
+ if children[i](false) {
+ children.swap_remove_back(i);
+ break;
+ }
+ }
+
if children.len() >= max_processes {
// await oldest child
- children.pop_front().unwrap()();
+ children.pop_front().unwrap()(true);
}
}
// await remaining children
for mut child in children {
- child();
+ child(true);
}
});
@@ -172,4 +265,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
drop(tx);
thread.join().unwrap();
+ if !check {
+ update_rustfmt_version(build);
+ }
}