@@ -14,7 +14,7 @@ use std::sync::OnceLock;
14
14
use std:: { cmp, env, fs} ;
15
15
16
16
use build_helper:: exit;
17
- use build_helper:: git:: GitConfig ;
17
+ use build_helper:: git:: { output_result , GitConfig } ;
18
18
use serde:: { Deserialize , Deserializer } ;
19
19
use serde_derive:: Deserialize ;
20
20
@@ -2509,6 +2509,123 @@ impl Config {
2509
2509
}
2510
2510
}
2511
2511
2512
+ /// Given a path to the directory of a submodule, update it.
2513
+ ///
2514
+ /// `relative_path` should be relative to the root of the git repository, not an absolute path.
2515
+ ///
2516
+ /// This *does not* update the submodule if `config.toml` explicitly says
2517
+ /// not to, or if we're not in a git repository (like a plain source
2518
+ /// tarball). Typically [`crate::Build::require_submodule`] should be
2519
+ /// used instead to provide a nice error to the user if the submodule is
2520
+ /// missing.
2521
+ pub ( crate ) fn update_submodule ( & self , relative_path : & str ) {
2522
+ if !self . submodules ( ) {
2523
+ return ;
2524
+ }
2525
+
2526
+ let absolute_path = self . src . join ( relative_path) ;
2527
+
2528
+ // NOTE: The check for the empty directory is here because when running x.py the first time,
2529
+ // the submodule won't be checked out. Check it out now so we can build it.
2530
+ if !GitInfo :: new ( false , & absolute_path) . is_managed_git_subrepository ( )
2531
+ && !helpers:: dir_is_empty ( & absolute_path)
2532
+ {
2533
+ return ;
2534
+ }
2535
+
2536
+ // Submodule updating actually happens during in the dry run mode. We need to make sure that
2537
+ // all the git commands below are actually executed, because some follow-up code
2538
+ // in bootstrap might depend on the submodules being checked out. Furthermore, not all
2539
+ // the command executions below work with an empty output (produced during dry run).
2540
+ // Therefore, all commands below are marked with `run_always()`, so that they also run in
2541
+ // dry run mode.
2542
+ let submodule_git = || {
2543
+ let mut cmd = helpers:: git ( Some ( & absolute_path) ) ;
2544
+ cmd. run_always ( ) ;
2545
+ cmd
2546
+ } ;
2547
+
2548
+ // Determine commit checked out in submodule.
2549
+ let checked_out_hash = output ( submodule_git ( ) . args ( [ "rev-parse" , "HEAD" ] ) . as_command_mut ( ) ) ;
2550
+ let checked_out_hash = checked_out_hash. trim_end ( ) ;
2551
+ // Determine commit that the submodule *should* have.
2552
+ let recorded = output (
2553
+ helpers:: git ( Some ( & self . src ) )
2554
+ . run_always ( )
2555
+ . args ( [ "ls-tree" , "HEAD" ] )
2556
+ . arg ( relative_path)
2557
+ . as_command_mut ( ) ,
2558
+ ) ;
2559
+
2560
+ let actual_hash = recorded
2561
+ . split_whitespace ( )
2562
+ . nth ( 2 )
2563
+ . unwrap_or_else ( || panic ! ( "unexpected output `{}`" , recorded) ) ;
2564
+
2565
+ if actual_hash == checked_out_hash {
2566
+ // already checked out
2567
+ return ;
2568
+ }
2569
+
2570
+ println ! ( "Updating submodule {relative_path}" ) ;
2571
+ self . check_run (
2572
+ helpers:: git ( Some ( & self . src ) )
2573
+ . run_always ( )
2574
+ . args ( [ "submodule" , "-q" , "sync" ] )
2575
+ . arg ( relative_path) ,
2576
+ ) ;
2577
+
2578
+ // Try passing `--progress` to start, then run git again without if that fails.
2579
+ let update = |progress : bool | {
2580
+ // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
2581
+ // even though that has no relation to the upstream for the submodule.
2582
+ let current_branch = output_result (
2583
+ helpers:: git ( Some ( & self . src ) )
2584
+ . allow_failure ( )
2585
+ . run_always ( )
2586
+ . args ( [ "symbolic-ref" , "--short" , "HEAD" ] )
2587
+ . as_command_mut ( ) ,
2588
+ )
2589
+ . map ( |b| b. trim ( ) . to_owned ( ) ) ;
2590
+
2591
+ let mut git = helpers:: git ( Some ( & self . src ) ) . allow_failure ( ) ;
2592
+ git. run_always ( ) ;
2593
+ if let Ok ( branch) = current_branch {
2594
+ // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
2595
+ // This syntax isn't accepted by `branch.{branch}`. Strip it.
2596
+ let branch = branch. strip_prefix ( "heads/" ) . unwrap_or ( & branch) ;
2597
+ git. arg ( "-c" ) . arg ( format ! ( "branch.{branch}.remote=origin" ) ) ;
2598
+ }
2599
+ git. args ( [ "submodule" , "update" , "--init" , "--recursive" , "--depth=1" ] ) ;
2600
+ if progress {
2601
+ git. arg ( "--progress" ) ;
2602
+ }
2603
+ git. arg ( relative_path) ;
2604
+ git
2605
+ } ;
2606
+ if !self . check_run ( & mut update ( true ) ) {
2607
+ self . check_run ( & mut update ( false ) ) ;
2608
+ }
2609
+
2610
+ // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
2611
+ // diff-index reports the modifications through the exit status
2612
+ let has_local_modifications = !self . check_run ( submodule_git ( ) . allow_failure ( ) . args ( [
2613
+ "diff-index" ,
2614
+ "--quiet" ,
2615
+ "HEAD" ,
2616
+ ] ) ) ;
2617
+ if has_local_modifications {
2618
+ self . check_run ( submodule_git ( ) . args ( [ "stash" , "push" ] ) ) ;
2619
+ }
2620
+
2621
+ self . check_run ( submodule_git ( ) . args ( [ "reset" , "-q" , "--hard" ] ) ) ;
2622
+ self . check_run ( submodule_git ( ) . args ( [ "clean" , "-qdfx" ] ) ) ;
2623
+
2624
+ if has_local_modifications {
2625
+ self . check_run ( submodule_git ( ) . args ( [ "stash" , "pop" ] ) ) ;
2626
+ }
2627
+ }
2628
+
2512
2629
#[ cfg( feature = "bootstrap-self-test" ) ]
2513
2630
pub fn check_stage0_version ( & self , _program_path : & Path , _component_name : & ' static str ) { }
2514
2631
@@ -2613,19 +2730,23 @@ impl Config {
2613
2730
asserts : bool ,
2614
2731
) -> bool {
2615
2732
let if_unchanged = || {
2616
- // Git is needed to track modifications here, but tarball source is not available.
2617
- // If not modified here or built through tarball source, we maintain consistency
2618
- // with '"if available"'.
2619
- if !self . rust_info . is_from_tarball ( )
2620
- && self
2621
- . last_modified_commit ( & [ "src/llvm-project" ] , "download-ci-llvm" , true )
2622
- . is_none ( )
2623
- {
2624
- // there are some untracked changes in the given paths.
2625
- false
2626
- } else {
2627
- llvm:: is_ci_llvm_available ( self , asserts)
2733
+ if self . rust_info . is_from_tarball ( ) {
2734
+ // Git is needed for running "if-unchanged" logic.
2735
+ println ! (
2736
+ "WARNING: 'if-unchanged' has no effect on tarball sources; ignoring `download-ci-llvm`."
2737
+ ) ;
2738
+ return false ;
2628
2739
}
2740
+
2741
+ self . update_submodule ( "src/llvm-project" ) ;
2742
+
2743
+ // Check for untracked changes in `src/llvm-project`.
2744
+ let has_changes = self
2745
+ . last_modified_commit ( & [ "src/llvm-project" ] , "download-ci-llvm" , true )
2746
+ . is_none ( ) ;
2747
+
2748
+ // Return false if there are untracked changes, otherwise check if CI LLVM is available.
2749
+ if has_changes { false } else { llvm:: is_ci_llvm_available ( self , asserts) }
2629
2750
} ;
2630
2751
2631
2752
match download_ci_llvm {
0 commit comments