mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-22 06:53:01 +00:00
187 lines
6.4 KiB
Nix
187 lines
6.4 KiB
Nix
|
{
|
||
|
runCommandLocal,
|
||
|
nix,
|
||
|
lib,
|
||
|
}:
|
||
|
|
||
|
# 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.
|
||
|
# 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 rebuilt derivation should be used.
|
||
|
# Each old dependency and the corresponding new dependency MUST have the same-length name, and ideally should have close-to-identical directory layout.
|
||
|
#
|
||
|
# Example: safeFirefox = replaceDependencies {
|
||
|
# drv = firefox;
|
||
|
# replacements = [
|
||
|
# {
|
||
|
# oldDependency = glibc;
|
||
|
# newDependency = glibc.overrideAttrs (oldAttrs: {
|
||
|
# patches = oldAttrs.patches ++ [ ./fix-glibc-hole.patch ];
|
||
|
# });
|
||
|
# }
|
||
|
# {
|
||
|
# oldDependency = libwebp;
|
||
|
# newDependency = libwebp.overrideAttrs (oldAttrs: {
|
||
|
# patches = oldAttrs.patches ++ [ ./fix-libwebp-hole.patch ];
|
||
|
# });
|
||
|
# }
|
||
|
# ];
|
||
|
# };
|
||
|
# This will first rebuild glibc and libwebp with your security patches.
|
||
|
# Then it copies over firefox (and all of its dependencies) without rebuilding further.
|
||
|
# In particular, the glibc dependency of libwebp will be replaced by the patched version as well.
|
||
|
#
|
||
|
# In rare cases, it is possible for the replacement process to cause breakage (for example due to checksum mismatch).
|
||
|
# The cutoffPackages argument can be used to exempt the problematic packages from the replacement process.
|
||
|
{
|
||
|
drv,
|
||
|
replacements,
|
||
|
cutoffPackages ? [ ],
|
||
|
verbose ? true,
|
||
|
}:
|
||
|
|
||
|
let
|
||
|
inherit (builtins) unsafeDiscardStringContext appendContext;
|
||
|
inherit (lib)
|
||
|
trace
|
||
|
substring
|
||
|
stringLength
|
||
|
concatStringsSep
|
||
|
mapAttrsToList
|
||
|
listToAttrs
|
||
|
attrValues
|
||
|
mapAttrs
|
||
|
filter
|
||
|
hasAttr
|
||
|
all
|
||
|
;
|
||
|
inherit (lib.attrsets) mergeAttrsList;
|
||
|
|
||
|
toContextlessString = x: unsafeDiscardStringContext (toString x);
|
||
|
warn = if verbose then trace else (x: y: y);
|
||
|
|
||
|
referencesOf =
|
||
|
drv:
|
||
|
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 " \"$ref_path\""
|
||
|
fi
|
||
|
count=$(($count - 1))
|
||
|
done
|
||
|
echo " ];"
|
||
|
done < graph
|
||
|
echo }) > $out
|
||
|
''
|
||
|
).outPath;
|
||
|
rewriteHashes =
|
||
|
drv: rewrites:
|
||
|
if rewrites == { } then
|
||
|
drv
|
||
|
else
|
||
|
let
|
||
|
drvName = substring 33 (stringLength (baseNameOf drv)) (baseNameOf drv);
|
||
|
in
|
||
|
runCommandLocal drvName { nixStore = "${nix}/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'") rewrites
|
||
|
)
|
||
|
} | $nixStore --restore $out
|
||
|
'';
|
||
|
|
||
|
knownDerivations = [ drv ] ++ map ({ newDependency, ... }: newDependency) replacements;
|
||
|
referencesMemo = listToAttrs (
|
||
|
map (drv: {
|
||
|
name = toContextlessString drv;
|
||
|
value = referencesOf drv;
|
||
|
}) knownDerivations
|
||
|
);
|
||
|
relevantReferences = mergeAttrsList (attrValues referencesMemo);
|
||
|
# Make sure a derivation is returned even when no replacements are actually applied.
|
||
|
# Yes, even in the stupid edge case where the root derivation itself is replaced.
|
||
|
storePathOrKnownDerivationMemo =
|
||
|
mapAttrs (
|
||
|
drv: _references:
|
||
|
# builtins.storePath does not work in pure evaluation mode, even though it is not impure.
|
||
|
# This reimplementation in Nix works as long as the path is already allowed in the evaluation state.
|
||
|
# This is always the case here, because all paths come from the closure of the original derivation.
|
||
|
appendContext drv { ${drv}.path = true; }
|
||
|
) relevantReferences
|
||
|
// listToAttrs (
|
||
|
map (drv: {
|
||
|
name = toContextlessString drv;
|
||
|
value = drv;
|
||
|
}) knownDerivations
|
||
|
);
|
||
|
|
||
|
relevantReplacements = filter (
|
||
|
{ oldDependency, newDependency }:
|
||
|
if toString oldDependency == toString newDependency then
|
||
|
warn "replaceDependencies: attempting to replace dependency ${oldDependency} of ${drv} with itself"
|
||
|
# Attempting to replace a dependency by itself is completely useless, and would only lead to infinite recursion.
|
||
|
# Hence it must not be attempted to apply this replacement in any case.
|
||
|
false
|
||
|
else if !hasAttr (toContextlessString oldDependency) referencesMemo.${toContextlessString drv} then
|
||
|
warn "replaceDependencies: ${drv} does not depend on ${oldDependency}"
|
||
|
# Handle the corner case where one of the other replacements introduces the dependency.
|
||
|
# It would be more correct to not show the warning in this case, but the added complexity is probably not worth it.
|
||
|
true
|
||
|
else
|
||
|
true
|
||
|
) replacements;
|
||
|
|
||
|
rewriteMemo =
|
||
|
# Mind the order of how the three attrsets are merged here.
|
||
|
# The order of precedence needs to be "explicitly specified replacements" > "rewrite exclusion (cutoffPackages)" > "rewrite".
|
||
|
# So the attrset merge order is the opposite.
|
||
|
mapAttrs (
|
||
|
drv: references:
|
||
|
let
|
||
|
rewrittenReferences = filter (dep: dep != drv && toString rewriteMemo.${dep} != dep) references;
|
||
|
rewrites = listToAttrs (
|
||
|
map (reference: {
|
||
|
name = reference;
|
||
|
value = rewriteMemo.${reference};
|
||
|
}) rewrittenReferences
|
||
|
);
|
||
|
in
|
||
|
rewriteHashes storePathOrKnownDerivationMemo.${drv} rewrites
|
||
|
) relevantReferences
|
||
|
// listToAttrs (
|
||
|
map (drv: {
|
||
|
name = toContextlessString drv;
|
||
|
value = storePathOrKnownDerivationMemo.${toContextlessString drv};
|
||
|
}) cutoffPackages
|
||
|
)
|
||
|
// listToAttrs (
|
||
|
map (
|
||
|
{ oldDependency, newDependency }:
|
||
|
{
|
||
|
name = toContextlessString oldDependency;
|
||
|
value = rewriteMemo.${toContextlessString newDependency};
|
||
|
}
|
||
|
) relevantReplacements
|
||
|
);
|
||
|
in
|
||
|
assert all (
|
||
|
{ oldDependency, newDependency }: stringLength oldDependency == stringLength newDependency
|
||
|
) replacements;
|
||
|
rewriteMemo.${toContextlessString drv}
|