biome: 1.9.2 -> 1.9.3
[NixPkgs.git] / pkgs / build-support / replace-dependencies.nix
blobfe325b175fe7bc99ac66c837d3e7877ca02c4328
2   lib,
3   runCommandLocal,
4   replaceDirectDependencies,
5 }:
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.
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.
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.
36   drv,
37   replacements,
38   cutoffPackages ? [ ],
39   verbose ? true,
42 let
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;
56   toContextlessString = x: unsafeDiscardStringContext (toString x);
57   warn = if verbose then lib.warn else (x: y: y);
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;
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     );
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     );
193 rewriteMemo.${realisation drv}