Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 27ee8fc

Browse files
committedMar 18, 2025
Auto merge of rust-lang#138591 - Kobzol:git-ci, r=<try>
Refactor git change detection in bootstrap While working on rust-lang#138395, I finally found the courage to delve into the insides of git path change detection in bootstrap, which is used (amongst other things) to detect if we should rebuilt od download `[llvm|rustc|gcc]`. I found it a bit hard to understand, and given that this code was historically quite fragile, I thought that it would be better to rebuild it from scratch. The previous approach had a bunch of limitations: - It separated the computation of "are there local changes?" and "what upstream SHA should we use?" even though these two things are intertwined. - It used hacks to work around what happens on CI. - It had special cases for CI scattered throughout the codebase, rather than centralized in one place. - It wasn't documented enough and didn't have tests for the git behavior. The current approach should hopefully resolve all of that. I implemented a single entrypoint called `check_path_modifications` (naming bikeshed pending, half of the time I spend on this PR was thinking about names, as it's quite tricky here..) that explicitly receives a mode of operation (in CI or outside CI), and accordingly figures out that upstream SHA that we should use for downloading artifacts and it also figures out if there are any local changes. Users of this function can then use this unified output to implement `download-ci-X` and other functionality. I also added a bunch of integration tests that literally spawn a git repository on disk and then check that the function can deal with various situations (PR CI, auto/try CI, local builds). The tests are super fast and run in parallel, as they are currently in `build_helper` and not in `bootstrap`. After I built this inner layer, I used it for downloading GCC, LLVM and rustc. The latter two (and especially rustc) were using the `last_modified_commit` function before, but in all cases but one this function was actually only used to check if there are any local changes, which was IMO confusing. The LLVM handling would deserve a bit of refactoring, but that's a larger change that can be done as a follow-up. In the future we could cache the results of `check_path_modifications` to reduce the number of git invocations, but I don't think that it should be excessive even now. I hope that the implementation is now clear and easy to understand, so that in combination with the tests we can have more confidence that it does what we want. I tried to include a lot of documentation in the code, so I won't be repeating the actual implementation details here, if there are any questions, I'll add the answers to the documentation too :) The new approach explicitly supports three scenarios: - Running on PR CI, where we have one upstream bors parent commit and one PR merge commit made by GitHub. - Running on try/auto CI, where we have one upstream bors parent commit and one PR merge commit made by bors. - Running locally, where we assume that we have at least one upstream bors parent commit in our git history. I removed the handling of upstreams on CI, as I think that it shouldn't be needed and I considered it to be a hack. However, it's possible that there are other use-cases that I haven't considered, so I want to ask around if people have other situations than the three use-cases described above. If there are other such use-cases, I would like to include them in the new centralized implementation and add them to the git test suite, rather than going back to the old ways :) In particular, the code before relied on `git merge-base`, but I don't see why we can't just lookup the most recent bors commit and assume that is a merge commit that is also upstream? I might be running into Chesterton's Fence here :) CC `@pietroalbini` To make sure that this won't break downstream users of Rust's CI. Best reviewed commit by commit. Companion PRs: - For testing beta: rust-lang#138597 r? `@onur-ozkan` try-job: x86_64-gnu-aux
2 parents 259fdb5 + afe1f99 commit 27ee8fc

File tree

18 files changed

+562
-290
lines changed

18 files changed

+562
-290
lines changed
 

‎.github/workflows/ci.yml

-3
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,6 @@ jobs:
116116
# which then uses log commands to actually set them.
117117
EXTRA_VARIABLES: ${{ toJson(matrix.env) }}
118118

119-
- name: setup upstream remote
120-
run: src/ci/scripts/setup-upstream-remote.sh
121-
122119
- name: ensure the channel matches the target branch
123120
run: src/ci/scripts/verify-channel.sh
124121

‎Cargo.lock

+27-8
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ version = "0.1.0"
304304
dependencies = [
305305
"serde",
306306
"serde_derive",
307+
"tempfile",
307308
]
308309

309310
[[package]]
@@ -2076,6 +2077,12 @@ version = "0.4.15"
20762077
source = "registry+https://github.com/rust-lang/crates.io-index"
20772078
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
20782079

