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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

95 lines
3.4 KiB
Nix
Raw Normal View History

{ runCommandLocal, nix, lib }:
# 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.
{ drv, oldDependency, newDependency, verbose ? true }:
let
inherit (lib)
any
attrNames
concatStringsSep
elem
filter
filterAttrs
listToAttrs
mapAttrsToList
stringLength
substring
;
2016-10-27 22:59:56 +00:00
warn = if verbose then builtins.trace else (x: y: y);
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;
discard = builtins.unsafeDiscardStringContext;
oldStorepath = builtins.storePath (discard (toString oldDependency));
referencesOf = drv: references.${discard (toString drv)};
Exponentially reduce computation time needed for replace-dependency by using memoization. This patch makes two changes. (1) It memoizes the computation of dependsOnOld. (2) It replaces rewrittenDerivations with a similar memoized table rewriteMemo. This prevents the entire tree of run-time dependencies from being traversed and instead only traverses the graph of run-time dependencies. In the case of deep dependency changes (such as changing one's bash version for an entire NixOS system) this can lead to an exponential speedup in processing time because shared dependencies are no longer traversed multiple times. This patch isn't quite derivation-per-derivation equivalent to the original computation. There are two immaterial differences. (1) The previous version would always call upon sed to replace oldDependency with newDependency even when the store object being updated doesn't directly depend on oldDependency. The new version only replaceds oldDependency with newDependency when the store object being updated actually directly depends on oldDependency (which means there is actually a hash to replace). (2) The previous version would list the old store object as a source input of the new store object, *except* for the root derivation being updated. Because the root derivation being updated has its actual derivation avaiable the previous verions would make the updated root derivation depend on the old derivation as a derivation input instead of a source input. The new version always lists the old store object as a source input, including the root derivation.
2014-09-28 13:39:39 +00:00
dependsOnOldMemo = listToAttrs (map
(drv: { name = discard (toString drv);
value = elem oldStorepath (referencesOf drv) ||
any dependsOnOld (referencesOf drv);
}) (attrNames references));
Exponentially reduce computation time needed for replace-dependency by using memoization. This patch makes two changes. (1) It memoizes the computation of dependsOnOld. (2) It replaces rewrittenDerivations with a similar memoized table rewriteMemo. This prevents the entire tree of run-time dependencies from being traversed and instead only traverses the graph of run-time dependencies. In the case of deep dependency changes (such as changing one's bash version for an entire NixOS system) this can lead to an exponential speedup in processing time because shared dependencies are no longer traversed multiple times. This patch isn't quite derivation-per-derivation equivalent to the original computation. There are two immaterial differences. (1) The previous version would always call upon sed to replace oldDependency with newDependency even when the store object being updated doesn't directly depend on oldDependency. The new version only replaceds oldDependency with newDependency when the store object being updated actually directly depends on oldDependency (which means there is actually a hash to replace). (2) The previous version would list the old store object as a source input of the new store object, *except* for the root derivation being updated. Because the root derivation being updated has its actual derivation avaiable the previous verions would make the updated root derivation depend on the old derivation as a derivation input instead of a source input. The new version always lists the old store object as a source input, including the root derivation.
2014-09-28 13:39:39 +00:00
dependsOnOld = drv: dependsOnOldMemo.${discard (toString drv)};
drvName = drv:
discard (substring 33 (stringLength (builtins.baseNameOf drv)) (builtins.baseNameOf drv));
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
'';
rewrittenDeps = listToAttrs [ {name = discard (toString oldDependency); value = newDependency;} ];
Exponentially reduce computation time needed for replace-dependency by using memoization. This patch makes two changes. (1) It memoizes the computation of dependsOnOld. (2) It replaces rewrittenDerivations with a similar memoized table rewriteMemo. This prevents the entire tree of run-time dependencies from being traversed and instead only traverses the graph of run-time dependencies. In the case of deep dependency changes (such as changing one's bash version for an entire NixOS system) this can lead to an exponential speedup in processing time because shared dependencies are no longer traversed multiple times. This patch isn't quite derivation-per-derivation equivalent to the original computation. There are two immaterial differences. (1) The previous version would always call upon sed to replace oldDependency with newDependency even when the store object being updated doesn't directly depend on oldDependency. The new version only replaceds oldDependency with newDependency when the store object being updated actually directly depends on oldDependency (which means there is actually a hash to replace). (2) The previous version would list the old store object as a source input of the new store object, *except* for the root derivation being updated. Because the root derivation being updated has its actual derivation avaiable the previous verions would make the updated root derivation depend on the old derivation as a derivation input instead of a source input. The new version always lists the old store object as a source input, including the root derivation.
2014-09-28 13:39:39 +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);
Exponentially reduce computation time needed for replace-dependency by using memoization. This patch makes two changes. (1) It memoizes the computation of dependsOnOld. (2) It replaces rewrittenDerivations with a similar memoized table rewriteMemo. This prevents the entire tree of run-time dependencies from being traversed and instead only traverses the graph of run-time dependencies. In the case of deep dependency changes (such as changing one's bash version for an entire NixOS system) this can lead to an exponential speedup in processing time because shared dependencies are no longer traversed multiple times. This patch isn't quite derivation-per-derivation equivalent to the original computation. There are two immaterial differences. (1) The previous version would always call upon sed to replace oldDependency with newDependency even when the store object being updated doesn't directly depend on oldDependency. The new version only replaceds oldDependency with newDependency when the store object being updated actually directly depends on oldDependency (which means there is actually a hash to replace). (2) The previous version would list the old store object as a source input of the new store object, *except* for the root derivation being updated. Because the root derivation being updated has its actual derivation avaiable the previous verions would make the updated root derivation depend on the old derivation as a derivation input instead of a source input. The new version always lists the old store object as a source input, including the root derivation.
2014-09-28 13:39:39 +00:00
})
(filter dependsOnOld (attrNames references))) // rewrittenDeps;
drvHash = discard (toString drv);
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)