1{ runCommand, 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 }:
21
22with lib;
23
24let
25 references = import (runCommand "references.nix" { exportReferencesGraph = [ "graph" drv ]; } ''
26 (echo {
27 while read path
28 do
29 echo " \"$path\" = ["
30 read count
31 read count
32 while [ "0" != "$count" ]
33 do
34 read ref_path
35 if [ "$ref_path" != "$path" ]
36 then
37 echo " (builtins.storePath $ref_path)"
38 fi
39 count=$(($count - 1))
40 done
41 echo " ];"
42 done < graph
43 echo }) > $out
44 '').outPath;
45
46 discard = builtins.unsafeDiscardStringContext;
47
48 oldStorepath = builtins.storePath (discard (toString oldDependency));
49
50 referencesOf = drv: getAttr (discard (toString drv)) references;
51
52 dependsOnOldMemo = listToAttrs (map
53 (drv: { name = discard (toString drv);
54 value = elem oldStorepath (referencesOf drv) ||
55 any dependsOnOld (referencesOf drv);
56 }) (builtins.attrNames references));
57
58 dependsOnOld = drv: getAttr (discard (toString drv)) dependsOnOldMemo;
59
60 drvName = drv:
61 discard (substring 33 (stringLength (builtins.baseNameOf drv)) (builtins.baseNameOf drv));
62
63 rewriteHashes = drv: hashes: runCommand (drvName drv) { nixStore = "${nix}/bin/nix-store"; } ''
64 $nixStore --dump ${drv} | sed 's|${baseNameOf drv}|'$(basename $out)'|g' | sed -e ${
65 concatStringsSep " -e " (mapAttrsToList (name: value:
66 "'s|${baseNameOf name}|${baseNameOf value}|g'"
67 ) hashes)
68 } | $nixStore --restore $out
69 '';
70
71 rewrittenDeps = listToAttrs [ {name = discard (toString oldDependency); value = newDependency;} ];
72
73 rewriteMemo = listToAttrs (map
74 (drv: { name = discard (toString drv);
75 value = rewriteHashes (builtins.storePath drv)
76 (filterAttrs (n: v: builtins.elem (builtins.storePath (discard (toString n))) (referencesOf drv)) rewriteMemo);
77 })
78 (filter dependsOnOld (builtins.attrNames references))) // rewrittenDeps;
79
80in assert (stringLength (drvName (toString oldDependency)) == stringLength (drvName (toString newDependency)));
81getAttr (discard (toString drv)) rewriteMemo