Skip to content

Commit a08a2ef

Browse files
authored
Rollup merge of #129231 - onur-ozkan:improve-submodule-updates, r=Mark-Simulacrum
improve submodule updates During config parsing, some bootstrap logic (e.g., `download-ci-llvm`) checks certain sources (for `download-ci-llvm`, it's `src/llvm-project`) and acts based on their state. This means that if path is a git submodule, bootstrap needs to update it before checking its state. Otherwise it may make incorrect assumptions by relying on outdated sources. To enable submodule updates during config parsing, we need to move the `update_submodule` function from the `Build` to `Config`, so we can access to it during the parsing process. Closes #122787
2 parents 9fd8a2c + d2d8fa4 commit a08a2ef

File tree

4 files changed

+139
-129
lines changed

4 files changed

+139
-129
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> L
110110

111111
// Initialize the llvm submodule if not initialized already.
112112
// If submodules are disabled, this does nothing.
113-
builder.update_submodule("src/llvm-project");
113+
builder.config.update_submodule("src/llvm-project");
114114

115115
let root = "src/llvm-project/llvm";
116116
let out_dir = builder.llvm_out(target);

src/bootstrap/src/core/config/config.rs

+134-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::sync::OnceLock;
1414
use std::{cmp, env, fs};
1515

1616
use build_helper::exit;
17-
use build_helper::git::GitConfig;
17+
use build_helper::git::{output_result, GitConfig};
1818
use serde::{Deserialize, Deserializer};
1919
use serde_derive::Deserialize;
2020

@@ -2509,6 +2509,123 @@ impl Config {
25092509
}
25102510
}
25112511

