Skip to content

Commit 111a0e8

Browse files
committed
add parse_download_ci_llvm function and invoke from parse_inner
1 parent b4675dc commit 111a0e8

File tree

2 files changed

+248
-7
lines changed

2 files changed

+248
-7
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ pub(crate) fn detect_llvm_freshness(config: &Config, is_git: bool) -> PathFreshn
196196
///
197197
/// This checks the build triple platform to confirm we're usable at all, and if LLVM
198198
/// with/without assertions is available.
199-
pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) -> bool {
199+
pub(crate) fn is_ci_llvm_available_for_target(
200+
host_target: &TargetSelection,
201+
asserts: bool,
202+
) -> bool {
200203
// This is currently all tier 1 targets and tier 2 targets with host tools
201204
// (since others may not have CI artifacts)
202205
// https://doc.rust-lang.org/rustc/platform-support.html#tier-1
@@ -235,8 +238,8 @@ pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) ->
235238
("x86_64-unknown-netbsd", false),
236239
];
237240

238-
if !supported_platforms.contains(&(&*config.host_target.triple, asserts))
239-
&& (asserts || !supported_platforms.contains(&(&*config.host_target.triple, true)))
241+
if !supported_platforms.contains(&(&*host_target.triple, asserts))
242+
&& (asserts || !supported_platforms.contains(&(&*host_target.triple, true)))
240243
{
241244
return false;
242245
}

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

Lines changed: 242 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,8 +1200,19 @@ impl Config {
12001200
config.llvm_enable_warnings = llvm_enable_warnings.unwrap_or(false);
12011201
config.llvm_build_config = llvm_build_config.clone().unwrap_or(Default::default());
12021202

1203-
config.llvm_from_ci =
1204-
config.parse_download_ci_llvm(llvm_download_ci_llvm, config.llvm_assertions);
1203+
config.llvm_from_ci = parse_download_ci_llvm(
1204+
&config.exec_ctx,
1205+
&config.submodules,
1206+
&config.stage0_metadata,
1207+
&config.src,
1208+
config.path_modification_cache.clone(),
1209+
&config.host_target,
1210+
&config.download_rustc_commit,
1211+
&config.rust_info,
1212+
config.is_running_on_ci,
1213+
llvm_download_ci_llvm,
1214+
config.llvm_assertions,
1215+
);
12051216

12061217
if config.llvm_from_ci {
12071218
let warn = |option: &str| {
@@ -1902,7 +1913,11 @@ impl Config {
19021913
let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS);
19031914

19041915
// Return false if there are untracked changes, otherwise check if CI LLVM is available.
1905-
if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) }
1916+
if has_changes {
1917+
false
1918+
} else {
1919+
llvm::is_ci_llvm_available_for_target(&self.host_target, asserts)
1920+
}
19061921
};
19071922

