core/pkgs/build-support/replace-dependency.nix

130 lines
3.6 KiB
Nix
Raw Normal View History

2024-06-30 08:12:46 +00:00
{
runCommandLocal,
nix,
lib,
}:
2024-05-02 00:46:19 +00:00
# Replace a single dependency in the requisites tree of drv, propagating
# the change all the way up the tree, without a full rebuild. This can be
# useful, for example, to patch a security hole in libc and still use your
# system safely without rebuilding the world. This should be a short term
# solution, as soon as a rebuild can be done the properly rebuild derivation
# should be used. The old dependency and new dependency MUST have the same-length
# name, and ideally should have close-to-identical directory layout.
#
# Example: safeFirefox = replaceDependency {
# drv = firefox;
# oldDependency = glibc;
# newDependency = overrideDerivation glibc (attrs: {
# patches = attrs.patches ++ [ ./fix-glibc-hole.patch ];
# });
# };
# This will rebuild glibc with your security patch, then copy over firefox
# (and all of its dependencies) without rebuilding further.
2024-06-30 08:12:46 +00:00
{
drv,
oldDependency,
newDependency,
verbose ? true,
}:
2024-05-02 00:46:19 +00:00
let
inherit (lib)
any
attrNames
concatStringsSep
elem
filter
filterAttrs
listToAttrs
mapAttrsToList
stringLength
substring
;
warn = if verbose then builtins.trace else (x: y: y);
2024-06-30 08:12:46 +00:00
references =
import
(runCommandLocal "references.nix"
{
exportReferencesGraph = [
"graph"
drv
];
}
''
(echo {
while read path
do
echo " \"$path\" = ["
read count
read count
while [ "0" != "$count" ]
do
read ref_path
if [ "$ref_path" != "$path" ]
then
echo " (builtins.storePath (/. + \"$ref_path\"))"
fi
count=$(($count - 1))
done
echo " ];"
done < graph
echo }) > $out
''
).outPath;
2024-05-02 00:46:19 +00:00
discard = builtins.unsafeDiscardStringContext;
oldStorepath = builtins.storePath (discard (toString oldDependency));
referencesOf = drv: references.${discard (toString drv)};
2024-06-30 08:12:46 +00:00
dependsOnOldMemo = listToAttrs (
map (drv: {
name = discard (toString drv);
value = elem oldStorepath (referencesOf drv) || any dependsOnOld (referencesOf drv);
}) (attrNames references)
);
2024-05-02 00:46:19 +00:00
dependsOnOld = drv: dependsOnOldMemo.${discard (toString drv)};
2024-06-30 08:12:46 +00:00
drvName =
drv: discard (substring 33 (stringLength (builtins.baseNameOf drv)) (builtins.baseNameOf drv));
2024-05-02 00:46:19 +00:00
2024-06-30 08:12:46 +00:00
rewriteHashes =
drv: hashes:
runCommandLocal (drvName drv) { nixStore = "${nix.out}/bin/nix-store"; } ''
$nixStore --dump ${drv} | sed 's|${baseNameOf drv}|'$(basename $out)'|g' | sed -e ${
concatStringsSep " -e " (
mapAttrsToList (name: value: "'s|${baseNameOf name}|${baseNameOf value}|g'") hashes
)
} | $nixStore --restore $out
'';
2024-05-02 00:46:19 +00:00
2024-06-30 08:12:46 +00:00
rewrittenDeps = listToAttrs [
{
name = discard (toString oldDependency);
value = newDependency;
}
];
2024-05-02 00:46:19 +00:00
2024-06-30 08:12:46 +00:00
rewriteMemo =
listToAttrs (
map (drv: {
name = discard (toString drv);
value = rewriteHashes (builtins.storePath drv) (
filterAttrs (n: v: elem (builtins.storePath (discard (toString n))) (referencesOf drv)) rewriteMemo
);
}) (filter dependsOnOld (attrNames references))
)
// rewrittenDeps;
2024-05-02 00:46:19 +00:00
drvHash = discard (toString drv);
2024-06-30 08:12:46 +00:00
in
assert (
stringLength (drvName (toString oldDependency)) == stringLength (drvName (toString newDependency))
);
rewriteMemo.${drvHash}
or (warn "replace-dependency.nix: Derivation ${drvHash} does not depend on ${discard (toString oldDependency)}" drv)