1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

Simplify handling of statuses for build errors

Instead of passing them around separately, or doing finicky logic in a
try-catch block to recover them, just make `BuildError` always contain a
status, and make it the thrower's responsibility to set it. This is much
more simple and explicit.

Once that change is done, split the `done` functions of `DerivationGoal`
and `DerivationBuildingGoal` into separate success and failure
functions, which ends up being easier to understand and hardly any
duplication.

Also, change the handling of failures in resolved cases to use
`BuildResult::DependencyFailed` and a new message. This is because the
underlying derivation will also get its message printed --- which is
good, because in general the resolved derivation is not unique. One dyn
drv test had to be updated, but CA (and dyn drv) is experimental, so I
do not mind.

Finally, delete `SubstError` because it is unused.
This commit is contained in:
John Ericson 2025-08-27 18:58:58 -04:00
parent 0590b13156
commit 169033001d
16 changed files with 153 additions and 92 deletions

View file

@ -118,7 +118,7 @@ void DerivationBuildingGoal::timedOut(Error && ex)
killChild(); killChild();
// We're not inside a coroutine, hence we can't use co_return here. // We're not inside a coroutine, hence we can't use co_return here.
// Thus we ignore the return value. // Thus we ignore the return value.
[[maybe_unused]] Done _ = done(BuildResult::TimedOut, {}, std::move(ex)); [[maybe_unused]] Done _ = doneFailure({BuildResult::TimedOut, std::move(ex)});
} }
/** /**
@ -258,7 +258,7 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
nrFailed, nrFailed,
nrFailed == 1 ? "dependency" : "dependencies"); nrFailed == 1 ? "dependency" : "dependencies");
msg += showKnownOutputs(worker.store, *drv); msg += showKnownOutputs(worker.store, *drv);
co_return done(BuildResult::DependencyFailed, {}, Error(msg)); co_return doneFailure(BuildError(BuildResult::DependencyFailed, msg));
} }
/* Gather information necessary for computing the closure and/or /* Gather information necessary for computing the closure and/or
@ -359,9 +359,9 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
auto resolvedResult = resolvedDrvGoal->buildResult; auto resolvedResult = resolvedDrvGoal->buildResult;
SingleDrvOutputs builtOutputs;
if (resolvedResult.success()) { if (resolvedResult.success()) {
SingleDrvOutputs builtOutputs;
auto resolvedHashes = staticOutputHashes(worker.store, drvResolved); auto resolvedHashes = staticOutputHashes(worker.store, drvResolved);
StorePathSet outputPaths; StorePathSet outputPaths;
@ -411,13 +411,19 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
} }
runPostBuildHook(worker.store, *logger, drvPath, outputPaths); runPostBuildHook(worker.store, *logger, drvPath, outputPaths);
auto status = resolvedResult.status;
if (status == BuildResult::AlreadyValid)
status = BuildResult::ResolvesToAlreadyValid;
co_return doneSuccess(status, std::move(builtOutputs));
} else {
co_return doneFailure({
BuildResult::DependencyFailed,
"build of resolved derivation '%s' failed",
worker.store.printStorePath(pathResolved),
});
} }
auto status = resolvedResult.status;
if (status == BuildResult::AlreadyValid)
status = BuildResult::ResolvesToAlreadyValid;
co_return done(status, std::move(builtOutputs));
} }
/* If we get this far, we know no dynamic drvs inputs */ /* If we get this far, we know no dynamic drvs inputs */
@ -542,7 +548,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath)); debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
outputLocks.setDeletion(true); outputLocks.setDeletion(true);
outputLocks.unlock(); outputLocks.unlock();
co_return done(BuildResult::AlreadyValid, std::move(validOutputs)); co_return doneSuccess(BuildResult::AlreadyValid, std::move(validOutputs));
} }
/* If any of the outputs already exist but are not valid, delete /* If any of the outputs already exist but are not valid, delete
@ -752,7 +758,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
} catch (BuildError & e) { } catch (BuildError & e) {
outputLocks.unlock(); outputLocks.unlock();
worker.permanentFailure = true; worker.permanentFailure = true;
co_return done(BuildResult::InputRejected, {}, std::move(e)); co_return doneFailure(std::move(e));
} }
/* If we have to wait and retry (see below), then `builder` will /* If we have to wait and retry (see below), then `builder` will
@ -800,7 +806,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
builder.reset(); builder.reset();
outputLocks.unlock(); outputLocks.unlock();
worker.permanentFailure = true; worker.permanentFailure = true;
co_return done(BuildResult::InputRejected, {}, std::move(e)); co_return doneFailure(std::move(e)); // InputRejected
} }
started(); started();
@ -812,7 +818,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
// N.B. cannot use `std::visit` with co-routine return // N.B. cannot use `std::visit` with co-routine return
if (auto * ste = std::get_if<0>(&res)) { if (auto * ste = std::get_if<0>(&res)) {
outputLocks.unlock(); outputLocks.unlock();
co_return done(std::move(ste->first), {}, std::move(ste->second)); co_return doneFailure(std::move(*ste));
} else if (auto * builtOutputs = std::get_if<1>(&res)) { } else if (auto * builtOutputs = std::get_if<1>(&res)) {
StorePathSet outputPaths; StorePathSet outputPaths;
for (auto & [_, output] : *builtOutputs) for (auto & [_, output] : *builtOutputs)
@ -825,7 +831,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
(unlinked) lock files. */ (unlinked) lock files. */
outputLocks.setDeletion(true); outputLocks.setDeletion(true);
outputLocks.unlock(); outputLocks.unlock();
co_return done(BuildResult::Built, std::move(*builtOutputs)); co_return doneSuccess(BuildResult::Built, std::move(*builtOutputs));
} else { } else {
unreachable(); unreachable();
} }
@ -970,7 +976,7 @@ Goal::Co DerivationBuildingGoal::hookDone()
/* TODO (once again) support fine-grained error codes, see issue #12641. */ /* TODO (once again) support fine-grained error codes, see issue #12641. */
co_return done(BuildResult::MiscFailure, {}, BuildError(msg)); co_return doneFailure(BuildError{BuildResult::MiscFailure, msg});
} }
/* Compute the FS closure of the outputs and register them as /* Compute the FS closure of the outputs and register them as
@ -997,7 +1003,7 @@ Goal::Co DerivationBuildingGoal::hookDone()
outputLocks.setDeletion(true); outputLocks.setDeletion(true);
outputLocks.unlock(); outputLocks.unlock();
co_return done(BuildResult::Built, std::move(builtOutputs)); co_return doneSuccess(BuildResult::Built, std::move(builtOutputs));
} }
HookReply DerivationBuildingGoal::tryBuildHook() HookReply DerivationBuildingGoal::tryBuildHook()
@ -1179,10 +1185,11 @@ void DerivationBuildingGoal::handleChildOutput(Descriptor fd, std::string_view d
killChild(); killChild();
// We're not inside a coroutine, hence we can't use co_return here. // We're not inside a coroutine, hence we can't use co_return here.
// Thus we ignore the return value. // Thus we ignore the return value.
[[maybe_unused]] Done _ = done( [[maybe_unused]] Done _ = doneFailure(BuildError(
BuildResult::LogLimitExceeded, BuildResult::LogLimitExceeded,
{}, "%s killed after writing more than %d bytes of log output",
Error("%s killed after writing more than %d bytes of log output", getName(), settings.maxLogSize)); getName(),
settings.maxLogSize));
return; return;
} }
@ -1343,13 +1350,27 @@ SingleDrvOutputs DerivationBuildingGoal::assertPathValidity()
return validOutputs; return validOutputs;
} }
Goal::Done Goal::Done DerivationBuildingGoal::doneSuccess(BuildResult::Status status, SingleDrvOutputs builtOutputs)
DerivationBuildingGoal::done(BuildResult::Status status, SingleDrvOutputs builtOutputs, std::optional<Error> ex)
{ {
outputLocks.unlock();
buildResult.status = status; buildResult.status = status;
if (ex)
buildResult.errorMsg = fmt("%s", Uncolored(ex->info().msg)); assert(buildResult.success());
mcRunningBuilds.reset();
buildResult.builtOutputs = std::move(builtOutputs);
if (status == BuildResult::Built)
worker.doneBuilds++;
worker.updateProgress();
return amDone(ecSuccess, std::nullopt);
}
Goal::Done DerivationBuildingGoal::doneFailure(BuildError ex)
{
buildResult.status = ex.status;
buildResult.errorMsg = fmt("%s", Uncolored(ex.info().msg));
if (buildResult.status == BuildResult::TimedOut) if (buildResult.status == BuildResult::TimedOut)
worker.timedOut = true; worker.timedOut = true;
if (buildResult.status == BuildResult::PermanentFailure) if (buildResult.status == BuildResult::PermanentFailure)
@ -1357,18 +1378,12 @@ DerivationBuildingGoal::done(BuildResult::Status status, SingleDrvOutputs builtO
mcRunningBuilds.reset(); mcRunningBuilds.reset();
if (buildResult.success()) { if (ex.status != BuildResult::DependencyFailed)
buildResult.builtOutputs = std::move(builtOutputs); worker.failedBuilds++;
if (status == BuildResult::Built)
worker.doneBuilds++;
} else {
if (status != BuildResult::DependencyFailed)
worker.failedBuilds++;
}
worker.updateProgress(); worker.updateProgress();
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex)); return amDone(ecFailed, {std::move(ex)});
} }
} // namespace nix } // namespace nix

View file

@ -1,6 +1,7 @@
#include <queue> #include <queue>
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/store/build-result.hh"
#include "derivation-check.hh" #include "derivation-check.hh"
@ -54,6 +55,7 @@ void checkOutputs(
auto applyChecks = [&](const DerivationOptions::OutputChecks & checks) { auto applyChecks = [&](const DerivationOptions::OutputChecks & checks) {
if (checks.maxSize && info.narSize > *checks.maxSize) if (checks.maxSize && info.narSize > *checks.maxSize)
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"path '%s' is too large at %d bytes; limit is %d bytes", "path '%s' is too large at %d bytes; limit is %d bytes",
store.printStorePath(info.path), store.printStorePath(info.path),
info.narSize, info.narSize,
@ -63,6 +65,7 @@ void checkOutputs(
uint64_t closureSize = getClosure(info.path).second; uint64_t closureSize = getClosure(info.path).second;
if (closureSize > *checks.maxClosureSize) if (closureSize > *checks.maxClosureSize)
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"closure of path '%s' is too large at %d bytes; limit is %d bytes", "closure of path '%s' is too large at %d bytes; limit is %d bytes",
store.printStorePath(info.path), store.printStorePath(info.path),
closureSize, closureSize,
@ -83,6 +86,7 @@ void checkOutputs(
std::string outputsListing = std::string outputsListing =
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; }); concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"derivation '%s' output check for '%s' contains an illegal reference specifier '%s'," "derivation '%s' output check for '%s' contains an illegal reference specifier '%s',"
" expected store path or output name (one of [%s])", " expected store path or output name (one of [%s])",
store.printStorePath(drvPath), store.printStorePath(drvPath),
@ -115,6 +119,7 @@ void checkOutputs(
badPathsStr += store.printStorePath(i); badPathsStr += store.printStorePath(i);
} }
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"output '%s' is not allowed to refer to the following paths:%s", "output '%s' is not allowed to refer to the following paths:%s",
store.printStorePath(info.path), store.printStorePath(info.path),
badPathsStr); badPathsStr);

View file

@ -94,7 +94,7 @@ Goal::Co DerivationGoal::haveDerivation()
/* If they are all valid, then we're done. */ /* If they are all valid, then we're done. */
if (checkResult && checkResult->second == PathStatus::Valid && buildMode == bmNormal) { if (checkResult && checkResult->second == PathStatus::Valid && buildMode == bmNormal) {
co_return done(BuildResult::AlreadyValid, checkResult->first); co_return doneSuccess(BuildResult::AlreadyValid, checkResult->first);
} }
Goals waitees; Goals waitees;
@ -122,12 +122,10 @@ Goal::Co DerivationGoal::haveDerivation()
assert(!drv->type().isImpure()); assert(!drv->type().isImpure());
if (nrFailed > 0 && nrFailed > nrNoSubstituters && !settings.tryFallback) { if (nrFailed > 0 && nrFailed > nrNoSubstituters && !settings.tryFallback) {
co_return done( co_return doneFailure(BuildError(
BuildResult::TransientFailure, BuildResult::TransientFailure,
{}, "some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
Error( worker.store.printStorePath(drvPath)));
"some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
worker.store.printStorePath(drvPath)));
} }
nrFailed = nrNoSubstituters = 0; nrFailed = nrNoSubstituters = 0;
@ -137,7 +135,7 @@ Goal::Co DerivationGoal::haveDerivation()
bool allValid = checkResult && checkResult->second == PathStatus::Valid; bool allValid = checkResult && checkResult->second == PathStatus::Valid;
if (buildMode == bmNormal && allValid) { if (buildMode == bmNormal && allValid) {
co_return done(BuildResult::Substituted, checkResult->first); co_return doneSuccess(BuildResult::Substituted, checkResult->first);
} }
if (buildMode == bmRepair && allValid) { if (buildMode == bmRepair && allValid) {
co_return repairClosure(); co_return repairClosure();
@ -281,7 +279,7 @@ Goal::Co DerivationGoal::repairClosure()
"some paths in the output closure of derivation '%s' could not be repaired", "some paths in the output closure of derivation '%s' could not be repaired",
worker.store.printStorePath(drvPath)); worker.store.printStorePath(drvPath));
} }
co_return done(BuildResult::AlreadyValid, assertPathValidity()); co_return doneSuccess(BuildResult::AlreadyValid, assertPathValidity());
} }
std::optional<std::pair<Realisation, PathStatus>> DerivationGoal::checkPathValidity() std::optional<std::pair<Realisation, PathStatus>> DerivationGoal::checkPathValidity()
@ -339,12 +337,27 @@ Realisation DerivationGoal::assertPathValidity()
return checkResult->first; return checkResult->first;
} }
Goal::Done Goal::Done DerivationGoal::doneSuccess(BuildResult::Status status, Realisation builtOutput)
DerivationGoal::done(BuildResult::Status status, std::optional<Realisation> builtOutput, std::optional<Error> ex)
{ {
buildResult.status = status; buildResult.status = status;
if (ex)
buildResult.errorMsg = fmt("%s", Uncolored(ex->info().msg)); assert(buildResult.success());
mcExpectedBuilds.reset();
buildResult.builtOutputs = {{wantedOutput, std::move(builtOutput)}};
if (status == BuildResult::Built)
worker.doneBuilds++;
worker.updateProgress();
return amDone(ecSuccess, std::nullopt);
}
Goal::Done DerivationGoal::doneFailure(BuildError ex)
{
buildResult.status = ex.status;
buildResult.errorMsg = fmt("%s", Uncolored(ex.info().msg));
if (buildResult.status == BuildResult::TimedOut) if (buildResult.status == BuildResult::TimedOut)
worker.timedOut = true; worker.timedOut = true;
if (buildResult.status == BuildResult::PermanentFailure) if (buildResult.status == BuildResult::PermanentFailure)
@ -352,19 +365,12 @@ DerivationGoal::done(BuildResult::Status status, std::optional<Realisation> buil
mcExpectedBuilds.reset(); mcExpectedBuilds.reset();
if (buildResult.success()) { if (ex.status != BuildResult::DependencyFailed)
assert(builtOutput); worker.failedBuilds++;
buildResult.builtOutputs = {{wantedOutput, std::move(*builtOutput)}};
if (status == BuildResult::Built)
worker.doneBuilds++;
} else {
if (status != BuildResult::DependencyFailed)
worker.failedBuilds++;
}
worker.updateProgress(); worker.updateProgress();
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex)); return amDone(ecFailed, {std::move(ex)});
} }
} // namespace nix } // namespace nix

