From e751cb8bd222a4956b31742b282ffc9e3aa23947 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Mon, 23 Mar 2026 10:03:50 +0100 Subject: [PATCH 1/2] bootstrap: Print why `if-unchanged` isn't downloading rustc Example output: ``` $ x b compiler Building bootstrap Finished `dev` profile [unoptimized] target(s) in 0.02s NOTE: detected 3 modifications that could affect a build of rustc - src/bootstrap/src/core/config/config.rs - src/bootstrap/src/core/download.rs - src/build_helper/src/git.rs skipping rustc download due to `download-rustc = 'if-unchanged'` Building stage1 compiler artifacts (stage0 -> stage1, aarch64-apple-darwin) Finished `release` profile [optimized] target(s) in 0.72s Creating a sysroot for stage1 compiler (use `rustup toolchain link 'name' build/host/stage1`) Build completed successfully in 0:00:05 ``` --- src/bootstrap/src/core/config/config.rs | 24 +++++++++++++++---- src/bootstrap/src/core/config/tests.rs | 30 +++++++++++------------- src/bootstrap/src/core/download.rs | 2 +- src/build_helper/src/git.rs | 31 ++++++++++++++++++------- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 2c3b5251e12f6..2b52e3b672358 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -2237,11 +2237,7 @@ pub fn download_ci_rustc_commit<'a>( }); match freshness { PathFreshness::LastModifiedUpstream { upstream } => upstream, - PathFreshness::HasLocalModifications { upstream } => { - if if_unchanged { - return None; - } - + PathFreshness::HasLocalModifications { upstream, modifications } => { if dwn_ctx.is_running_on_ci() { eprintln!("CI rustc commit matches with HEAD and we are in CI."); eprintln!( @@ -2250,6 +2246,24 @@ pub fn download_ci_rustc_commit<'a>( return None; } + eprintln!( + "NOTE: detected {} modifications that could affect a build of rustc", + modifications.len() + ); + for file in modifications.iter().take(10) { + eprintln!("- {}", file.display()); + } + if modifications.len() > 10 { + eprintln!("- ... and {} more", modifications.len() - 10); + } + + if if_unchanged { + eprintln!("skipping rustc download due to `download-rustc = 'if-unchanged'`"); + return None; + } else { + eprintln!("downloading unconditionally due to `download-rustc = true`"); + } + upstream } PathFreshness::MissingUpstream => { diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index 277ede8d77456..c281aa94f64e6 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -32,6 +32,13 @@ fn get_toml(file: &Path) -> Result { toml::from_str(&contents).and_then(|table: toml::Value| TomlConfig::deserialize(table)) } +fn modified(upstream: impl Into, changes: &[&str]) -> PathFreshness { + PathFreshness::HasLocalModifications { + upstream: upstream.into(), + modifications: changes.iter().copied().map(PathBuf::from).collect(), + } +} + #[test] fn download_ci_llvm() { let config = TestCtx::new().config("check").create_config(); @@ -692,7 +699,7 @@ fn test_pr_ci_changed_in_pr() { let sha = ctx.create_upstream_merge(&["a"]); ctx.create_nonupstream_merge(&["b"]); let src = ctx.check_modifications(&["b"], CiEnv::GitHubActions); - assert_eq!(src, PathFreshness::HasLocalModifications { upstream: sha }); + assert_eq!(src, modified(sha, &["b"])); }); } @@ -712,7 +719,7 @@ fn test_auto_ci_changed_in_pr() { let sha = ctx.create_upstream_merge(&["a"]); ctx.create_upstream_merge(&["b", "c"]); let src = ctx.check_modifications(&["c", "d"], CiEnv::GitHubActions); - assert_eq!(src, PathFreshness::HasLocalModifications { upstream: sha }); + assert_eq!(src, modified(sha, &["c"])); }); } @@ -723,10 +730,7 @@ fn test_local_uncommitted_modifications() { ctx.create_branch("feature"); ctx.modify("a"); - assert_eq!( - ctx.check_modifications(&["a", "d"], CiEnv::None), - PathFreshness::HasLocalModifications { upstream: sha } - ); + assert_eq!(ctx.check_modifications(&["a", "d"], CiEnv::None), modified(sha, &["a"]),); }); } @@ -741,10 +745,7 @@ fn test_local_committed_modifications() { ctx.modify("a"); ctx.commit(); - assert_eq!( - ctx.check_modifications(&["a", "d"], CiEnv::None), - PathFreshness::HasLocalModifications { upstream: sha } - ); + assert_eq!(ctx.check_modifications(&["a", "d"], CiEnv::None), modified(sha, &["a"]),); }); } @@ -757,10 +758,7 @@ fn test_local_committed_modifications_subdirectory() { ctx.modify("a/b/d"); ctx.commit(); - assert_eq!( - ctx.check_modifications(&["a/b"], CiEnv::None), - PathFreshness::HasLocalModifications { upstream: sha } - ); + assert_eq!(ctx.check_modifications(&["a/b"], CiEnv::None), modified(sha, &["a/b/d"]),); }); } @@ -836,11 +834,11 @@ fn test_local_changes_negative_path() { ); assert_eq!( ctx.check_modifications(&[":!c"], CiEnv::None), - PathFreshness::HasLocalModifications { upstream: upstream.clone() } + modified(&upstream, &["b", "d"]), ); assert_eq!( ctx.check_modifications(&[":!d", ":!x"], CiEnv::None), - PathFreshness::HasLocalModifications { upstream } + modified(&upstream, &["b"]), ); }); } diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 389956f145994..b8e00c596f282 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -266,7 +266,7 @@ impl Config { }); let llvm_sha = match llvm_freshness { PathFreshness::LastModifiedUpstream { upstream } => upstream, - PathFreshness::HasLocalModifications { upstream } => upstream, + PathFreshness::HasLocalModifications { upstream, modifications: _ } => upstream, PathFreshness::MissingUpstream => { eprintln!("error: could not find commit hash for downloading LLVM"); eprintln!("HELP: maybe your repository history is too shallow?"); diff --git a/src/build_helper/src/git.rs b/src/build_helper/src/git.rs index 330fb465de42c..87a52eee49b85 100644 --- a/src/build_helper/src/git.rs +++ b/src/build_helper/src/git.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use crate::ci::CiEnv; @@ -38,7 +38,7 @@ pub enum PathFreshness { /// "Local" essentially means "not-upstream" here. /// `upstream` is the latest upstream merge commit that made modifications to the /// set of paths. - HasLocalModifications { upstream: String }, + HasLocalModifications { upstream: String, modifications: Vec }, /// No upstream commit was found. /// This should not happen in most reasonable circumstances, but one never knows. MissingUpstream, @@ -134,21 +134,34 @@ pub fn check_path_modifications( // However, that should be equivalent to checking if something has changed // from the latest upstream commit *that modified `target_paths`*, and // with this approach we do not need to invoke git an additional time. - if has_changed_since(git_dir, &upstream_sha, target_paths) { - Ok(PathFreshness::HasLocalModifications { upstream: upstream_sha }) + let modifications = changes_since(git_dir, &upstream_sha, target_paths)?; + if !modifications.is_empty() { + Ok(PathFreshness::HasLocalModifications { upstream: upstream_sha, modifications }) } else { Ok(PathFreshness::LastModifiedUpstream { upstream: upstream_sha }) } } /// Returns true if any of the passed `paths` have changed since the `base` commit. -pub fn has_changed_since(git_dir: &Path, base: &str, paths: &[&str]) -> bool { +pub fn changes_since(git_dir: &Path, base: &str, paths: &[&str]) -> Result, String> { + use std::io::BufRead; + run_git_diff_index(Some(git_dir), |cmd| { - cmd.args(["--quiet", base, "--"]).args(paths); + cmd.args([base, "--name-only", "--"]).args(paths); + + let output = cmd.stderr(Stdio::inherit()).output().expect("cannot run git diff-index"); + if !output.status.success() { + return Err(format!("failed to run: {cmd:?}: {:?}", output.status)); + } - // Exit code 0 => no changes - // Exit code 1 => some changes were detected - !cmd.status().expect("cannot run git diff-index").success() + output + .stdout + .lines() + .map(|res| match res { + Ok(line) => Ok(PathBuf::from(line)), + Err(e) => Err(format!("invalid UTF-8 in diff-index: {e:?}")), + }) + .collect() }) } From 9b5e085d537030bbb9dce7344cc55cb83557ac86 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Mon, 23 Mar 2026 10:05:21 +0100 Subject: [PATCH 2/2] Don't consider `bootstrap/defaults` to invalidate the rustc cache Anything that can change there can also be changed in bootstrap.toml. We have a separate check to make sure that the local config is compatable with CI's config. --- src/bootstrap/src/core/config/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 2b52e3b672358..36e6432ee82ad 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -78,6 +78,7 @@ pub const RUSTC_IF_UNCHANGED_ALLOWED_PATHS: &[&str] = &[ ":!src/rustdoc-json-types", ":!tests", ":!triagebot.toml", + ":!src/bootstrap/defaults", ]; /// Global configuration for the entire build and/or bootstrap.