19081923
match download_ci_llvm {
@@ -1921,7 +1936,7 @@ impl Config {
19211936
}
19221937

19231938
// If download-ci-llvm=true we also want to check that CI llvm is available
1924-
b && llvm::is_ci_llvm_available_for_target(self, asserts)
1939+
b && llvm::is_ci_llvm_available_for_target(&self.host_target, asserts)
19251940
}
19261941
StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
19271942
StringOrBool::String(other) => {
@@ -2462,3 +2477,226 @@ pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitC
24622477
git_merge_commit_email: &stage0_metadata.config.git_merge_commit_email,
24632478
}
24642479
}
2480+
2481+
pub fn parse_download_ci_llvm(
2482+
exec_ctx: &ExecutionContext,
2483+
submodules: &Option<bool>,
2484+
stage0_metadata: &build_helper::stage0_parser::Stage0,
2485+
src: &Path,
2486+
path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
2487+
host_target: &TargetSelection,
2488+
download_rustc_commit: &Option<String>,
2489+
rust_info: &channel::GitInfo,
2490+
is_running_on_ci: bool,
2491+
download_ci_llvm: Option<StringOrBool>,
2492+
asserts: bool,
2493+
) -> bool {
2494+
// We don't ever want to use `true` on CI, as we should not
2495+
// download upstream artifacts if there are any local modifications.
2496+
let default = if is_running_on_ci {
2497+
StringOrBool::String("if-unchanged".to_string())
2498+
} else {
2499+
StringOrBool::Bool(true)
2500+
};
2501+
let download_ci_llvm = download_ci_llvm.unwrap_or(default);
2502+
2503+
let if_unchanged = || {
2504+
if rust_info.is_from_tarball() {
2505+
// Git is needed for running "if-unchanged" logic.
2506+
println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
2507+
crate::exit!(1);
2508+
}
2509+
2510+
// Fetching the LLVM submodule is unnecessary for self-tests.
2511+
#[cfg(not(test))]
2512+
update_submodule(submodules, exec_ctx, src, rust_info, "src/llvm-project");
2513+
2514+
// Check for untracked changes in `src/llvm-project` and other important places.
2515+
let has_changes = has_changes_from_upstream(
2516+
stage0_metadata,
2517+
src,
2518+
path_modification_cache,
2519+
LLVM_INVALIDATION_PATHS,
2520+
);
2521+
2522+
// Return false if there are untracked changes, otherwise check if CI LLVM is available.
2523+
if has_changes {
2524+
false
2525+
} else {
2526+
llvm::is_ci_llvm_available_for_target(host_target, asserts)
2527+
}
2528+
};
2529+
2530+
match download_ci_llvm {
2531+
StringOrBool::Bool(b) => {
2532+
if !b && download_rustc_commit.is_some() {
2533+
panic!(
2534+
"`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
2535+
);
2536+
}
2537+
2538+
if b && is_running_on_ci {
2539+
// On CI, we must always rebuild LLVM if there were any modifications to it
2540+
panic!(
2541+
"`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
2542+
);
2543+
}
2544+
2545+
// If download-ci-llvm=true we also want to check that CI llvm is available
2546+
b && llvm::is_ci_llvm_available_for_target(host_target, asserts)
2547+
}
2548+
StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
2549+
StringOrBool::String(other) => {
2550+
panic!("unrecognized option for download-ci-llvm: {other:?}")
2551+
}
2552+
}
2553+
}
2554+
2555+
pub fn has_changes_from_upstream(
2556+
stage0_metadata: &build_helper::stage0_parser::Stage0,
2557+
src: &Path,
2558+
path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
2559+
paths: &[&'static str],
2560+
) -> bool {
2561+
match check_path_modifications_(stage0_metadata, src, path_modification_cache, paths) {
2562+
PathFreshness::LastModifiedUpstream { .. } => false,
2563+
PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true,
2564+
}
2565+
}
2566+
2567+
#[cfg_attr(
2568+
feature = "tracing",
2569+
instrument(
2570+
level = "trace",
2571+
name = "Config::update_submodule",
2572+
skip_all,
2573+
fields(relative_path = ?relative_path),
2574+
),
2575+
)]
2576+
pub(crate) fn update_submodule(
2577+
submodules: &Option<bool>,
2578+
exec_ctx: &ExecutionContext,
2579+
src: &Path,
2580+
rust_info: &channel::GitInfo,
2581+
relative_path: &str,
2582+
) {
2583+
if rust_info.is_from_tarball() || !submodules_(submodules, rust_info) {
2584+
return;
2585+
}
2586+
2587+
let absolute_path = src.join(relative_path);
2588+
2589+
// NOTE: This check is required because `jj git clone` doesn't create directories for
2590+
// submodules, they are completely ignored. The code below assumes this directory exists,
2591+
// so create it here.
2592+
if !absolute_path.exists() {
2593+
t!(fs::create_dir_all(&absolute_path));
2594+
}
2595+
2596+
// NOTE: The check for the empty directory is here because when running x.py the first time,
2597+
// the submodule won't be checked out. Check it out now so we can build it.
2598+
if !git_info(exec_ctx, false, &absolute_path).is_managed_git_subrepository()
2599+
&& !helpers::dir_is_empty(&absolute_path)
2600+
{
2601+
return;
2602+
}
2603+
2604+
// Submodule updating actually happens during in the dry run mode. We need to make sure that
2605+
// all the git commands below are actually executed, because some follow-up code
2606+
// in bootstrap might depend on the submodules being checked out. Furthermore, not all
2607+
// the command executions below work with an empty output (produced during dry run).
2608+
// Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in
2609+
// dry run mode.
2610+
let submodule_git = || {
2611+
let mut cmd = helpers::git(Some(&absolute_path));
2612+
cmd.run_in_dry_run();
2613+
cmd
2614+
};
2615+
2616+
// Determine commit checked out in submodule.
2617+
let checked_out_hash =
2618+
submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(exec_ctx).stdout();
2619+
let checked_out_hash = checked_out_hash.trim_end();
2620+
// Determine commit that the submodule *should* have.
2621+
let recorded = helpers::git(Some(src))
2622+
.run_in_dry_run()
2623+
.args(["ls-tree", "HEAD"])
2624+
.arg(relative_path)
2625+
.run_capture_stdout(exec_ctx)
2626+
.stdout();
2627+
2628+
let actual_hash = recorded
2629+
.split_whitespace()
2630+
.nth(2)
2631+
.unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
2632+
2633+
if actual_hash == checked_out_hash {
2634+
// already checked out
2635+
return;
2636+
}
2637+
2638+
println!("Updating submodule {relative_path}");
2639+
2640+
helpers::git(Some(src))
2641+
.allow_failure()
2642+
.run_in_dry_run()
2643+
.args(["submodule", "-q", "sync"])
2644+
.arg(relative_path)
2645+
.run(exec_ctx);
2646+
2647+
// Try passing `--progress` to start, then run git again without if that fails.
2648+
let update = |progress: bool| {
2649+
// Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
2650+
// even though that has no relation to the upstream for the submodule.
2651+
let current_branch = helpers::git(Some(src))
2652+
.allow_failure()
2653+
.run_in_dry_run()
2654+
.args(["symbolic-ref", "--short", "HEAD"])
2655+
.run_capture(exec_ctx);
2656+
2657+
let mut git = helpers::git(Some(&src)).allow_failure();
2658+
git.run_in_dry_run();
2659+
if current_branch.is_success() {
2660+
// If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
2661+
// This syntax isn't accepted by `branch.{branch}`. Strip it.
2662+
let branch = current_branch.stdout();
2663+
let branch = branch.trim();
2664+
let branch = branch.strip_prefix("heads/").unwrap_or(branch);
2665+
git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
2666+
}
2667+
git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
2668+
if progress {
2669+
git.arg("--progress");
2670+
}
2671+
git.arg(relative_path);
2672+
git
2673+
};
2674+
if !update(true).allow_failure().run(exec_ctx) {
2675+
update(false).allow_failure().run(exec_ctx);
2676+
}
2677+
2678+
// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
2679+
// diff-index reports the modifications through the exit status
2680+
let has_local_modifications =
2681+
!submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]).run(exec_ctx);
2682+
if has_local_modifications {
2683+
submodule_git().allow_failure().args(["stash", "push"]).run(exec_ctx);
2684+
}
2685+
2686+
submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(exec_ctx);
2687+
submodule_git().allow_failure().args(["clean", "-qdfx"]).run(exec_ctx);
2688+
2689+
if has_local_modifications {
2690+
submodule_git().allow_failure().args(["stash", "pop"]).run(exec_ctx);
2691+
}
2692+
}
2693+
2694+
pub fn git_info(exec_ctx: &ExecutionContext, omit_git_hash: bool, dir: &Path) -> GitInfo {
2695+
GitInfo::new(omit_git_hash, dir, exec_ctx)
2696+
}
2697+
2698+
pub fn submodules_(submodules: &Option<bool>, rust_info: &channel::GitInfo) -> bool {
2699+
// If not specified in config, the default is to only manage
2700+
// submodules if we're currently inside a git repository.
2701+
submodules.unwrap_or(rust_info.is_managed_git_subrepository())
2702+
}

0 commit comments

Comments
 (0)