View file

@ -265,7 +265,8 @@ DerivationOptions::getParsedExportReferencesGraph(const StoreDirConfig & store)
StorePathSet storePaths; StorePathSet storePaths;
for (auto & storePathS : ss) { for (auto & storePathS : ss) {
if (!store.isInStore(storePathS)) if (!store.isInStore(storePathS))
throw BuildError("'exportReferencesGraph' contains a non-store path '%1%'", storePathS); throw BuildError(
BuildResult::InputRejected, "'exportReferencesGraph' contains a non-store path '%1%'", storePathS);
storePaths.insert(store.toStorePath(storePathS).first); storePaths.insert(store.toStorePath(storePathS).first);
} }
res.insert_or_assign(fileName, storePaths); res.insert_or_assign(fileName, storePaths);

View file

@ -1,13 +1,13 @@
#pragma once #pragma once
///@file ///@file
#include "nix/store/realisation.hh"
#include "nix/store/derived-path.hh"
#include <string> #include <string>
#include <chrono> #include <chrono>
#include <optional> #include <optional>
#include "nix/store/derived-path.hh"
#include "nix/store/realisation.hh"
namespace nix { namespace nix {
struct BuildResult struct BuildResult
@ -90,6 +90,26 @@ struct BuildResult
} }
}; };
/**
* denotes a permanent build failure
*/
struct BuildError : public Error
{
BuildResult::Status status;
BuildError(BuildResult::Status status, BuildError && error)
: Error{std::move(error)}
, status{status}
{
}
BuildError(BuildResult::Status status, auto &&... args)
: Error{args...}
, status{status}
{
}
};
/** /**
* A `BuildResult` together with its "primary key". * A `BuildResult` together with its "primary key".
*/ */

