Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1{
2 lib,
3 runCommandLocal,
4 replaceDirectDependencies,
5}:
6
7# Replace some dependencies in the requisites tree of drv, propagating the change all the way up the tree, even within other replacements, without a full rebuild.
8# This can be useful, for example, to patch a security hole in libc and still use your system safely without rebuilding the world.
9# This should be a short term solution, as soon as a rebuild can be done the properly rebuilt derivation should be used.
10# Each old dependency and the corresponding new dependency MUST have the same-length name, and ideally should have close-to-identical directory layout.
11#
12# Example: safeFirefox = replaceDependencies {
13# drv = firefox;
14# replacements = [
15# {
16# oldDependency = glibc;
17# newDependency = glibc.overrideAttrs (oldAttrs: {
18# patches = oldAttrs.patches ++ [ ./fix-glibc-hole.patch ];
19# });
20# }
21# {
22# oldDependency = libwebp;
23# newDependency = libwebp.overrideAttrs (oldAttrs: {
24# patches = oldAttrs.patches ++ [ ./fix-libwebp-hole.patch ];
25# });
26# }
27# ];
28# };
29# This will first rebuild glibc and libwebp with your security patches.
30# Then it copies over firefox (and all of its dependencies) without rebuilding further.
31# In particular, the glibc dependency of libwebp will be replaced by the patched version as well.
32#
33# In rare cases, it is possible for the replacement process to cause breakage (for example due to checksum mismatch).
34# The cutoffPackages argument can be used to exempt the problematic packages from the replacement process.
35{
36 drv,
37 replacements,
38 cutoffPackages ? [ ],
39 verbose ? true,
40}:
41
42let
43 inherit (builtins) unsafeDiscardStringContext appendContext;
44 inherit (lib)
45 listToAttrs
46 isStorePath
47 readFile
48 attrValues
49 mapAttrs
50 filter
51 hasAttr
52 mapAttrsToList
53 ;
54 inherit (lib.attrsets) mergeAttrsList;
55
56 toContextlessString = x: unsafeDiscardStringContext (toString x);
57 warn = if verbose then lib.warn else (x: y: y);
58
59 referencesOf =
60 drv:
61 import
62 (runCommandLocal "references.nix"
63 {
64 exportReferencesGraph = [
65 "graph"
66 drv
67 ];
68 }
69 ''
70 (echo {
71 while read path
72 do
73 echo " \"$path\" = ["
74 read count
75 read count
76 while [ "0" != "$count" ]
77 do
78 read ref_path
79 if [ "$ref_path" != "$path" ]
80 then
81 echo " \"$ref_path\""
82 fi
83 count=$(($count - 1))
84 done
85 echo " ];"
86 done < graph
87 echo }) > $out
88 ''
89 ).outPath;
90
91 realisation =
92 drv:
93 if isStorePath drv then
94 # Input-addressed and fixed-output derivations have their realisation as outPath.
95 toContextlessString drv
96 else
97 # Floating and deferred derivations have a placeholder outPath.
98 # The realisation can only be obtained by performing an actual build.
99 unsafeDiscardStringContext (
100 readFile (
101 runCommandLocal "realisation"
102 {
103 env = {
104 inherit drv;
105 };
106 }
107 ''
108 echo -n "$drv" > $out
109 ''
110 )
111 );
112 rootReferences = referencesOf drv;
113 relevantReplacements = filter (
114 { oldDependency, newDependency }:
115 if toString oldDependency == toString newDependency then
116 warn "replaceDependencies: attempting to replace dependency ${oldDependency} of ${drv} with itself"
117 # Attempting to replace a dependency by itself is completely useless, and would only lead to infinite recursion.
118 # Hence it must not be attempted to apply this replacement in any case.
119 false
120 else if !hasAttr (realisation oldDependency) rootReferences then
121 warn "replaceDependencies: ${drv} does not depend on ${oldDependency}, so it will not be replaced"
122 # Strictly speaking, another replacement could introduce the dependency.
123 # However, handling this corner case would add significant complexity.
124 # So we just leave it to the user to apply the replacement at the correct place, but show a warning to let them know.
125 false
126 else
127 true
128 ) replacements;
129 targetDerivations = [ drv ] ++ map ({ newDependency, ... }: newDependency) relevantReplacements;
130 referencesMemo = listToAttrs (
131 map (drv: {
132 name = realisation drv;
133 value = referencesOf drv;
134 }) targetDerivations
135 );
136 relevantReferences = mergeAttrsList (attrValues referencesMemo);
137 # Make sure a derivation is returned even when no replacements are actually applied.
138 # Yes, even in the stupid edge case where the root derivation itself is replaced.
139 storePathOrKnownTargetDerivationMemo =
140 mapAttrs (
141 drv: _references:
142 # builtins.storePath does not work in pure evaluation mode, even though it is not impure.
143 # This reimplementation in Nix works as long as the path is already allowed in the evaluation state.
144 # This is always the case here, because all paths come from the closure of the original derivation.
145 appendContext drv { ${drv}.path = true; }
146 ) relevantReferences
147 // listToAttrs (
148 map (drv: {
149 name = realisation drv;
150 value = drv;
151 }) targetDerivations
152 );
153
154 rewriteMemo =
155 # Mind the order of how the three attrsets are merged here.
156 # The order of precedence needs to be "explicitly specified replacements" > "rewrite exclusion (cutoffPackages)" > "rewrite".
157 # So the attrset merge order is the opposite.
158 mapAttrs (
159 drv: references:
160 let
161 rewrittenReferences = filter (dep: dep != drv && toString rewriteMemo.${dep} != dep) references;
162 rewrites = listToAttrs (
163 map (reference: {
164 name = reference;
165 value = rewriteMemo.${reference};
166 }) rewrittenReferences
167 );
168 in
169 replaceDirectDependencies {
170 drv = storePathOrKnownTargetDerivationMemo.${drv};
171 replacements = mapAttrsToList (name: value: {
172 oldDependency = name;
173 newDependency = value;
174 }) rewrites;
175 }
176 ) relevantReferences
177 // listToAttrs (
178 map (drv: {
179 name = realisation drv;
180 value = storePathOrKnownTargetDerivationMemo.${realisation drv};
181 }) cutoffPackages
182 )
183 // listToAttrs (
184 map (
185 { oldDependency, newDependency }:
186 {
187 name = realisation oldDependency;
188 value = rewriteMemo.${realisation newDependency};
189 }
190 ) relevantReplacements
191 );
192in
193rewriteMemo.${realisation drv}