@@ -48,12 +48,11 @@ def eprint(*args, **kwargs):
4848 print (* args , ** kwargs )
4949
5050
51- def get (base , url , path , checksums , verbose = False ):
51+ def get (base , url , path , checksums , verbose = False , verify_checksum = True ):
5252 with tempfile .NamedTemporaryFile (delete = False ) as temp_file :
5353 temp_path = temp_file .name
54-
5554 try :
56- if url not in checksums :
55+ if url not in checksums and verify_checksum :
5756 raise RuntimeError (
5857 (
5958 "src/stage0 doesn't contain a checksum for {}. "
@@ -62,30 +61,32 @@ def get(base, url, path, checksums, verbose=False):
6261 "/rustc/platform-support.html for more information."
6362 ).format (url )
6463 )
65- sha256 = checksums [url ]
66- if os .path .exists (path ):
67- if verify (path , sha256 , False ):
68- if verbose :
69- eprint ("using already-download file" , path )
70- return
71- else :
72- if verbose :
73- eprint (
74- "ignoring already-download file" ,
75- path ,
76- "due to failed verification" ,
77- )
78- os .unlink (path )
64+ if verify_checksum :
65+ sha256 = checksums [url ]
66+ if os .path .exists (path ):
67+ if verify (path , sha256 , False ):
68+ if verbose :
69+ print ("using already-download file" , path , file = sys .stderr )
70+ return
71+ else :
72+ if verbose :
73+ print (
74+ "ignoring already-download file" ,
75+ path ,
76+ "due to failed verification" ,
77+ file = sys .stderr ,
78+ )
79+ os .unlink (path )
7980 download (temp_path , "{}/{}" .format (base , url ), True , verbose )
80- if not verify (temp_path , sha256 , verbose ):
81+ if verify_checksum and not verify (temp_path , checksums [ url ] , verbose ):
8182 raise RuntimeError ("failed verification" )
8283 if verbose :
83- eprint ("moving {} to {}" .format (temp_path , path ))
84+ print ("moving {} to {}" .format (temp_path , path ), file = sys . stderr )
8485 shutil .move (temp_path , path )
8586 finally :
8687 if os .path .isfile (temp_path ):
8788 if verbose :
88- eprint ("removing" , temp_path )
89+ print ("removing" , temp_path , file = sys . stderr )
8990 os .unlink (temp_path )
9091
9192
@@ -267,6 +268,12 @@ def require(cmd, exit=True, exception=False):
267268 return None
268269
269270
271+ def output_cmd (cmd ):
272+ p = subprocess .Popen (cmd , stdout = subprocess .PIPE , text = True )
273+ output = p .communicate ()[0 ].strip ('"' ).strip ()
274+ return output
275+
276+
270277def format_build_time (duration ):
271278 """Return a nicer format for build time
272279
@@ -531,6 +538,7 @@ class FakeArgs:
531538 """Used for unit tests to avoid updating all call sites"""
532539
533540 def __init__ (self ):
541+ self .is_precompiled_bootstrap = False
534542 self .build = ""
535543 self .build_dir = ""
536544 self .clean = False
@@ -558,6 +566,7 @@ def __init__(self, config_toml="", args=None):
558566 self .verbose = args .verbose
559567 self .color = args .color
560568 self .warnings = args .warnings
569+ self .is_precompiled_bootstrap = False
561570
562571 config_verbose_count = self .get_toml ("verbose" , "build" )
563572 if config_verbose_count is not None :
@@ -724,6 +733,119 @@ def download_toolchain(self):
724733 with output (self .rustc_stamp ()) as rust_stamp :
725734 rust_stamp .write (key )
726735
736+ def is_bootstrap_modified (self ):
737+ cmd = ["git" , "status" , "--porcelain" , "src/bootstrap" ]
738+ try :
739+ output = output_cmd (cmd )
740+ return bool (output )
741+ except subprocess .CalledProcessError :
742+ return False
743+
744+ def download_or_build_bootstrap (self ):
745+ try :
746+ if self .is_bootstrap_modified ():
747+ self .is_precompiled_bootstrap = False
748+ self .build_bootstrap ()
749+ return
750+ last_commit = self .last_bootstrap_commit ()
751+ if last_commit is None :
752+ self .build_bootstrap ()
753+ return
754+ if self .bootstrap_out_of_date (last_commit ):
755+ success = self .download_bootstrap (last_commit )
756+ if success :
757+ stamp = os .path .join (
758+ self .build_dir , "bootstrap" , ".bootstrap-stamp"
759+ )
760+ os .makedirs (os .path .dirname (stamp ), exist_ok = True )
761+ with open (stamp , "w" ) as f :
762+ f .write (last_commit )
763+ self .is_precompiled_bootstrap = True
764+
765+ except Exception as e :
766+ return
767+
768+ if not self .is_precompiled_bootstrap :
769+ self .build_bootstrap ()
770+
771+ def download_bootstrap (self , commit_hash ):
772+ filename = f"bootstrap-nightly-{ self .build_triple ()} .tar.xz"
773+ tarball_suffix = ".tar.xz"
774+ key = commit_hash
775+ pattern = "bootstrap"
776+
777+ bootstrap_path = os .path .join (
778+ self .bin_root (),
779+ f"nightly-{ self .build_triple ()} " ,
780+ "bootstrap" ,
781+ "bootstrap" ,
782+ "bin" ,
783+ "bootstrap" ,
784+ )
785+
786+ if os .path .exists (bootstrap_path ):
787+ return True
788+
789+ cache_dir = os .path .join (self .build_dir , "cache" )
790+ tarball_path = os .path .join (cache_dir , filename )
791+ base_url = self .stage0_data .get ("artifacts_server" )
792+ download_url = f"{ key } /{ filename } "
793+
794+ if not os .path .exists (tarball_path ):
795+ try :
796+ get (
797+ base_url ,
798+ download_url ,
799+ tarball_path ,
800+ self .stage0_data ,
801+ verbose = self .verbose ,
802+ verify_checksum = False ,
803+ )
804+ except Exception as e :
805+ return False
806+
807+ try :
808+ unpack (
809+ tarball_path ,
810+ tarball_suffix ,
811+ self .bin_root (),
812+ match = pattern ,
813+ verbose = self .verbose ,
814+ )
815+ except Exception as e :
816+ return False
817+
818+ return True
819+
820+ def last_bootstrap_commit (self ):
821+ merge_email = self .stage0_data .get ("git_merge_commit_email" )
822+ if not merge_email :
823+ return None
824+
825+ cmd = [
826+ "git" ,
827+ "log" ,
828+ "-1" ,
829+ f"--author={ merge_email } " ,
830+ "--pretty=format:%H" ,
831+ "src/bootstrap" ,
832+ ]
833+
834+ try :
835+ commit_hash = output_cmd (cmd )
836+ except subprocess .CalledProcessError :
837+ return None
838+
839+ return commit_hash .strip () if commit_hash else None
840+
841+ def bootstrap_out_of_date (self , commit : str ):
842+ stamp_path = os .path .join (self .bin_root (), "bootstrap" , ".bootstrap-stamp" )
843+ if not os .path .exists (stamp_path ):
844+ return True
845+ with open (stamp_path , "r" ) as f :
846+ stamp_commit = f .read ().strip ()
847+ return stamp_commit != commit
848+
727849 def should_fix_bins_and_dylibs (self ):
728850 """Whether or not `fix_bin_or_dylib` needs to be run; can only be True
729851 on NixOS or if bootstrap.toml has `build.patch-binaries-for-nix` set.
@@ -995,7 +1117,11 @@ def bootstrap_binary(self):
9951117 ... "debug", "bootstrap")
9961118 True
9971119 """
998- return os .path .join (self .bootstrap_out (), "debug" , "bootstrap" )
1120+ if self .is_precompiled_bootstrap :
1121+ root = self .bin_root ()
1122+ return os .path .join (root , "bootstrap" , "bin" , "bootstrap" )
1123+ else :
1124+ return os .path .join (self .bootstrap_out (), "debug" , "bootstrap" )
9991125
10001126 def build_bootstrap (self ):
10011127 """Build bootstrap"""
@@ -1330,13 +1456,16 @@ def bootstrap(args):
13301456 build = RustBuild (config_toml , args )
13311457 build .check_vendored_status ()
13321458
1459+ build_dir = args .build_dir or build .get_toml ("build_dir" , "build" ) or "build"
1460+ build .build_dir = os .path .abspath (build_dir )
1461+
13331462 if not os .path .exists (build .build_dir ):
13341463 os .makedirs (os .path .realpath (build .build_dir ))
13351464
1336- # Fetch/build the bootstrap
13371465 build .download_toolchain ()
13381466 sys .stdout .flush ()
1339- build .build_bootstrap ()
1467+
1468+ build .download_or_build_bootstrap ()
13401469 sys .stdout .flush ()
13411470
13421471 # Run the bootstrap
@@ -1362,8 +1491,8 @@ def main():
13621491 # process has to happen before anything is printed out.
13631492 if help_triggered :
13641493 eprint (
1365- "INFO: Downloading and building bootstrap before processing --help command. \n "
1366- " See src/bootstrap/README.md for help with common commands."
1494+ "INFO: Checking if bootstrap needs to be downloaded or built before processing "
1495+ "--help command. \n See src/bootstrap/README.md for help with common commands."
13671496 )
13681497
13691498 exit_code = 0
0 commit comments