@@ -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