2512+
/// Given a path to the directory of a submodule, update it.
2513+
///
2514+
/// `relative_path` should be relative to the root of the git repository, not an absolute path.
2515+
///
2516+
/// This *does not* update the submodule if `config.toml` explicitly says
2517+
/// not to, or if we're not in a git repository (like a plain source
2518+
/// tarball). Typically [`crate::Build::require_submodule`] should be
2519+
/// used instead to provide a nice error to the user if the submodule is
2520+
/// missing.
2521+
pub(crate) fn update_submodule(&self, relative_path: &str) {
2522+
if !self.submodules() {
2523+
return;
2524+
}
2525+
2526+
let absolute_path = self.src.join(relative_path);
2527+
2528+
// NOTE: The check for the empty directory is here because when running x.py the first time,
2529+
// the submodule won't be checked out. Check it out now so we can build it.
2530+
if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository()
2531+
&& !helpers::dir_is_empty(&absolute_path)
2532+
{
2533+
return;
2534+
}
2535+
2536+
// Submodule updating actually happens during in the dry run mode. We need to make sure that
2537+
// all the git commands below are actually executed, because some follow-up code
2538+
// in bootstrap might depend on the submodules being checked out. Furthermore, not all
2539+
// the command executions below work with an empty output (produced during dry run).
2540+
// Therefore, all commands below are marked with `run_always()`, so that they also run in
2541+
// dry run mode.
2542+
let submodule_git = || {
2543+
let mut cmd = helpers::git(Some(&absolute_path));
2544+
cmd.run_always();
2545+
cmd
2546+
};
2547+
2548+
// Determine commit checked out in submodule.
2549+
let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut());
2550+
let checked_out_hash = checked_out_hash.trim_end();
2551+
// Determine commit that the submodule *should* have.
2552+
let recorded = output(
2553+
helpers::git(Some(&self.src))
2554+
.run_always()
2555+
.args(["ls-tree", "HEAD"])
2556+
.arg(relative_path)
2557+
.as_command_mut(),
2558+
);
2559+
2560+
let actual_hash = recorded
2561+
.split_whitespace()
2562+
.nth(2)
2563+
.unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
2564+
2565+
if actual_hash == checked_out_hash {
2566+
// already checked out
2567+
return;
2568+
}
2569+
2570+
println!("Updating submodule {relative_path}");
2571+
self.check_run(
2572+
helpers::git(Some(&self.src))
2573+
.run_always()
2574+
.args(["submodule", "-q", "sync"])
2575+
.arg(relative_path),
2576+
);
2577+
2578+
// Try passing `--progress` to start, then run git again without if that fails.
2579+
let update = |progress: bool| {
2580+
// Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
2581+
// even though that has no relation to the upstream for the submodule.
2582+
let current_branch = output_result(
2583+
helpers::git(Some(&self.src))
2584+
.allow_failure()
2585+
.run_always()
2586+
.args(["symbolic-ref", "--short", "HEAD"])
2587+
.as_command_mut(),
2588+
)
2589+
.map(|b| b.trim().to_owned());
2590+
2591+
let mut git = helpers::git(Some(&self.src)).allow_failure();
2592+
git.run_always();
2593+
if let Ok(branch) = current_branch {
2594+
// If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
2595+
// This syntax isn't accepted by `branch.{branch}`. Strip it.
2596+
let branch = branch.strip_prefix("heads/").unwrap_or(&branch);
2597+
git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
2598+
}
2599+
git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
2600+
if progress {
2601+
git.arg("--progress");
2602+
}
2603+
git.arg(relative_path);
2604+
git
2605+
};
2606+
if !self.check_run(&mut update(true)) {
2607+
self.check_run(&mut update(false));
2608+
}
2609+
2610+
// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
2611+
// diff-index reports the modifications through the exit status
2612+
let has_local_modifications = !self.check_run(submodule_git().allow_failure().args([
2613+
"diff-index",
2614+
"--quiet",
2615+
"HEAD",
2616+
]));
2617+
if has_local_modifications {
2618+
self.check_run(submodule_git().args(["stash", "push"]));
2619+
}
2620+
2621+
self.check_run(submodule_git().args(["reset", "-q", "--hard"]));
2622+
self.check_run(submodule_git().args(["clean", "-qdfx"]));
2623+
2624+
if has_local_modifications {
2625+
self.check_run(submodule_git().args(["stash", "pop"]));
2626+
}
2627+
}
2628+
25122629
#[cfg(feature = "bootstrap-self-test")]
25132630
pub fn check_stage0_version(&self, _program_path: &Path, _component_name: &'static str) {}
25142631

@@ -2613,19 +2730,23 @@ impl Config {
26132730
asserts: bool,
26142731
) -> bool {
26152732
let if_unchanged = || {
2616-
// Git is needed to track modifications here, but tarball source is not available.
2617-
// If not modified here or built through tarball source, we maintain consistency
2618-
// with '"if available"'.
2619-
if !self.rust_info.is_from_tarball()
2620-
&& self
2621-
.last_modified_commit(&["src/llvm-project"], "download-ci-llvm", true)
2622-
.is_none()
2623-
{
2624-
// there are some untracked changes in the given paths.
2625-
false
2626-
} else {
2627-
llvm::is_ci_llvm_available(self, asserts)
2733+
if self.rust_info.is_from_tarball() {
2734+
// Git is needed for running "if-unchanged" logic.
2735+
println!(
2736+
"WARNING: 'if-unchanged' has no effect on tarball sources; ignoring `download-ci-llvm`."
2737+
);
2738+
return false;
26282739
}
2740+
2741+
self.update_submodule("src/llvm-project");
2742+
2743+
// Check for untracked changes in `src/llvm-project`.
2744+
let has_changes = self
2745+
.last_modified_commit(&["src/llvm-project"], "download-ci-llvm", true)
2746+
.is_none();
2747+
2748+
// Return false if there are untracked changes, otherwise check if CI LLVM is available.
2749+
if has_changes { false } else { llvm::is_ci_llvm_available(self, asserts) }
26292750
};
26302751

26312752
match download_ci_llvm {

src/bootstrap/src/core/download.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl Config {
5656
/// Returns false if do not execute at all, otherwise returns its
5757
/// `status.success()`.
5858
pub(crate) fn check_run(&self, cmd: &mut BootstrapCommand) -> bool {
59-
if self.dry_run() {
59+
if self.dry_run() && !cmd.run_always {
6060
return true;
6161
}
6262
self.verbose(|| println!("running: {cmd:?}"));

src/bootstrap/src/lib.rs

+3-114
Original file line numberDiff line numberDiff line change
@@ -473,117 +473,6 @@ impl Build {
473473
build
474474
}
475475

476-
/// Given a path to the directory of a submodule, update it.
477-
///
478-
/// `relative_path` should be relative to the root of the git repository, not an absolute path.
479-
///
480-
/// This *does not* update the submodule if `config.toml` explicitly says
481-
/// not to, or if we're not in a git repository (like a plain source
482-
/// tarball). Typically [`Build::require_submodule`] should be
483-
/// used instead to provide a nice error to the user if the submodule is
484-
/// missing.
485-
fn update_submodule(&self, relative_path: &str) {
486-
if !self.config.submodules() {
487-
return;
488-
}
489-
490-
let absolute_path = self.config.src.join(relative_path);
491-
492-
// NOTE: The check for the empty directory is here because when running x.py the first time,
493-
// the submodule won't be checked out. Check it out now so we can build it.
494-
if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository()
495-
&& !dir_is_empty(&absolute_path)
496-
{
497-
return;
498-
}
499-
500-
// Submodule updating actually happens during in the dry run mode. We need to make sure that
501-
// all the git commands below are actually executed, because some follow-up code
502-
// in bootstrap might depend on the submodules being checked out. Furthermore, not all
503-
// the command executions below work with an empty output (produced during dry run).
504-
// Therefore, all commands below are marked with `run_always()`, so that they also run in
505-
// dry run mode.
506-
let submodule_git = || {
507-
let mut cmd = helpers::git(Some(&absolute_path));
508-
cmd.run_always();
509-
cmd
510-
};
511-
512-
// Determine commit checked out in submodule.
513-
let checked_out_hash =
514-
submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout();
515-
let checked_out_hash = checked_out_hash.trim_end();
516-
// Determine commit that the submodule *should* have.
517-
let recorded = helpers::git(Some(&self.src))
518-
.run_always()
519-
.args(["ls-tree", "HEAD"])
520-
.arg(relative_path)
521-
.run_capture_stdout(self)
522-
.stdout();
523-
let actual_hash = recorded
524-
.split_whitespace()
525-
.nth(2)
526-
.unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
527-
528-
if actual_hash == checked_out_hash {
529-
// already checked out
530-
return;
531-
}
532-
533-
println!("Updating submodule {relative_path}");
534-
helpers::git(Some(&self.src))
535-
.run_always()
536-
.args(["submodule", "-q", "sync"])
537-
.arg(relative_path)
538-
.run(self);
539-
540-
// Try passing `--progress` to start, then run git again without if that fails.
541-
let update = |progress: bool| {
542-
// Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
543-
// even though that has no relation to the upstream for the submodule.
544-
let current_branch = helpers::git(Some(&self.src))
545-
.allow_failure()
546-
.run_always()
547-
.args(["symbolic-ref", "--short", "HEAD"])
548-
.run_capture_stdout(self)
549-
.stdout_if_ok()
550-
.map(|s| s.trim().to_owned());
551-
552-
let mut git = helpers::git(Some(&self.src)).allow_failure();
553-
git.run_always();
554-
if let Some(branch) = current_branch {
555-
// If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
556-
// This syntax isn't accepted by `branch.{branch}`. Strip it.
557-
let branch = branch.strip_prefix("heads/").unwrap_or(&branch);
558-
git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
559-
}
560-
git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
561-
if progress {
562-
git.arg("--progress");
563-
}
564-
git.arg(relative_path);
565-
git
566-
};
567-
if !update(true).run(self) {
568-
update(false).run(self);
569-
}
570-
571-
// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
572-
// diff-index reports the modifications through the exit status
573-
let has_local_modifications =
574-
!submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]).run(self);
575-
if has_local_modifications {
576-
submodule_git().args(["stash", "push"]).run(self);
577-
}
578-
579-
submodule_git().args(["reset", "-q", "--hard"]).run(self);
580-
submodule_git().args(["clean", "-qdfx"]).run(self);
581-
582-
if has_local_modifications {
583-
submodule_git().args(["stash", "pop"]).run(self);
584-
}
585-
}
586-
587476
/// Updates a submodule, and exits with a failure if submodule management
588477
/// is disabled and the submodule does not exist.
589478
///
@@ -598,7 +487,7 @@ impl Build {
598487
if cfg!(test) && !self.config.submodules() {
599488
return;
600489
}
601-
self.update_submodule(submodule);
490+
self.config.update_submodule(submodule);
602491
let absolute_path = self.config.src.join(submodule);
603492
if dir_is_empty(&absolute_path) {
604493
let maybe_enable = if !self.config.submodules()
@@ -646,7 +535,7 @@ impl Build {
646535
let path = Path::new(submodule);
647536
// Don't update the submodule unless it's already been cloned.
648537
if GitInfo::new(false, path).is_managed_git_subrepository() {
649-
self.update_submodule(submodule);
538+
self.config.update_submodule(submodule);
650539
}
651540
}
652541
}
@@ -659,7 +548,7 @@ impl Build {
659548
}
660549

661550
if GitInfo::new(false, Path::new(submodule)).is_managed_git_subrepository() {
662-
self.update_submodule(submodule);
551+
self.config.update_submodule(submodule);
663552
}
664553
}
665554

0 commit comments

Comments
 (0)