@@ -48,12 +48,11 @@ def eprint(*args, **kwargs):
48
48
print (* args , ** kwargs )
49
49
50
50
51
- def get (base , url , path , checksums , verbose = False ):
51
+ def get (base , url , path , checksums , verbose = False , verify_checksum = True ):
52
52
with tempfile .NamedTemporaryFile (delete = False ) as temp_file :
53
53
temp_path = temp_file .name
54
-
55
54
try :
56
- if url not in checksums :
55
+ if url not in checksums and verify_checksum :
57
56
raise RuntimeError (
58
57
(
59
58
"src/stage0 doesn't contain a checksum for {}. "
@@ -62,30 +61,32 @@ def get(base, url, path, checksums, verbose=False):
62
61
"/rustc/platform-support.html for more information."
63
62
).format (url )
64
63
)
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 )
79
80
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 ):
81
82
raise RuntimeError ("failed verification" )
82
83
if verbose :
83
- eprint ("moving {} to {}" .format (temp_path , path ))
84
+ print ("moving {} to {}" .format (temp_path , path ), file = sys . stderr )
84
85
shutil .move (temp_path , path )
85
86
finally :
86
87
if os .path .isfile (temp_path ):
87
88
if verbose :
88
- eprint ("removing" , temp_path )
89
+ print ("removing" , temp_path , file = sys . stderr )
89
90
os .unlink (temp_path )
90
91
91
92
@@ -267,6 +268,12 @@ def require(cmd, exit=True, exception=False):
267
268
return None
268
269
269
270
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
+
270
277
def format_build_time (duration ):
271
278
"""Return a nicer format for build time
272
279
@@ -297,7 +304,7 @@ def default_build_triple(verbose):
297
304
"detected default triple {} from pre-installed rustc" .format (triple )
298
305
)
299
306
return triple
300
- except Exception as e :
307
+ except Exception :
301
308
if verbose :
302
309
eprint ("pre-installed rustc not detected: {}" .format (e ))
303
310
eprint ("falling back to auto-detect" )
@@ -531,6 +538,7 @@ class FakeArgs:
531
538
"""Used for unit tests to avoid updating all call sites"""
532
539
533
540
def __init__ (self ):
541
+ self .is_precompiled_bootstrap = False
534
542
self .build = ""
535
543
self .build_dir = ""
536
544
self .clean = False
@@ -558,6 +566,7 @@ def __init__(self, config_toml="", args=None):
558
566
self .verbose = args .verbose
559
567
self .color = args .color
560
568
self .warnings = args .warnings
569
+ self .is_precompiled_bootstrap = False
561
570
562
571
config_verbose_count = self .get_toml ("verbose" , "build" )
563
572
if config_verbose_count is not None :
@@ -724,6 +733,119 @@ def download_toolchain(self):
724
733
with output (self .rustc_stamp ()) as rust_stamp :
725
734
rust_stamp .write (key )
726
735
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 :
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 :
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 :
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
+
727
849
def should_fix_bins_and_dylibs (self ):
728
850
"""Whether or not `fix_bin_or_dylib` needs to be run; can only be True
729
851
on NixOS or if bootstrap.toml has `build.patch-binaries-for-nix` set.
@@ -995,7 +1117,11 @@ def bootstrap_binary(self):
995
1117
... "debug", "bootstrap")
996
1118
True
997
1119
"""
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" )
999
1125
1000
1126
def build_bootstrap (self ):
1001
1127
"""Build bootstrap"""
@@ -1330,13 +1456,16 @@ def bootstrap(args):
1330
1456
build = RustBuild (config_toml , args )
1331
1457
build .check_vendored_status ()
1332
1458
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
+
1333
1462
if not os .path .exists (build .build_dir ):
1334
1463
os .makedirs (os .path .realpath (build .build_dir ))
1335
1464
1336
- # Fetch/build the bootstrap
1337
1465
build .download_toolchain ()
1338
1466
sys .stdout .flush ()
1339
- build .build_bootstrap ()
1467
+
1468
+ build .download_or_build_bootstrap ()
1340
1469
sys .stdout .flush ()
1341
1470
1342
1471
# Run the bootstrap
@@ -1362,8 +1491,8 @@ def main():
1362
1491
# process has to happen before anything is printed out.
1363
1492
if help_triggered :
1364
1493
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."
1367
1496
)
1368
1497
1369
1498
exit_code = 0
0 commit comments