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..fb4bf819f
--- /dev/null
+++ b/doc/manual/advanced-topics/diff-hook.xml
@@ -0,0 +1,205 @@
+
+
+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/diff -r "$1" "$2"
+
+
+
+
+The diff hook is executed by the same user and group who ran the
+build. However, the diff hook does not have write access to the store
+path just built.
+
+
+
+ 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..24fbf28cf 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,71 @@ 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 diff hook is executed by the same user and group who ran the
+ build. However, the diff hook does not have write access to the
+ store path just built.
+
+
+ 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 path to the build's scratch directory. This directory
+ will exist only if the build was run with
+ .
+
+
+
+
+
+ The stderr and stdout output from the diff hook will not be
+ displayed to the user. Instead, it will print to the nix-daemon's
+ 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 +662,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 +695,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/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh
index 4dd249923..fc999d336 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-closure.sh
@@ -26,7 +26,7 @@ fi
if [ "$(uname -s)" = "Darwin" ]; then
macos_major=$(sw_vers -productVersion | cut -d '.' -f 2)
macos_minor=$(sw_vers -productVersion | cut -d '.' -f 3)
- if [ "$macos_major" -lt 12 ] || ([ "$macos_major" -eq 12 ] && [ "$macos_minor" -lt 6 ]); then
+ if [ "$macos_major" -lt 12 ] || { [ "$macos_major" -eq 12 ] && [ "$macos_minor" -lt 6 ]; }; then
echo "$0: macOS $(sw_vers -productVersion) is not supported, upgrade to 10.12.6 or higher"
exit 1
fi
diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in
index db03e16ba..f3cfa157c 100644
--- a/scripts/nix-profile.sh.in
+++ b/scripts/nix-profile.sh.in
@@ -51,10 +51,9 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
unset __nix_defexpr
fi
- # Append ~/.nix-defexpr/channels/nixpkgs to $NIX_PATH so that
- # paths work when the user has fetched the Nixpkgs
- # channel.
- export NIX_PATH="${NIX_PATH:+$NIX_PATH:}nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs"
+ # Append ~/.nix-defexpr/channels to $NIX_PATH so that
+ # paths work when the user has fetched the Nixpkgs channel.
+ export NIX_PATH=${NIX_PATH:+$NIX_PATH:}$HOME/.nix-defexpr/channels
# Set up environment.
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 3f4d82490..004be8010 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -461,6 +461,28 @@ static void commonChildInit(Pipe & logPipe)
close(fdDevNull);
}
+void handleDiffHook(uid_t uid, uid_t gid, Path tryA, Path tryB, Path drvPath, Path tmpDir)
+{
+ auto diffHook = settings.diffHook;
+ if (diffHook != "" && settings.runDiffHook) {
+ try {
+ RunOptions diffHookOptions(diffHook,{tryA, tryB, drvPath, tmpDir});
+ diffHookOptions.searchPath = true;
+ diffHookOptions.uid = uid;
+ diffHookOptions.gid = gid;
+ diffHookOptions.chdir = "/";
+
+ auto diffRes = runProgram(diffHookOptions);
+ if (!statusOk(diffRes.first))
+ throw ExecError(diffRes.first, fmt("diff-hook program '%1%' %2%", diffHook, statusToString(diffRes.first)));
+
+ if (diffRes.second != "")
+ printError(chomp(diffRes.second));
+ } catch (Error & error) {
+ printError("diff hook execution failed: %s", error.what());
+ }
+ }
+}
//////////////////////////////////////////////////////////////////////
@@ -803,9 +825,6 @@ private:
/* Whether we're currently doing a chroot build. */
bool useChroot = false;
- /* Whether we need to perform hash rewriting if there are valid output paths. */
- bool needsHashRewrite;
-
Path chrootRootDir;
/* RAII object to delete the chroot directory. */
@@ -885,6 +904,9 @@ public:
Worker & worker, BuildMode buildMode = bmNormal);
~DerivationGoal();
+ /* Whether we need to perform hash rewriting if there are valid output paths. */
+ bool needsHashRewrite();
+
void timedOut() override;
string key() override
@@ -997,13 +1019,6 @@ DerivationGoal::DerivationGoal(const Path & drvPath, const StringSet & wantedOut
, wantedOutputs(wantedOutputs)
, buildMode(buildMode)
{
-#if __linux__
- needsHashRewrite = !useChroot;
-#else
- /* Darwin requires hash rewriting even when sandboxing is enabled. */
- needsHashRewrite = true;
-#endif
-
state = &DerivationGoal::getDerivation;
name = (format("building of '%1%'") % drvPath).str();
trace("created");
@@ -1044,6 +1059,17 @@ DerivationGoal::~DerivationGoal()
}
+inline bool DerivationGoal::needsHashRewrite()
+{
+#if __linux__
+ return !useChroot;
+#else
+ /* Darwin requires hash rewriting even when sandboxing is enabled. */
+ return true;
+#endif
+}
+
+
void DerivationGoal::killChild()
{
if (pid != -1) {
@@ -2083,7 +2109,7 @@ void DerivationGoal::startBuilder()
#endif
}
- if (needsHashRewrite) {
+ if (needsHashRewrite()) {
if (pathExists(homeDir))
throw Error(format("directory '%1%' exists; please remove it") % homeDir);
@@ -3039,8 +3065,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;
@@ -3067,7 +3092,7 @@ void DerivationGoal::registerOutputs()
if (buildMode != bmCheck) actualPath = worker.store.toRealPath(path);
}
- if (needsHashRewrite) {
+ if (needsHashRewrite()) {
Path redirected = redirectedOutputs[path];
if (buildMode == bmRepair
&& redirectedBadOutputs.find(path) != redirectedBadOutputs.end()
@@ -3185,11 +3210,17 @@ void DerivationGoal::registerOutputs()
if (!worker.store.isValidPath(path)) continue;
auto info = *worker.store.queryPathInfo(path);
if (hash.first != info.narHash) {
- if (settings.keepFailed) {
+ if (settings.runDiffHook || 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);
+
+ handleDiffHook(
+ buildUser ? buildUser->getUID() : getuid(),
+ buildUser ? buildUser->getGID() : getgid(),
+ path, dst, drvPath, tmpDir);
+
throw Error(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
% drvPath % path % dst);
} else
@@ -3254,16 +3285,10 @@ 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(
+ buildUser ? buildUser->getUID() : getuid(),
+ buildUser ? buildUser->getGID() : getgid(),
+ prev, i->second.path, drvPath, tmpDir);
if (settings.enforceDeterminism)
throw NotDeterministic(msg);
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index a9dab780f..f82f902fc 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -16,6 +16,7 @@
#include
#include
+#include
#include
#include
#include
@@ -1038,6 +1039,16 @@ void runProgram2(const RunOptions & options)
if (source && dup2(in.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin");
+ if (options.chdir && chdir((*options.chdir).c_str()) == -1)
+ throw SysError("chdir failed");
+ if (options.gid && setgid(*options.gid) == -1)
+ throw SysError("setgid failed");
+ /* Drop all other groups if we're setgid. */
+ if (options.gid && setgroups(0, 0) == -1)
+ throw SysError("setgroups failed");
+ if (options.uid && setuid(*options.uid) == -1)
+ throw SysError("setuid failed");
+
Strings args_(options.args);
args_.push_front(options.program);
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 09a90a340..35f9169f6 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -271,6 +271,9 @@ string runProgram(Path program, bool searchPath = false,
struct RunOptions
{
+ std::optional uid;
+ std::optional gid;
+ std::optional chdir;
Path program;
bool searchPath = true;
Strings args;
@@ -427,6 +430,7 @@ void ignoreException();
/* Some ANSI escape sequences. */
#define ANSI_NORMAL "\e[0m"
#define ANSI_BOLD "\e[1m"
+#define ANSI_FAINT "\e[2m"
#define ANSI_RED "\e[31;1m"
#define ANSI_GREEN "\e[32;1m"
#define ANSI_BLUE "\e[34;1m"
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 3ec5f48d5..a1fcb892a 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -20,6 +20,8 @@ std::string programPath;
struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{
+ bool printBuildLogs = false;
+
NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
{
mkFlag()
@@ -41,6 +43,11 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
throw Exit();
});
+ mkFlag()
+ .longName("print-build-logs")
+ .description("print full build logs on stderr")
+ .set(&printBuildLogs, true);
+
mkFlag()
.longName("version")
.description("show version information")
@@ -109,8 +116,7 @@ void mainWrapped(int argc, char * * argv)
Finally f([]() { stopProgressBar(); });
- if (isatty(STDERR_FILENO))
- startProgressBar();
+ startProgressBar(args.printBuildLogs);
args.command->prepare();
args.command->run();
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index 40b905ba3..304f918cc 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -2,6 +2,7 @@
#include "util.hh"
#include "sync.hh"
#include "store-api.hh"
+#include "names.hh"
#include
#include