4 replaceDirectDependencies,
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 {
16 # oldDependency = glibc;
17 # newDependency = glibc.overrideAttrs (oldAttrs: {
18 # patches = oldAttrs.patches ++ [ ./fix-glibc-hole.patch ];
22 # oldDependency = libwebp;
23 # newDependency = libwebp.overrideAttrs (oldAttrs: {
24 # patches = oldAttrs.patches ++ [ ./fix-libwebp-hole.patch ];
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.
43 inherit (builtins) unsafeDiscardStringContext appendContext;
54 inherit (lib.attrsets) mergeAttrsList;
56 toContextlessString = x: unsafeDiscardStringContext (toString x);
57 warn = if verbose then lib.warn else (x: y: y);
62 (runCommandLocal "references.nix"
64 exportReferencesGraph = [
76 while [ "0" != "$count" ]
79 if [ "$ref_path" != "$path" ]
93 if isStorePath drv then
94 # Input-addressed and fixed-output derivations have their realisation as outPath.
95 toContextlessString drv
97 # Floating and deferred derivations have a placeholder outPath.
98 # The realisation can only be obtained by performing an actual build.
99 unsafeDiscardStringContext (
101 runCommandLocal "realisation"
108 echo -n "$drv" > $out
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.
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.
129 targetDerivations = [ drv ] ++ map ({ newDependency, ... }: newDependency) relevantReplacements;
130 referencesMemo = listToAttrs (
132 name = realisation drv;
133 value = referencesOf drv;
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 =
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; }
149 name = realisation drv;
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.
161 rewrittenReferences = filter (dep: dep != drv && toString rewriteMemo.${dep} != dep) references;
162 rewrites = listToAttrs (
165 value = rewriteMemo.${reference};
166 }) rewrittenReferences
169 replaceDirectDependencies {
170 drv = storePathOrKnownTargetDerivationMemo.${drv};
171 replacements = mapAttrsToList (name: value: {
172 oldDependency = name;
173 newDependency = value;
179 name = realisation drv;
180 value = storePathOrKnownTargetDerivationMemo.${realisation drv};
185 { oldDependency, newDependency }:
187 name = realisation oldDependency;
188 value = rewriteMemo.${realisation newDependency};
190 ) relevantReplacements
193 rewriteMemo.${realisation drv}