@@ -1200,8 +1200,19 @@ impl Config {
1200
1200
config. llvm_enable_warnings = llvm_enable_warnings. unwrap_or ( false ) ;
1201
1201
config. llvm_build_config = llvm_build_config. clone ( ) . unwrap_or ( Default :: default ( ) ) ;
1202
1202
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
+ ) ;
1205
1216
1206
1217
if config. llvm_from_ci {
1207
1218
let warn = |option : & str | {
@@ -1902,7 +1913,11 @@ impl Config {
1902
1913
let has_changes = self . has_changes_from_upstream ( LLVM_INVALIDATION_PATHS ) ;
1903
1914
1904
1915
// 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
+ }
1906
1921
} ;
1907
1922
1908
1923
match download_ci_llvm {
@@ -1921,7 +1936,7 @@ impl Config {
1921
1936
}
1922
1937
1923
1938
// 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)
1925
1940
}
1926
1941
StringOrBool :: String ( s) if s == "if-unchanged" => if_unchanged ( ) ,
1927
1942
StringOrBool :: String ( other) => {
@@ -2467,3 +2482,226 @@ pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitC
2467
2482
git_merge_commit_email : & stage0_metadata. config . git_merge_commit_email ,
2468
2483
}
2469
2484
}
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