@@ -349,6 +349,7 @@ def __init__(self):
349
349
self .use_vendored_sources = ''
350
350
self .verbose = False
351
351
self .git_version = None
352
+ self .nix_deps_dir = None
352
353
353
354
def download_stage0 (self ):
354
355
"""Fetch the build system for Rust, written in Rust
@@ -440,8 +441,7 @@ def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
440
441
get ("{}/{}" .format (url , filename ), tarball , verbose = self .verbose )
441
442
unpack (tarball , tarball_suffix , self .bin_root (), match = pattern , verbose = self .verbose )
442
443
443
- @staticmethod
444
- def fix_executable (fname ):
444
+ def fix_executable (self , fname ):
445
445
"""Modifies the interpreter section of 'fname' to fix the dynamic linker
446
446
447
447
This method is only required on NixOS and uses the PatchELF utility to
@@ -472,38 +472,49 @@ def fix_executable(fname):
472
472
nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
473
473
print (nix_os_msg , fname )
474
474
475
- try :
476
- interpreter = subprocess .check_output (
477
- ["patchelf" , "--print-interpreter" , fname ])
478
- interpreter = interpreter .strip ().decode (default_encoding )
479
- except subprocess .CalledProcessError as reason :
480
- print ("warning: failed to call patchelf:" , reason )
481
- return
482
-
483
- loader = interpreter .split ("/" )[- 1 ]
484
-
485
- try :
486
- ldd_output = subprocess .check_output (
487
- ['ldd' , '/run/current-system/sw/bin/sh' ])
488
- ldd_output = ldd_output .strip ().decode (default_encoding )
489
- except subprocess .CalledProcessError as reason :
490
- print ("warning: unable to call ldd:" , reason )
491
- return
492
-
493
- for line in ldd_output .splitlines ():
494
- libname = line .split ()[0 ]
495
- if libname .endswith (loader ):
496
- loader_path = libname [:len (libname ) - len (loader )]
497
- break
498
- else :
499
- print ("warning: unable to find the path to the dynamic linker" )
500
- return
501
-
502
- correct_interpreter = loader_path + loader
475
+ # Only build `stage0/.nix-deps` once.
476
+ nix_deps_dir = self .nix_deps_dir
477
+ if not nix_deps_dir :
478
+ nix_deps_dir = "{}/.nix-deps" .format (self .bin_root ())
479
+ if not os .path .exists (nix_deps_dir ):
480
+ os .makedirs (nix_deps_dir )
481
+
482
+ nix_deps = [
483
+ # Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
484
+ "stdenv.cc.bintools" ,
485
+
486
+ # Needed for patching ELF binaries (see doc comment above).
487
+ "patchelf" ,
488
+ ]
489
+
490
+ # Run `nix-build` to "build" each dependency (which will likely reuse
491
+ # the existing `/nix/store` copy, or at most download a pre-built copy).
492
+ # Importantly, we don't rely on `nix-build` printing the `/nix/store`
493
+ # path on stdout, but use `-o` to symlink it into `stage0/.nix-deps/$dep`,
494
+ # ensuring garbage collection will never remove the `/nix/store` path
495
+ # (which would break our patched binaries that hardcode those paths).
496
+ for dep in nix_deps :
497
+ try :
498
+ subprocess .check_output ([
499
+ "nix-build" , "<nixpkgs>" ,
500
+ "-A" , dep ,
501
+ "-o" , "{}/{}" .format (nix_deps_dir , dep ),
502
+ ])
503
+ except subprocess .CalledProcessError as reason :
504
+ print ("warning: failed to call nix-build:" , reason )
505
+ return
506
+
507
+ self .nix_deps_dir = nix_deps_dir
508
+
509
+ patchelf = "{}/patchelf/bin/patchelf" .format (nix_deps_dir )
510
+ bintools_dir = "{}/stdenv.cc.bintools" .format (nix_deps_dir )
511
+
512
+ with open ("{}/nix-support/dynamic-linker" .format (bintools_dir )) as dynamic_linker :
513
+ interpreter = dynamic_linker .read ().rstrip ()
503
514
504
515
try :
505
516
subprocess .check_output (
506
- [" patchelf" , "--set-interpreter" , correct_interpreter , fname ])
517
+ [patchelf , "--set-interpreter" , interpreter , fname ])
507
518
except subprocess .CalledProcessError as reason :
508
519
print ("warning: failed to call patchelf:" , reason )
509
520
return
0 commit comments