@@ -48,44 +48,45 @@ 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 (
59- "src/stage0 doesn't contain a checksum for {}. "
58+ "src/stage0.json doesn't contain a checksum for {}. "
6059 "Pre-built artifacts might not be available for this "
6160 "target at this time, see https://doc.rust-lang.org/nightly"
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,105 @@ 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+ with open (stamp , "w" ) as f :
761+ f .write (last_commit )
762+ self .is_precompiled_bootstrap = True
763+
764+ except Exception as e :
765+ return
766+
767+ if not self .is_precompiled_bootstrap :
768+ self .build_bootstrap ()
769+
770+ def download_bootstrap (self , commit_hash ):
771+ filename = f"bootstrap-nightly-{ self .build_triple ()} .tar.gz"
772+ tarball_suffix = "tar.gz"
773+ key = commit_hash
774+ pattern = "bootstrap"
775+
776+ bootstrap_path = os .path .join (
777+ self .bin_root (),
778+ f"nightly-{ self .build_triple ()} " ,
779+ "bootstrap" ,
780+ "bootstrap" ,
781+ "bin" ,
782+ "bootstrap" ,
783+ )
784+
785+ if os .path .exists (bootstrap_path ):
786+ return True
787+
788+ cache_dir = os .path .join (self .build_dir , "cache" )
789+ tarball_path = os .path .join (cache_dir , filename )
790+ base_url = self .stage0_data .get ("artifacts_server" )
791+ download_url = f"{ key } /{ filename } "
792+
793+ if not os .path .exists (tarball_path ):
794+ try :
795+ get (
796+ base_url ,
797+ download_url ,
798+ tarball_path ,
799+ self .stage0_data ,
800+ verbose = self .verbose ,
801+ verify_checksum = False ,
802+ )
803+ except Exception as e :
804+ return False
805+
806+ try :
807+ unpack (
808+ tarball_path ,
809+ tarball_suffix ,
810+ self .bin_root (),
811+ match = pattern ,
812+ verbose = self .verbose ,
813+ )
814+ except Exception as e :
815+ return False
816+
817+ return True
818+
819+ def last_bootstrap_commit (self ):
820+ cmd = ["git" , "log" , "-1" , "--pretty=format:%H" , "src/bootstrap" ]
821+ try :
822+ commit_hash = output_cmd (cmd )
823+ return commit_hash .strip () if commit_hash else None
824+ except subprocess .CalledProcessError :
825+ return None
826+
827+ def bootstrap_out_of_date (self , commit : str ):
828+ stamp_path = os .path .join (self .bin_root (), "bootstrap" , ".bootstrap-stamp" )
829+ if not os .path .exists (stamp_path ):
830+ return True
831+ with open (stamp_path , "r" ) as f :
832+ stamp_commit = f .read ().strip ()
833+ return stamp_commit != commit
834+
727835 def should_fix_bins_and_dylibs (self ):
728836 """Whether or not `fix_bin_or_dylib` needs to be run; can only be True
729837 on NixOS or if bootstrap.toml has `build.patch-binaries-for-nix` set.
@@ -995,7 +1103,14 @@ def bootstrap_binary(self):
9951103 ... "debug", "bootstrap")
9961104 True
9971105 """
998- return os .path .join (self .bootstrap_out (), "debug" , "bootstrap" )
1106+ if self .is_precompiled_bootstrap :
1107+ root = self .bin_root ()
1108+ subfolder = "nightly-{}" .format (self .build_triple ())
1109+ return os .path .join (
1110+ root , subfolder , "bootstrap" , "bootstrap" , "bin" , "bootstrap"
1111+ )
1112+ else :
1113+ return os .path .join (self .bootstrap_out (), "debug" , "bootstrap" )
9991114
10001115 def build_bootstrap (self ):
10011116 """Build bootstrap"""
@@ -1330,13 +1445,16 @@ def bootstrap(args):
13301445 build = RustBuild (config_toml , args )
13311446 build .check_vendored_status ()
13321447
1448+ build_dir = args .build_dir or build .get_toml ("build_dir" , "build" ) or "build"
1449+ build .build_dir = os .path .abspath (build_dir )
1450+
13331451 if not os .path .exists (build .build_dir ):
13341452 os .makedirs (os .path .realpath (build .build_dir ))
13351453
1336- # Fetch/build the bootstrap
13371454 build .download_toolchain ()
13381455 sys .stdout .flush ()
1339- build .build_bootstrap ()
1456+
1457+ build .download_or_build_bootstrap ()
13401458 sys .stdout .flush ()
13411459
13421460 # Run the bootstrap
@@ -1362,8 +1480,8 @@ def main():
13621480 # process has to happen before anything is printed out.
13631481 if help_triggered :
13641482 eprint (
1365- "INFO: Downloading and building bootstrap before processing --help command. \n "
1366- " See src/bootstrap/README.md for help with common commands."
1483+ "INFO: Checking if bootstrap needs to be downloaded or built before processing "
1484+ "--help command. \n See src/bootstrap/README.md for help with common commands."
13671485 )
13681486
13691487 exit_code = 0
0 commit comments