@@ -349,6 +349,7 @@ def __init__(self):
349349 self .use_vendored_sources = ''
350350 self .verbose = False
351351 self .git_version = None
352+ self .nix_deps_dir = None
352353
353354 def download_stage0 (self ):
354355 """Fetch the build system for Rust, written in Rust
@@ -440,8 +441,7 @@ def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
440441 get ("{}/{}" .format (url , filename ), tarball , verbose = self .verbose )
441442 unpack (tarball , tarball_suffix , self .bin_root (), match = pattern , verbose = self .verbose )
442443
443- @staticmethod
444- def fix_executable (fname ):
444+ def fix_executable (self , fname ):
445445 """Modifies the interpreter section of 'fname' to fix the dynamic linker
446446
447447 This method is only required on NixOS and uses the PatchELF utility to
@@ -472,38 +472,49 @@ def fix_executable(fname):
472472 nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
473473 print (nix_os_msg , fname )
474474
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 ()
503514
504515 try :
505516 subprocess .check_output (
506- [" patchelf" , "--set-interpreter" , correct_interpreter , fname ])
517+ [patchelf , "--set-interpreter" , interpreter , fname ])
507518 except subprocess .CalledProcessError as reason :
508519 print ("warning: failed to call patchelf:" , reason )
509520 return
0 commit comments