summary refs log tree commit diff
path: root/pkgs/build-support/replace-dependency.nix
diff options
context:
space:
mode:
authorShea Levy <shea@shealevy.com>2013-01-11 14:42:09 -0500
committerShea Levy <shea@shealevy.com>2013-01-11 14:42:09 -0500
commitd1662d715514e6ef9d3dc29f132f1b3d8e608a18 (patch)
tree766b0b4d303fda96f7c9ba0d6381fb0048f5c4e1 /pkgs/build-support/replace-dependency.nix
parent2dd59fc4cf74d62d47de613512213c63cf6400b6 (diff)
downloadnixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar.gz
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar.bz2
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar.lz
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar.xz
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.tar.zst
nixpkgs-d1662d715514e6ef9d3dc29f132f1b3d8e608a18.zip
Add the replace-dependency build support function.
The use case is to do a deep replacement of a dependency without rebuilding the entire tree.
For example, suppose a security hole is found in glibc and a patch released. Ideally, you'd
just rebuild everything, but that takes time, space, and CPU that you might not have, so in
the mean time you could build a safe version of, say, firefox with:

  firefox-safe = replace-dependency { drv = firefox; old-dependency = glibc; new-dependency = patched-glibc; };

Building firefox-safe will rebuild glibc, but only do a simple copy/string replacement on all other dependencies
of firefox. On my system (MBP 13" mid-2012), after a new glibc had been build building firefox took around 11 seconds.

See the comments in the file for more details.
Diffstat (limited to 'pkgs/build-support/replace-dependency.nix')
-rw-r--r--pkgs/build-support/replace-dependency.nix80
1 files changed, 80 insertions, 0 deletions
diff --git a/pkgs/build-support/replace-dependency.nix b/pkgs/build-support/replace-dependency.nix
new file mode 100644
index 00000000000..364d5548151
--- /dev/null
+++ b/pkgs/build-support/replace-dependency.nix
@@ -0,0 +1,80 @@
+{ runCommand, 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: safe-firefox = replace-dependency {
+#   drv = firefox;
+#   old-dependency = glibc;
+#   new-dependency = 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, old-dependency, new-dependency }:
+
+with lib;
+
+let
+  references = import (runCommand "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;
+
+  old-storepath = builtins.storePath (discard old-dependency.outPath);
+
+  references-of = drv: getAttr (discard (toString drv)) references;
+
+  depends-on-old = drv: elem old-storepath (references-of drv) ||
+    any depends-on-old (references-of drv);
+
+  drv-name = drv:
+    discard (substring 33 (stringLength (builtins.baseNameOf drv)) (builtins.baseNameOf drv));
+
+  replace-strings = drv: rewritten-drvs: runCommand (drv-name drv) { 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'"
+      ) rewritten-drvs)
+    } | $nixStore --restore $out
+  '';
+
+  fn = { drv, rewritten-drvs }: rewritten-drvs // (
+    if depends-on-old drv
+      then listToAttrs [ {
+        name = discard (toString drv);
+
+        value = replace-strings drv (rewritten-drvs // (fold (drv: acc:
+          (fn { inherit drv rewritten-drvs; }) // acc
+        ) {} (references-of drv)));
+      } ]
+      else {}
+  );
+in assert (stringLength old-dependency.name == stringLength new-dependency.name); getAttr (discard drv.outPath) (fn {
+  inherit drv;
+  rewritten-drvs = listToAttrs [ {name = discard old-dependency.outPath; value = new-dependency;} ];
+})