@@ -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) => {
@@ -2462,3 +2477,226 @@ pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitC
2462
2477
git_merge_commit_email : & stage0_metadata. config . git_merge_commit_email ,
2463
2478
}
2464
2479
}
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