@@ -14,7 +14,7 @@ use std::sync::OnceLock;
1414use std:: { cmp, env, fs} ;
1515
1616use build_helper:: exit;
17- use build_helper:: git:: GitConfig ;
17+ use build_helper:: git:: { output_result , GitConfig } ;
1818use serde:: { Deserialize , Deserializer } ;
1919use serde_derive:: Deserialize ;
2020
@@ -2509,6 +2509,123 @@ impl Config {
25092509 }
25102510 }
25112511
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+
25122629 #[ cfg( feature = "bootstrap-self-test" ) ]
25132630 pub fn check_stage0_version ( & self , _program_path : & Path , _component_name : & ' static str ) { }
25142631
@@ -2613,19 +2730,23 @@ impl Config {
26132730 asserts : bool ,
26142731 ) -> bool {
26152732 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 ;
26282739 }
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) }
26292750 } ;
26302751
26312752 match download_ci_llvm {
0 commit comments