View file

@ -191,7 +191,7 @@ struct DerivationBuilder : RestrictionContext
* more information. The second case indicates success, and * more information. The second case indicates success, and
* realisations for each output of the derivation are returned. * realisations for each output of the derivation are returned.
*/ */
virtual std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> unprepareBuild() = 0; virtual std::variant<BuildError, SingleDrvOutputs> unprepareBuild() = 0;
/** /**
* Stop the in-process nix daemon thread. * Stop the in-process nix daemon thread.

View file

@ -170,7 +170,9 @@ struct DerivationBuildingGoal : public Goal
void started(); void started();
Done done(BuildResult::Status status, SingleDrvOutputs builtOutputs = {}, std::optional<Error> ex = {}); Done doneSuccess(BuildResult::Status status, SingleDrvOutputs builtOutputs);
Done doneFailure(BuildError ex);
void appendLogTailErrorMsg(std::string & msg); void appendLogTailErrorMsg(std::string & msg);

View file

@ -99,13 +99,9 @@ private:
Co repairClosure(); Co repairClosure();
/** Done doneSuccess(BuildResult::Status status, Realisation builtOutput);
* @param builtOutput Must be set if `status` is successful.
*/ Done doneFailure(BuildError ex);
Done done(
BuildResult::Status status,
std::optional<Realisation> builtOutput = std::nullopt,
std::optional<Error> ex = {});
}; };
} // namespace nix } // namespace nix

