at 24.11-pre 94 lines 3.4 kB view raw
1{ runCommandLocal, nix, lib }: 2 3# Replace a single dependency in the requisites tree of drv, propagating 4# the change all the way up the tree, without a full rebuild. This can be 5# useful, for example, to patch a security hole in libc and still use your 6# system safely without rebuilding the world. This should be a short term 7# solution, as soon as a rebuild can be done the properly rebuild derivation 8# should be used. The old dependency and new dependency MUST have the same-length 9# name, and ideally should have close-to-identical directory layout. 10# 11# Example: safeFirefox = replaceDependency { 12# drv = firefox; 13# oldDependency = glibc; 14# newDependency = overrideDerivation glibc (attrs: { 15# patches = attrs.patches ++ [ ./fix-glibc-hole.patch ]; 16# }); 17# }; 18# This will rebuild glibc with your security patch, then copy over firefox 19# (and all of its dependencies) without rebuilding further. 20{ drv, oldDependency, newDependency, verbose ? true }: 21 22let 23 inherit (lib) 24 any 25 attrNames 26 concatStringsSep 27 elem 28 filter 29 filterAttrs 30 listToAttrs 31 mapAttrsToList 32 stringLength 33 substring 34 ; 35 36 warn = if verbose then builtins.trace else (x: y: y); 37 references = import (runCommandLocal "references.nix" { exportReferencesGraph = [ "graph" drv ]; } '' 38 (echo { 39 while read path 40 do 41 echo " \"$path\" = [" 42 read count 43 read count 44 while [ "0" != "$count" ] 45 do 46 read ref_path 47 if [ "$ref_path" != "$path" ] 48 then 49 echo " (builtins.storePath (/. + \"$ref_path\"))" 50 fi 51 count=$(($count - 1)) 52 done 53 echo " ];" 54 done < graph 55 echo }) > $out 56 '').outPath; 57 58 discard = builtins.unsafeDiscardStringContext; 59 60 oldStorepath = builtins.storePath (discard (toString oldDependency)); 61 62 referencesOf = drv: references.${discard (toString drv)}; 63 64 dependsOnOldMemo = listToAttrs (map 65 (drv: { name = discard (toString drv); 66 value = elem oldStorepath (referencesOf drv) || 67 any dependsOnOld (referencesOf drv); 68 }) (attrNames references)); 69 70 dependsOnOld = drv: dependsOnOldMemo.${discard (toString drv)}; 71 72 drvName = drv: 73 discard (substring 33 (stringLength (builtins.baseNameOf drv)) (builtins.baseNameOf drv)); 74 75 rewriteHashes = drv: hashes: runCommandLocal (drvName drv) { nixStore = "${nix.out}/bin/nix-store"; } '' 76 $nixStore --dump ${drv} | sed 's|${baseNameOf drv}|'$(basename $out)'|g' | sed -e ${ 77 concatStringsSep " -e " (mapAttrsToList (name: value: 78 "'s|${baseNameOf name}|${baseNameOf value}|g'" 79 ) hashes) 80 } | $nixStore --restore $out 81 ''; 82 83 rewrittenDeps = listToAttrs [ {name = discard (toString oldDependency); value = newDependency;} ]; 84 85 rewriteMemo = listToAttrs (map 86 (drv: { name = discard (toString drv); 87 value = rewriteHashes (builtins.storePath drv) 88 (filterAttrs (n: v: elem (builtins.storePath (discard (toString n))) (referencesOf drv)) rewriteMemo); 89 }) 90 (filter dependsOnOld (attrNames references))) // rewrittenDeps; 91 92 drvHash = discard (toString drv); 93in assert (stringLength (drvName (toString oldDependency)) == stringLength (drvName (toString newDependency))); 94rewriteMemo.${drvHash} or (warn "replace-dependency.nix: Derivation ${drvHash} does not depend on ${discard (toString oldDependency)}" drv)