2080+
[[package]]
2081+
name = "linux-raw-sys"
2082+
version = "0.9.3"
2083+
source = "registry+https://github.com/rust-lang/crates.io-index"
2084+
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
2085+
20792086
[[package]]
20802087
name = "litemap"
20812088
version = "0.7.4"
@@ -4711,7 +4718,20 @@ dependencies = [
47114718
"bitflags",
47124719
"errno",
47134720
"libc",
4714-
"linux-raw-sys",
4721+
"linux-raw-sys 0.4.15",
4722+
"windows-sys 0.59.0",
4723+
]
4724+
4725+
[[package]]
4726+
name = "rustix"
4727+
version = "1.0.2"
4728+
source = "registry+https://github.com/rust-lang/crates.io-index"
4729+
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
4730+
dependencies = [
4731+
"bitflags",
4732+
"errno",
4733+
"libc",
4734+
"linux-raw-sys 0.9.3",
47154735
"windows-sys 0.59.0",
47164736
]
47174737

@@ -5118,15 +5138,14 @@ dependencies = [
51185138

51195139
[[package]]
51205140
name = "tempfile"
5121-
version = "3.15.0"
5141+
version = "3.19.0"
51225142
source = "registry+https://github.com/rust-lang/crates.io-index"
5123-
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
5143+
checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
51245144
dependencies = [
5125-
"cfg-if",
51265145
"fastrand",
5127-
"getrandom 0.2.15",
5146+
"getrandom 0.3.1",
51285147
"once_cell",
5129-
"rustix",
5148+
"rustix 1.0.2",
51305149
"windows-sys 0.59.0",
51315150
]
51325151

@@ -6454,8 +6473,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
64546473
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
64556474
dependencies = [
64566475
"libc",
6457-
"linux-raw-sys",
6458-
"rustix",
6476+
"linux-raw-sys 0.4.15",
6477+
"rustix 0.38.43",
64596478
]
64606479

64616480
[[package]]

‎src/bootstrap/Cargo.lock

+5-5
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ dependencies = [
225225

226226
[[package]]
227227
name = "errno"
228-
version = "0.3.9"
228+
version = "0.3.10"
229229
source = "registry+https://github.com/rust-lang/crates.io-index"
230-
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
230+
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
231231
dependencies = [
232232
"libc",
233-
"windows-sys 0.52.0",
233+
"windows-sys 0.59.0",
234234
]
235235

236236
[[package]]
@@ -334,9 +334,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
334334

335335
[[package]]
336336
name = "libc"
337-
version = "0.2.167"
337+
version = "0.2.171"
338338
source = "registry+https://github.com/rust-lang/crates.io-index"
339-
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
339+
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
340340

341341
[[package]]
342342
name = "libredox"

‎src/bootstrap/src/core/build_steps/compile.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,10 @@ impl Step for Std {
111111
// the `rust.download-rustc=true` option.
112112
let force_recompile = builder.rust_info().is_managed_git_subrepository()
113113
&& builder.download_rustc()
114-
&& builder.config.last_modified_commit(&["library"], "download-rustc", true).is_none();
114+
&& builder.config.has_changes_from_upstream(&["library"]);
115115

116116
trace!("is managed git repo: {}", builder.rust_info().is_managed_git_subrepository());
117117
trace!("download_rustc: {}", builder.download_rustc());
118-
trace!(
119-
"last modified commit: {:?}",
120-
builder.config.last_modified_commit(&["library"], "download-rustc", true)
121-
);
122118
trace!(force_recompile);
123119

124120
run.builder.ensure(Std {

‎src/bootstrap/src/core/build_steps/format.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, Str
9494
return Ok(None);
9595
}
9696

97-
get_git_modified_files(&build.config.git_config(), Some(&build.config.src), &["rs"])
97+
get_git_modified_files(&build.config.git_config(), Some(&build.config.src), &["rs"]).map(Some)
9898
}
9999

100100
#[derive(serde_derive::Deserialize)]

‎src/bootstrap/src/core/build_steps/gcc.rs

+43-26
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ pub enum GccBuildStatus {
9898
/// Returns a path to the libgccjit.so file.
9999
#[cfg(not(test))]
100100
fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option<PathBuf> {
101+
use build_helper::git::PathFreshness;
102+
101103
// Try to download GCC from CI if configured and available
102104
if !matches!(builder.config.gcc_ci_mode, crate::core::config::GccCiMode::DownloadFromCi) {
103105
return None;
@@ -106,18 +108,30 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option<Pa
106108
eprintln!("GCC CI download is only available for the `x86_64-unknown-linux-gnu` target");
107109
return None;
108110
}
109-
let sha =
110-
detect_gcc_sha(&builder.config, builder.config.rust_info.is_managed_git_subrepository());
111-
let root = ci_gcc_root(&builder.config);
112-
let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&sha);
113-
if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
114-
builder.config.download_ci_gcc(&sha, &root);
115-
t!(gcc_stamp.write());
111+
let source = detect_gcc_freshness(
112+
&builder.config,
113+
builder.config.rust_info.is_managed_git_subrepository(),
114+
);
115+
match source {
116+
PathFreshness::LastModifiedUpstream { upstream } => {
117+
// Download from upstream CI
118+
let root = ci_gcc_root(&builder.config);
119+
let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream);
120+
if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() {
121+
builder.config.download_ci_gcc(&upstream, &root);
122+
t!(gcc_stamp.write());
123+
}
124+
125+
let libgccjit = root.join("lib").join("libgccjit.so");
126+
create_lib_alias(builder, &libgccjit);
127+
Some(libgccjit)
128+
}
129+
PathFreshness::HasLocalModifications { .. } => {
130+
// We have local modifications, rebuild GCC.
131+
eprintln!("Found local GCC modifications, GCC will *not* be downloaded");
132+
None
133+
}
116134
}
117-
118-
let libgccjit = root.join("lib").join("libgccjit.so");
119-
create_lib_alias(builder, &libgccjit);
120-
Some(libgccjit)
121135
}
122136

123137
#[cfg(test)]
@@ -266,31 +280,34 @@ fn ci_gcc_root(config: &crate::Config) -> PathBuf {
266280
config.out.join(config.build).join("ci-gcc")
267281
}
268282

269-
/// This retrieves the GCC sha we *want* to use, according to git history.
283+
/// Detect whether GCC sources have been modified locally or not.
270284
#[cfg(not(test))]
271-
fn detect_gcc_sha(config: &crate::Config, is_git: bool) -> String {
272-
use build_helper::git::get_closest_merge_commit;
273-
274-
let gcc_sha = if is_git {
275-
get_closest_merge_commit(
276-
Some(&config.src),
277-
&config.git_config(),
278-
&[config.src.join("src/gcc"), config.src.join("src/bootstrap/download-ci-gcc-stamp")],
285+
fn detect_gcc_freshness(config: &crate::Config, is_git: bool) -> build_helper::git::PathFreshness {
286+
use build_helper::git::{PathFreshness, check_path_modifications};
287+
288+
let freshness = if is_git {
289+
Some(
290+
check_path_modifications(
291+
Some(&config.src),
292+
&config.git_config(),
293+
&["src/gcc", "src/bootstrap/download-ci-gcc-stamp"],
294+
CiEnv::current(),
295+
)
296+
.unwrap(),
279297
)
280-
.unwrap()
281298
} else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
282-
info.sha.trim().to_owned()
299+
Some(PathFreshness::LastModifiedUpstream { upstream: info.sha.trim().to_owned() })
283300
} else {
284-
"".to_owned()
301+
None
285302
};
286303

287-
if gcc_sha.is_empty() {
304+
let Some(freshness) = freshness else {
288305
eprintln!("error: could not find commit hash for downloading GCC");
289306
eprintln!("HELP: maybe your repository history is too shallow?");
290307
eprintln!("HELP: consider disabling `download-ci-gcc`");
291308
eprintln!("HELP: or fetch enough history to include one upstream commit");
292309
panic!();
293-
}
310+
};
294311

295-
gcc_sha
312+
freshness
296313
}

‎src/bootstrap/src/core/build_steps/llvm.rs

+23-25
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::sync::OnceLock;
1515
use std::{env, fs};
1616

1717
use build_helper::ci::CiEnv;
18-
use build_helper::git::get_closest_merge_commit;
18+
use build_helper::git::{PathFreshness, check_path_modifications};
1919
#[cfg(feature = "tracing")]
2020
use tracing::instrument;
2121

@@ -174,35 +174,38 @@ pub fn prebuilt_llvm_config(
174174
LlvmBuildStatus::ShouldBuild(Meta { stamp, res, out_dir, root: root.into() })
175175
}
176176

177-
/// This retrieves the LLVM sha we *want* to use, according to git history.
178-
pub(crate) fn detect_llvm_sha(config: &Config, is_git: bool) -> String {
179-
let llvm_sha = if is_git {
180-
get_closest_merge_commit(
181-
Some(&config.src),
182-
&config.git_config(),
183-
&[
184-
config.src.join("src/llvm-project"),
185-
config.src.join("src/bootstrap/download-ci-llvm-stamp"),
186-
// the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
187-
config.src.join("src/version"),
188-
],
177+
/// Detect whether LLVM sources have been modified locally or not.
178+
pub(crate) fn detect_llvm_freshness(config: &Config, is_git: bool) -> PathFreshness {
179+
let freshness = if is_git {
180+
Some(
181+
check_path_modifications(
182+
Some(&config.src),
183+
&config.git_config(),
184+
&[
185+
"src/llvm-project",
186+
"src/bootstrap/download-ci-llvm-stamp",
187+
// the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
188+
"src/version",
189+
],
190+
CiEnv::current(),
191+
)
192+
.unwrap(),
189193
)
190-
.unwrap()
191194
} else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
192-
info.sha.trim().to_owned()
195+
Some(PathFreshness::LastModifiedUpstream { upstream: info.sha.trim().to_owned() })
193196
} else {
194-
"".to_owned()
197+
None
195198
};
196199

197-
if llvm_sha.is_empty() {
200+
let Some(freshness) = freshness else {
198201
eprintln!("error: could not find commit hash for downloading LLVM");
199202
eprintln!("HELP: maybe your repository history is too shallow?");
200203
eprintln!("HELP: consider disabling `download-ci-llvm`");
201204
eprintln!("HELP: or fetch enough history to include one upstream commit");
202205
panic!();
203-
}
206+
};
204207

205-
llvm_sha
208+
freshness
206209
}
207210

208211
/// Returns whether the CI-found LLVM is currently usable.
@@ -282,12 +285,7 @@ pub(crate) fn is_ci_llvm_modified(config: &Config) -> bool {
282285
return false;
283286
}
284287

285-
let llvm_sha = detect_llvm_sha(config, true);
286-
let head_sha = crate::output(
287-
helpers::git(Some(&config.src)).arg("rev-parse").arg("HEAD").as_command_mut(),
288-
);
289-
let head_sha = head_sha.trim();
290-
llvm_sha == head_sha
288+
matches!(detect_llvm_freshness(config, true), PathFreshness::HasLocalModifications { .. })
291289
}
292290

293291
#[derive(Debug, Clone, Hash, PartialEq, Eq)]

‎src/bootstrap/src/core/build_steps/tool.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -687,8 +687,7 @@ impl Step for Rustdoc {
687687
let files_to_track = &["src/librustdoc", "src/tools/rustdoc"];
688688

689689
// Check if unchanged
690-
if builder.config.last_modified_commit(files_to_track, "download-rustc", true).is_some()
691-
{
690+
if !builder.config.has_changes_from_upstream(files_to_track) {
692691
let precompiled_rustdoc = builder
693692
.config
694693
.ci_rustc_dir()

‎src/bootstrap/src/core/builder/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ fn ci_rustc_if_unchanged_logic() {
268268
paths.push("library");
269269
}
270270

271-
let has_changes = config.last_modified_commit(&paths, "download-rustc", true).is_none();
271+
let has_changes = config.has_changes_from_upstream(&paths);
272272

273273
assert!(
274274
!has_changes,
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.