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)