View file

@ -24,11 +24,6 @@
namespace nix { namespace nix {
MakeError(SubstError, Error);
/**
* denotes a permanent build failure
*/
MakeError(BuildError, Error);
MakeError(InvalidPath, Error); MakeError(InvalidPath, Error);
MakeError(Unsupported, Error); MakeError(Unsupported, Error);
MakeError(SubstituteGone, Error); MakeError(SubstituteGone, Error);

View file

@ -1002,7 +1002,10 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
}}, }},
{[&](const StorePath & path, const StorePath & parent) { {[&](const StorePath & path, const StorePath & parent) {
return BuildError( return BuildError(
"cycle detected in the references of '%s' from '%s'", printStorePath(path), printStorePath(parent)); BuildResult::OutputRejected,
"cycle detected in the references of '%s' from '%s'",
printStorePath(path),
printStorePath(parent));
}}); }});
txn.commit(); txn.commit();

View file

@ -322,7 +322,10 @@ StorePaths Store::topoSortPaths(const StorePathSet & paths)
}}, }},
{[&](const StorePath & path, const StorePath & parent) { {[&](const StorePath & path, const StorePath & parent) {
return BuildError( return BuildError(
"cycle detected in the references of '%s' from '%s'", printStorePath(path), printStorePath(parent)); BuildResult::OutputRejected,
"cycle detected in the references of '%s' from '%s'",
printStorePath(path),
printStorePath(parent));
}}); }});
} }

