diff options
Diffstat (limited to 'nixpkgs/pkgs/build-support/replace-dependency.nix')
-rw-r--r-- | nixpkgs/pkgs/build-support/replace-dependency.nix | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/replace-dependency.nix b/nixpkgs/pkgs/build-support/replace-dependency.nix new file mode 100644 index 000000000000..7912d21bfd69 --- /dev/null +++ b/nixpkgs/pkgs/build-support/replace-dependency.nix @@ -0,0 +1,94 @@ +{ 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 + ; + + 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)}; + + dependsOnOldMemo = listToAttrs (map + (drv: { name = discard (toString drv); + value = elem oldStorepath (referencesOf drv) || + any dependsOnOld (referencesOf drv); + }) (attrNames references)); + + 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;} ]; + + 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; + + 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) |