diff --git a/doc/manual/command-ref/nix-store.xml b/doc/manual/command-ref/nix-store.xml
index 625598310..113a3c2e4 100644
--- a/doc/manual/command-ref/nix-store.xml
+++ b/doc/manual/command-ref/nix-store.xml
@@ -215,6 +215,48 @@ printed.)
+Special exit codes:
+
+
+
+ 100
+ Generic build failure, the builder process
+ returned with a non-zero exit code.
+
+
+ 101
+ Build timeout, the build was aborted because it
+ did not complete within the specified timeout.
+
+
+
+ 102
+ Hash mismatch, the build output was rejected
+ because it does not match the specified outputHash.
+
+
+
+ 104
+ Not deterministic, the build succeeded in check
+ mode but the resulting output is not binary reproducable.
+
+
+
+
+
+With the flag it's possible for
+multiple failures to occur, in this case the 1xx status codes are or combined
+using binary or.
+1100100
+ ^^^^
+ |||`- timeout
+ ||`-- output hash mismatch
+ |`--- build failure
+ `---- not deterministic
+
+
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 5b38bcf3c..350ac4092 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -266,6 +266,12 @@ public:
/* Set if at least one derivation had a timeout. */
bool timedOut;
+ /* Set if at least one derivation fails with a hash mismatch. */
+ bool hashMismatch;
+
+ /* Set if at least one derivation is not deterministic in check mode. */
+ bool checkMismatch;
+
LocalStore & store;
std::unique_ptr hook;
@@ -3213,6 +3219,7 @@ void DerivationGoal::registerOutputs()
/* Throw an error after registering the path as
valid. */
+ worker.hashMismatch = true;
delayedException = std::make_exception_ptr(
BuildError("hash mismatch in fixed-output derivation '%s':\n wanted: %s\n got: %s",
dest, h.to_string(), h2.to_string()));
@@ -3255,6 +3262,7 @@ void DerivationGoal::registerOutputs()
if (!worker.store.isValidPath(path)) continue;
auto info = *worker.store.queryPathInfo(path);
if (hash.first != info.narHash) {
+ worker.checkMismatch = true;
if (settings.runDiffHook || settings.keepFailed) {
Path dst = worker.store.toRealPath(path + checkSuffix);
deletePath(dst);
@@ -3266,10 +3274,10 @@ void DerivationGoal::registerOutputs()
buildUser ? buildUser->getGID() : getgid(),
path, dst, drvPath, tmpDir);
- throw Error(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
+ throw NotDeterministic(format("derivation '%1%' may not be deterministic: output '%2%' differs from '%3%'")
% drvPath % path % dst);
} else
- throw Error(format("derivation '%1%' may not be deterministic: output '%2%' differs")
+ throw NotDeterministic(format("derivation '%1%' may not be deterministic: output '%2%' differs")
% drvPath % path);
}
@@ -4101,6 +4109,8 @@ Worker::Worker(LocalStore & store)
lastWokenUp = steady_time_point::min();
permanentFailure = false;
timedOut = false;
+ hashMismatch = false;
+ checkMismatch = false;
}
@@ -4461,7 +4471,29 @@ void Worker::waitForInput()
unsigned int Worker::exitStatus()
{
- return timedOut ? 101 : (permanentFailure ? 100 : 1);
+ /*
+ * 1100100
+ * ^^^^
+ * |||`- timeout
+ * ||`-- output hash mismatch
+ * |`--- build failure
+ * `---- not deterministic
+ */
+ unsigned int mask = 0;
+ bool buildFailure = permanentFailure || timedOut || hashMismatch;
+ if (buildFailure)
+ mask |= 0x04; // 100
+ if (timedOut)
+ mask |= 0x01; // 101
+ if (hashMismatch)
+ mask |= 0x02; // 102
+ if (checkMismatch) {
+ mask |= 0x08; // 104
+ }
+
+ if (mask)
+ mask |= 0x60;
+ return mask ? mask : 1;
}
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 0c5a73ea3..7a2af237e 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -855,10 +855,11 @@ CachedDownloadResult Downloader::downloadCached(
}
if (expectedStorePath != "" && storePath != expectedStorePath) {
+ unsigned int statusCode = 102;
Hash gotHash = request.unpack
? hashPath(request.expectedHash.type, store->toRealPath(storePath)).first
: hashFile(request.expectedHash.type, store->toRealPath(storePath));
- throw nix::Error("hash mismatch in file downloaded from '%s':\n wanted: %s\n got: %s",
+ throw nix::Error(statusCode, "hash mismatch in file downloaded from '%s':\n wanted: %s\n got: %s",
url, request.expectedHash.to_string(), gotHash.to_string());
}
diff --git a/tests/check.nix b/tests/check.nix
index 08aac2fb0..56c82e565 100644
--- a/tests/check.nix
+++ b/tests/check.nix
@@ -10,6 +10,11 @@ with import ./config.nix;
'';
};
+ hashmismatch = import {
+ url = "file://" + toString ./dummy;
+ sha256 = "0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lzfd1877wk11c9c73";
+ };
+
fetchurl = import {
url = "file://" + toString ./lang/eval-okay-xml.exp.xml;
sha256 = "0kg4sla7ihm8ijr8cb3117fhl99zrc2bwy1jrngsfmkh8bav4m0v";
diff --git a/tests/check.sh b/tests/check.sh
index b05e40ffb..bc23a6634 100644
--- a/tests/check.sh
+++ b/tests/check.sh
@@ -6,14 +6,16 @@ nix-build dependencies.nix --no-out-link
nix-build dependencies.nix --no-out-link --check
nix-build check.nix -A nondeterministic --no-out-link
-(! nix-build check.nix -A nondeterministic --no-out-link --check 2> $TEST_ROOT/log)
+nix-build check.nix -A nondeterministic --no-out-link --check 2> $TEST_ROOT/log || status=$?
grep 'may not be deterministic' $TEST_ROOT/log
+[ "$status" = "104" ]
clearStore
nix-build dependencies.nix --no-out-link --repeat 3
-(! nix-build check.nix -A nondeterministic --no-out-link --repeat 1 2> $TEST_ROOT/log)
+nix-build check.nix -A nondeterministic --no-out-link --repeat 1 2> $TEST_ROOT/log || status=$?
+[ "$status" = "1" ]
grep 'differs from previous round' $TEST_ROOT/log
path=$(nix-build check.nix -A fetchurl --no-out-link --hashed-mirrors '')
@@ -23,10 +25,23 @@ echo foo > $path
chmod -w $path
nix-build check.nix -A fetchurl --no-out-link --check --hashed-mirrors ''
-
# Note: "check" doesn't repair anything, it just compares to the hash stored in the database.
[[ $(cat $path) = foo ]]
nix-build check.nix -A fetchurl --no-out-link --repair --hashed-mirrors ''
-
[[ $(cat $path) != foo ]]
+
+nix-build check.nix -A hashmismatch --no-out-link --hashed-mirrors '' || status=$?
+[ "$status" = "102" ]
+
+echo -n > ./dummy
+nix-build check.nix -A hashmismatch --no-out-link --hashed-mirrors ''
+echo 'Hello World' > ./dummy
+
+nix-build check.nix -A hashmismatch --no-out-link --check --hashed-mirrors '' || status=$?
+[ "$status" = "102" ]
+
+# Multiple failures with --keep-going
+nix-build check.nix -A nondeterministic --no-out-link
+nix-build check.nix -A nondeterministic -A hashmismatch --no-out-link --check --keep-going --hashed-mirrors '' || status=$?
+[ "$status" = "110" ]
diff --git a/tests/timeout.sh b/tests/timeout.sh
index 39ecf0a1a..eea9b5731 100644
--- a/tests/timeout.sh
+++ b/tests/timeout.sh
@@ -2,10 +2,14 @@
source common.sh
-failed=0
-messages="`nix-build -Q timeout.nix -A infiniteLoop --timeout 2 2>&1 || failed=1`"
-if [ $failed -ne 0 ]; then
- echo "error: 'nix-store' succeeded; should have timed out"
+
+set +e
+messages=$(nix-build -Q timeout.nix -A infiniteLoop --timeout 2 2>&1)
+status=$?
+set -e
+
+if [ $status -ne 101 ]; then
+ echo "error: 'nix-store' exited with '$status'; should have exited 101"
exit 1
fi