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