Advanced Topics
+
diff --git a/doc/manual/advanced-topics/diff-hook.xml b/doc/manual/advanced-topics/diff-hook.xml
new file mode 100644
index 000000000..d2613f6df
--- /dev/null
+++ b/doc/manual/advanced-topics/diff-hook.xml
@@ -0,0 +1,207 @@
+
+
+Verifying Build Reproducibility with
+
+Check build reproducibility by running builds multiple times
+and comparing their results.
+
+Specify a program with Nix's to
+compare build results when two builds produce different results. Note:
+this hook is only executed if the results are not the same, this hook
+is not used for determining if the results are the same.
+
+For purposes of demonstration, we'll use the following Nix file,
+deterministic.nix for testing:
+
+
+let
+ inherit (import <nixpkgs> {}) runCommand;
+in {
+ stable = runCommand "stable" {} ''
+ touch $out
+ '';
+
+ unstable = runCommand "unstable" {} ''
+ echo $RANDOM > $out
+ '';
+}
+
+
+Additionally, nix.conf contains:
+
+
+diff-hook = /etc/nix/my-diff-hook
+run-diff-hook = true
+
+
+where /etc/nix/my-diff-hook is an executable
+file containing:
+
+
+#!/bin/sh
+exec >&2
+echo "For derivation $3:"
+/run/current-system/sw/bin/runuser -u nobody -- /run/current-system/sw/bin/diff -r "$1" "$2"
+
+
+
+ The diff hook can be run as root. Take care to run as little
+ as possible as root, for this example we use runuser
+ to drop privileges.
+
+
+
+
+
+
+ Spot-Checking Build Determinism
+
+
+
+ Verify a path which already exists in the Nix store by passing
+ to the build command.
+
+
+ If the build passes and is deterministic, Nix will exit with a
+ status code of 0:
+
+
+$ nix-build ./deterministic.nix -A stable
+these derivations will be built:
+ /nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv
+building '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
+/nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
+
+$ nix-build ./deterministic.nix -A stable --check
+checking outputs of '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
+/nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
+
+
+ If the build is not deterministic, Nix will exit with a status
+ code of 1:
+
+
+$ nix-build ./deterministic.nix -A unstable
+these derivations will be built:
+ /nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv
+building '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable
+
+$ nix-build ./deterministic.nix -A unstable --check
+checking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs
+
+
+In the Nix daemon's log, we will now see:
+
+For derivation /nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv:
+1c1
+< 8108
+---
+> 30204
+
+
+
+ Using with
+ will cause Nix to keep the second build's output in a special,
+ .check path:
+
+
+$ nix-build ./deterministic.nix -A unstable --check --keep-failed
+checking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
+note: keeping build directory '/tmp/nix-build-unstable.drv-0'
+error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs from '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check'
+
+
+ In particular, notice the
+ /nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check
+ output. Nix has copied the build results to that directory where you
+ can examine it.
+
+
+ .check paths are not registered store paths
+
+ Check paths are not protected against garbage collection,
+ and this path will be deleted on the next garbage collection.
+
+ The path is guaranteed to be alive for the duration of
+ 's execution, but may be deleted
+ any time after.
+
+ If the comparison is performed as part of automated tooling,
+ please use the diff-hook or author your tooling to handle the case
+ where the build was not deterministic and also a check path does
+ not exist.
+
+
+
+ is only usable if the derivation has
+ been built on the system already. If the derivation has not been
+ built Nix will fail with the error:
+
+error: some outputs of '/nix/store/hzi1h60z2qf0nb85iwnpvrai3j2w7rr6-unstable.drv' are not valid, so checking is not possible
+
+
+ Run the build without , and then try with
+ again.
+
+
+
+
+
+ Automatic and Optionally Enforced Determinism Verification
+
+
+
+ Automatically verify every build at build time by executing the
+ build multiple times.
+
+
+
+ Setting and
+ in your
+ nix.conf permits the automated verification
+ of every build Nix performs.
+
+
+
+ The following configuration will run each build three times, and
+ will require the build to be deterministic:
+
+
+enforce-determinism = true
+repeat = 2
+
+
+
+
+ Setting to false as in
+ the following configuration will run the build multiple times,
+ execute the build hook, but will allow the build to succeed even
+ if it does not build reproducibly:
+
+
+enforce-determinism = false
+repeat = 1
+
+
+
+
+ An example output of this configuration:
+
+$ nix-build ./test.nix -A unstable
+these derivations will be built:
+ /nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv
+building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 1/2)...
+building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 2/2)...
+output '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable' of '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' differs from '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable.check' from previous round
+/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable
+
+
+
+
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index f0da1f612..a1a5d6e12 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -1,7 +1,9 @@
+
+ xml:id="sec-conf-file"
+ version="5">
nix.conf
@@ -240,6 +242,64 @@ false.
+ diff-hook
+
+
+ Absolute path to an executable capable of diffing build results.
+ The hook executes if is
+ true, and the output of a build is known to not be the same.
+ This program is not executed to determine if two results are the
+ same.
+
+
+
+
+ The root user executes the diff hook in a daemonised
+ installation. See for
+ information on using the diff hook safely.
+
+
+
+ The diff hook program receives three parameters:
+
+
+
+
+ A path to the previous build's results
+
+
+
+
+
+ A path to the current build's results
+
+
+
+
+
+ The path to the build's derivation
+
+
+
+
+ The diff hook should not print data to stderr or stdout, as
+ output is not displayed to the user. However, if information is
+ printed, it will be printed in the nix-daemon
+ log.
+
+ When using the Nix daemon, diff-hook must
+ be set in the nix.conf configuration file, and
+ cannot be passed at the command line.
+
+
+
+
+
+ enforce-determinism
+
+ See .
+
+
extra-sandbox-paths
@@ -595,9 +655,9 @@ password my-password
they are deterministic. The default value is 0. If the value is
non-zero, every build is repeated the specified number of
times. If the contents of any of the runs differs from the
- previous ones, the build is rejected and the resulting store paths
- are not registered as “valid” in Nix’s database.
-
+ previous ones and is
+ true, the build is rejected and the resulting store paths are not
+ registered as “valid” in Nix’s database.
require-sigs
@@ -628,6 +688,19 @@ password my-password
+ run-diff-hook
+
+
+ If true, enable the execution of .
+
+
+
+ When using the Nix daemon, run-diff-hook must
+ be set in the nix.conf configuration file,
+ and cannot be passed at the command line.
+
+
+ sandbox
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 91eb97dfb..026828c53 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -461,6 +461,19 @@ static void commonChildInit(Pipe & logPipe)
close(fdDevNull);
}
+void handleDiffHook(Path tryA, Path tryB, Path drvPath)
+{
+ auto diffHook = settings.diffHook;
+ if (diffHook != "" && settings.runDiffHook) {
+ try {
+ auto diff = runProgram(diffHook, true, {tryA, tryB, drvPath});
+ if (diff != "")
+ printError(chomp(diff));
+ } catch (Error & error) {
+ printError("diff hook execution failed: %s", error.what());
+ }
+ }
+}
//////////////////////////////////////////////////////////////////////
@@ -3039,8 +3052,7 @@ void DerivationGoal::registerOutputs()
InodesSeen inodesSeen;
Path checkSuffix = ".check";
- bool runDiffHook = settings.runDiffHook;
- bool keepPreviousRound = settings.keepFailed || runDiffHook;
+ bool keepPreviousRound = settings.keepFailed || settings.runDiffHook;
std::exception_ptr delayedException;
@@ -3185,11 +3197,14 @@ void DerivationGoal::registerOutputs()
if (!worker.store.isValidPath(path)) continue;
auto info = *worker.store.queryPathInfo(path);
if (hash.first != info.narHash) {
+ handleDiffHook(path, actualPath, drvPath);
+
if (settings.keepFailed) {
Path dst = worker.store.toRealPath(path + checkSuffix);
deletePath(dst);
if (rename(actualPath.c_str(), dst.c_str()))
throw SysError(format("renaming '%1%' to '%2%'") % actualPath % dst);
+
throw Error(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
% drvPath % path % dst);
} else
@@ -3254,16 +3269,7 @@ void DerivationGoal::registerOutputs()
? fmt("output '%1%' of '%2%' differs from '%3%' from previous round", i->second.path, drvPath, prev)
: fmt("output '%1%' of '%2%' differs from previous round", i->second.path, drvPath);
- auto diffHook = settings.diffHook;
- if (prevExists && diffHook != "" && runDiffHook) {
- try {
- auto diff = runProgram(diffHook, true, {prev, i->second.path});
- if (diff != "")
- printError(chomp(diff));
- } catch (Error & error) {
- printError("diff hook execution failed: %s", error.what());
- }
- }
+ handleDiffHook(prev, i->second.path, drvPath);
if (settings.enforceDeterminism)
throw NotDeterministic(msg);