X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2Fbootstrap%2Fformat.rs;h=bfc57a85cdb42bdd106098ae9e4fc3bc707904de;hb=f25598a0c1bc63ebebc8fddfc72a3668f66863df;hp=5e7264fe765a950ac9980b7ef55b5e05023312d6;hpb=3c25461fe7bfa681160dfae3ca0617fbdaa450c3;p=rustc.git diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 5e7264fe76..bfc57a85cd 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>, 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); + } }