View file

@ -98,7 +98,7 @@ static void canonicalisePathMetaData_(
(i.e. "touch $out/foo; ln $out/foo $out/bar"). */ (i.e. "touch $out/foo; ln $out/foo $out/bar"). */
if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) { if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) {
if (S_ISDIR(st.st_mode) || !inodesSeen.count(Inode(st.st_dev, st.st_ino))) if (S_ISDIR(st.st_mode) || !inodesSeen.count(Inode(st.st_dev, st.st_ino)))
throw BuildError("invalid ownership on file '%1%'", path); throw BuildError(BuildResult::OutputRejected, "invalid ownership on file '%1%'", path);
mode_t mode = st.st_mode & ~S_IFMT; mode_t mode = st.st_mode & ~S_IFMT;
assert( assert(
S_ISLNK(st.st_mode) S_ISLNK(st.st_mode)

View file

@ -770,6 +770,7 @@ StorePathSet Store::exportReferences(const StorePathSet & storePaths, const Stor
for (auto & storePath : storePaths) { for (auto & storePath : storePaths) {
if (!inputPaths.count(storePath)) if (!inputPaths.count(storePath))
throw BuildError( throw BuildError(
BuildResult::InputRejected,
"cannot export references of path '%s' because it is not in the input closure of the derivation", "cannot export references of path '%s' because it is not in the input closure of the derivation",
printStorePath(storePath)); printStorePath(storePath));

View file

@ -46,7 +46,13 @@
namespace nix { namespace nix {
MakeError(NotDeterministic, BuildError); struct NotDeterministic : BuildError
{
NotDeterministic(auto &&... args)
: BuildError(BuildResult::NotDeterministic, args...)
{
}
};
/** /**
* This class represents the state for building locally. * This class represents the state for building locally.
@ -185,7 +191,7 @@ public:
void startBuilder() override; void startBuilder() override;
std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> unprepareBuild() override; std::variant<BuildError, SingleDrvOutputs> unprepareBuild() override;
protected: protected:
@ -420,7 +426,7 @@ bool DerivationBuilderImpl::prepareBuild()
return true; return true;
} }
std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> DerivationBuilderImpl::unprepareBuild() std::variant<BuildError, SingleDrvOutputs> DerivationBuilderImpl::unprepareBuild()
{ {
// FIXME: get rid of this, rely on RAII. // FIXME: get rid of this, rely on RAII.
Finally releaseBuildUser([&]() { Finally releaseBuildUser([&]() {
@ -493,7 +499,10 @@ std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> Derivation
if (diskFull) if (diskFull)
msg += "\nnote: build failure may have been caused by lack of free disk space"; msg += "\nnote: build failure may have been caused by lack of free disk space";
throw BuildError(msg); throw BuildError(
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure
: BuildResult::PermanentFailure,
msg);
} }
/* Compute the FS closure of the outputs and register them as /* Compute the FS closure of the outputs and register them as
@ -509,12 +518,7 @@ std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> Derivation
return std::move(builtOutputs); return std::move(builtOutputs);
} catch (BuildError & e) { } catch (BuildError & e) {
BuildResult::Status st = dynamic_cast<NotDeterministic *>(&e) ? BuildResult::NotDeterministic return std::move(e);
: statusOk(status) ? BuildResult::OutputRejected
: !derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure
: BuildResult::PermanentFailure;
return std::pair{std::move(st), std::move(e)};
} }
} }
@ -682,7 +686,7 @@ void DerivationBuilderImpl::startBuilder()
fmt("\nNote: run `%s` to run programs for x86_64-darwin", fmt("\nNote: run `%s` to run programs for x86_64-darwin",
Magenta("/usr/sbin/softwareupdate --install-rosetta && launchctl stop org.nixos.nix-daemon")); Magenta("/usr/sbin/softwareupdate --install-rosetta && launchctl stop org.nixos.nix-daemon"));
throw BuildError(msg); throw BuildError(BuildResult::InputRejected, msg);
} }
auto buildDir = store.config->getBuildDir(); auto buildDir = store.config->getBuildDir();
@ -1378,6 +1382,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
auto optSt = maybeLstat(actualPath.c_str()); auto optSt = maybeLstat(actualPath.c_str());
if (!optSt) if (!optSt)
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"builder for '%s' failed to produce output path for output '%s' at '%s'", "builder for '%s' failed to produce output path for output '%s' at '%s'",
store.printStorePath(drvPath), store.printStorePath(drvPath),
outputName, outputName,
@ -1392,6 +1397,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH)))
|| (buildUser && st.st_uid != buildUser->getUID())) || (buildUser && st.st_uid != buildUser->getUID()))
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"suspicious ownership or permission on '%s' for output '%s'; rejecting this build output", "suspicious ownership or permission on '%s' for output '%s'; rejecting this build output",
actualPath, actualPath,
outputName); outputName);
@ -1428,7 +1434,11 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
{[&](const std::string & name) { {[&](const std::string & name) {
auto orifu = get(outputReferencesIfUnregistered, name); auto orifu = get(outputReferencesIfUnregistered, name);
if (!orifu) if (!orifu)
throw BuildError("no output reference for '%s' in build of '%s'", name, store.printStorePath(drvPath)); throw BuildError(
BuildResult::OutputRejected,
"no output reference for '%s' in build of '%s'",
name,
store.printStorePath(drvPath));
return std::visit( return std::visit(
overloaded{ overloaded{
/* Since we'll use the already installed versions of these, we /* Since we'll use the already installed versions of these, we
@ -1450,6 +1460,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
{[&](const std::string & path, const std::string & parent) { {[&](const std::string & path, const std::string & parent) {
// TODO with more -vvvv also show the temporary paths for manual inspection. // TODO with more -vvvv also show the temporary paths for manual inspection.
return BuildError( return BuildError(
BuildResult::OutputRejected,
"cycle detected in build of '%s' in the references of output '%s' from output '%s'", "cycle detected in build of '%s' in the references of output '%s' from output '%s'",
store.printStorePath(drvPath), store.printStorePath(drvPath),
path, path,
@ -1543,11 +1554,12 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo { auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
auto st = get(outputStats, outputName); auto st = get(outputStats, outputName);
if (!st) if (!st)
throw BuildError("output path %1% without valid stats info", actualPath); throw BuildError(BuildResult::OutputRejected, "output path %1% without valid stats info", actualPath);
if (outputHash.method.getFileIngestionMethod() == FileIngestionMethod::Flat) { if (outputHash.method.getFileIngestionMethod() == FileIngestionMethod::Flat) {
/* The output path should be a regular file without execute permission. */ /* The output path should be a regular file without execute permission. */
if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0) if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0)
throw BuildError( throw BuildError(
BuildResult::OutputRejected,
"output path '%1%' should be a non-executable regular file " "output path '%1%' should be a non-executable regular file "
"since recursive hashing is not enabled (one of outputHashMode={flat,text} is true)", "since recursive hashing is not enabled (one of outputHashMode={flat,text} is true)",
actualPath); actualPath);
@ -1649,6 +1661,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
valid. */ valid. */
miscMethods->noteHashMismatch(); miscMethods->noteHashMismatch();
delayedException = std::make_exception_ptr(BuildError( delayedException = std::make_exception_ptr(BuildError(
BuildResult::OutputRejected,
"hash mismatch in fixed-output derivation '%s':\n specified: %s\n got: %s", "hash mismatch in fixed-output derivation '%s':\n specified: %s\n got: %s",
store.printStorePath(drvPath), store.printStorePath(drvPath),
wanted.to_string(HashFormat::SRI, true), wanted.to_string(HashFormat::SRI, true),
@ -1657,6 +1670,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
if (!newInfo0.references.empty()) { if (!newInfo0.references.empty()) {
auto numViolations = newInfo.references.size(); auto numViolations = newInfo.references.size();
delayedException = std::make_exception_ptr(BuildError( delayedException = std::make_exception_ptr(BuildError(
BuildResult::OutputRejected,
"fixed-output derivations must not reference store paths: '%s' references %d distinct paths, e.g. '%s'", "fixed-output derivations must not reference store paths: '%s' references %d distinct paths, e.g. '%s'",
store.printStorePath(drvPath), store.printStorePath(drvPath),
numViolations, numViolations,

View file

@ -659,7 +659,7 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
throw SysError("setuid failed"); throw SysError("setuid failed");
} }
std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> unprepareBuild() override std::variant<BuildError, SingleDrvOutputs> unprepareBuild() override
{ {
sandboxMountNamespace = -1; sandboxMountNamespace = -1;
sandboxUserNamespace = -1; sandboxUserNamespace = -1;

View file

@ -9,4 +9,4 @@ expected=100
if [[ -v NIX_DAEMON_PACKAGE ]]; then expected=1; fi # work around the daemon not returning a 100 status correctly if [[ -v NIX_DAEMON_PACKAGE ]]; then expected=1; fi # work around the daemon not returning a 100 status correctly
expectStderr "$expected" nix-build ./text-hashed-output.nix -A failingWrapper --no-out-link \ expectStderr "$expected" nix-build ./text-hashed-output.nix -A failingWrapper --no-out-link \
| grepQuiet "build of '.*use-dynamic-drv-in-non-dynamic-drv-wrong.drv' failed" | grepQuiet "build of resolved derivation '.*use-dynamic-drv-in-non-dynamic-drv-wrong.drv' failed"