mirror of
https://github.com/NixOS/nix.git
synced 2025-11-09 03:56:01 +01:00
Merge pull request #14214 from obsidiansystems/derivation-resolution-goal
Split out `DerivationResolutionGoal`
This commit is contained in:
commit
d9cabddd17
14 changed files with 337 additions and 182 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
#include "nix/store/build/derivation-building-goal.hh"
|
#include "nix/store/build/derivation-building-goal.hh"
|
||||||
|
#include "nix/store/build/derivation-resolution-goal.hh"
|
||||||
#include "nix/store/build/derivation-env-desugar.hh"
|
#include "nix/store/build/derivation-env-desugar.hh"
|
||||||
#include "nix/store/build/derivation-trampoline-goal.hh"
|
#include "nix/store/build/derivation-trampoline-goal.hh"
|
||||||
#ifndef _WIN32 // TODO enable build hook on Windows
|
#ifndef _WIN32 // TODO enable build hook on Windows
|
||||||
|
|
@ -27,22 +28,21 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
DerivationBuildingGoal::DerivationBuildingGoal(
|
DerivationBuildingGoal::DerivationBuildingGoal(
|
||||||
const StorePath & drvPath, const Derivation & drv_, Worker & worker, BuildMode buildMode)
|
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode)
|
||||||
: Goal(worker, gaveUpOnSubstitution())
|
: Goal(worker, gaveUpOnSubstitution())
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
|
, drv{std::make_unique<Derivation>(drv)}
|
||||||
, buildMode(buildMode)
|
, buildMode(buildMode)
|
||||||
{
|
{
|
||||||
drv = std::make_unique<Derivation>(drv_);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
drvOptions =
|
drvOptions =
|
||||||
std::make_unique<DerivationOptions>(DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs));
|
std::make_unique<DerivationOptions>(DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
|
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = fmt("building of '%s' from in-memory derivation", worker.store.printStorePath(drvPath));
|
name = fmt("building derivation '%s'", worker.store.printStorePath(drvPath));
|
||||||
trace("created");
|
trace("created");
|
||||||
|
|
||||||
/* Prevent the .chroot directory from being
|
/* Prevent the .chroot directory from being
|
||||||
|
|
@ -67,11 +67,7 @@ DerivationBuildingGoal::~DerivationBuildingGoal()
|
||||||
|
|
||||||
std::string DerivationBuildingGoal::key()
|
std::string DerivationBuildingGoal::key()
|
||||||
{
|
{
|
||||||
/* Ensure that derivations get built in order of their name,
|
return "dd$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
|
||||||
i.e. a derivation named "aardvark" always comes before
|
|
||||||
"baboon". And substitution goals always happen before
|
|
||||||
derivation goals (due to "bd$"). */
|
|
||||||
return "bd$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationBuildingGoal::killChild()
|
void DerivationBuildingGoal::killChild()
|
||||||
|
|
@ -93,18 +89,6 @@ void DerivationBuildingGoal::timedOut(Error && ex)
|
||||||
[[maybe_unused]] Done _ = doneFailure({BuildResult::Failure::TimedOut, std::move(ex)});
|
[[maybe_unused]] Done _ = doneFailure({BuildResult::Failure::TimedOut, std::move(ex)});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for `inputGoals` local variable below
|
|
||||||
*/
|
|
||||||
struct value_comparison
|
|
||||||
{
|
|
||||||
template<typename T>
|
|
||||||
bool operator()(const ref<T> & lhs, const ref<T> & rhs) const
|
|
||||||
{
|
|
||||||
return *lhs < *rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string showKnownOutputs(const StoreDirConfig & store, const Derivation & drv)
|
std::string showKnownOutputs(const StoreDirConfig & store, const Derivation & drv)
|
||||||
{
|
{
|
||||||
std::string msg;
|
std::string msg;
|
||||||
|
|
@ -129,46 +113,6 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
|
||||||
{
|
{
|
||||||
Goals waitees;
|
Goals waitees;
|
||||||
|
|
||||||
std::map<ref<const SingleDerivedPath>, GoalPtr, value_comparison> inputGoals;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)>
|
|
||||||
addWaiteeDerivedPath;
|
|
||||||
|
|
||||||
addWaiteeDerivedPath = [&](ref<const SingleDerivedPath> inputDrv,
|
|
||||||
const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
|
||||||
if (!inputNode.value.empty()) {
|
|
||||||
auto g = worker.makeGoal(
|
|
||||||
DerivedPath::Built{
|
|
||||||
.drvPath = inputDrv,
|
|
||||||
.outputs = inputNode.value,
|
|
||||||
},
|
|
||||||
buildMode == bmRepair ? bmRepair : bmNormal);
|
|
||||||
inputGoals.insert_or_assign(inputDrv, g);
|
|
||||||
waitees.insert(std::move(g));
|
|
||||||
}
|
|
||||||
for (const auto & [outputName, childNode] : inputNode.childMap)
|
|
||||||
addWaiteeDerivedPath(
|
|
||||||
make_ref<SingleDerivedPath>(SingleDerivedPath::Built{inputDrv, outputName}), childNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto & [inputDrvPath, inputNode] : drv->inputDrvs.map) {
|
|
||||||
/* Ensure that pure, non-fixed-output derivations don't
|
|
||||||
depend on impure derivations. */
|
|
||||||
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && !drv->type().isImpure()
|
|
||||||
&& !drv->type().isFixed()) {
|
|
||||||
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
|
|
||||||
if (inputDrv.type().isImpure())
|
|
||||||
throw Error(
|
|
||||||
"pure derivation '%s' depends on impure derivation '%s'",
|
|
||||||
worker.store.printStorePath(drvPath),
|
|
||||||
worker.store.printStorePath(inputDrvPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy the input sources from the eval store to the build
|
/* Copy the input sources from the eval store to the build
|
||||||
store.
|
store.
|
||||||
|
|
||||||
|
|
@ -213,88 +157,22 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
|
||||||
|
|
||||||
/* Determine the full set of input paths. */
|
/* Determine the full set of input paths. */
|
||||||
|
|
||||||
/* First, the input derivations. */
|
|
||||||
{
|
{
|
||||||
auto & fullDrv = *drv;
|
auto resolutionGoal = worker.makeDerivationResolutionGoal(drvPath, *drv, buildMode);
|
||||||
|
{
|
||||||
auto drvType = fullDrv.type();
|
Goals waitees{resolutionGoal};
|
||||||
bool resolveDrv =
|
co_await await(std::move(waitees));
|
||||||
std::visit(
|
}
|
||||||
overloaded{
|
if (nrFailed != 0) {
|
||||||
[&](const DerivationType::InputAddressed & ia) {
|
co_return doneFailure({BuildResult::Failure::DependencyFailed, "resolution failed"});
|
||||||
/* must resolve if deferred. */
|
|
||||||
return ia.deferred;
|
|
||||||
},
|
|
||||||
[&](const DerivationType::ContentAddressed & ca) {
|
|
||||||
return !fullDrv.inputDrvs.map.empty()
|
|
||||||
&& (ca.fixed
|
|
||||||
/* Can optionally resolve if fixed, which is good
|
|
||||||
for avoiding unnecessary rebuilds. */
|
|
||||||
? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
|
|
||||||
/* Must resolve if floating and there are any inputs
|
|
||||||
drvs. */
|
|
||||||
: true);
|
|
||||||
},
|
|
||||||
[&](const DerivationType::Impure &) { return true; }},
|
|
||||||
drvType.raw)
|
|
||||||
/* no inputs are outputs of dynamic derivations */
|
|
||||||
|| std::ranges::any_of(fullDrv.inputDrvs.map.begin(), fullDrv.inputDrvs.map.end(), [](auto & pair) {
|
|
||||||
return !pair.second.childMap.empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
|
|
||||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
|
||||||
|
|
||||||
/* We are be able to resolve this derivation based on the
|
|
||||||
now-known results of dependencies. If so, we become a
|
|
||||||
stub goal aliasing that resolved derivation goal. */
|
|
||||||
std::optional attempt = fullDrv.tryResolve(
|
|
||||||
worker.store,
|
|
||||||
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
|
|
||||||
auto mEntry = get(inputGoals, drvPath);
|
|
||||||
if (!mEntry)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto & buildResult = (*mEntry)->buildResult;
|
|
||||||
return std::visit(
|
|
||||||
overloaded{
|
|
||||||
[](const BuildResult::Failure &) -> std::optional<StorePath> { return std::nullopt; },
|
|
||||||
[&](const BuildResult::Success & success) -> std::optional<StorePath> {
|
|
||||||
auto i = get(success.builtOutputs, outputName);
|
|
||||||
if (!i)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return i->outPath;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buildResult.inner);
|
|
||||||
});
|
|
||||||
if (!attempt) {
|
|
||||||
/* TODO (impure derivations-induced tech debt) (see below):
|
|
||||||
The above attempt should have found it, but because we manage
|
|
||||||
inputDrvOutputs statefully, sometimes it gets out of sync with
|
|
||||||
the real source of truth (store). So we query the store
|
|
||||||
directly if there's a problem. */
|
|
||||||
attempt = fullDrv.tryResolve(worker.store, &worker.evalStore);
|
|
||||||
}
|
}
|
||||||
assert(attempt);
|
|
||||||
Derivation drvResolved{std::move(*attempt)};
|
|
||||||
|
|
||||||
auto pathResolved = writeDerivation(worker.store, drvResolved);
|
if (resolutionGoal->resolvedDrv) {
|
||||||
|
auto & [pathResolved, drvResolved] = *resolutionGoal->resolvedDrv;
|
||||||
|
|
||||||
auto msg =
|
/* Store the resolved derivation, as part of the record of
|
||||||
fmt("resolved derivation: '%s' -> '%s'",
|
what we're actually building */
|
||||||
worker.store.printStorePath(drvPath),
|
writeDerivation(worker.store, drvResolved);
|
||||||
worker.store.printStorePath(pathResolved));
|
|
||||||
act = std::make_unique<Activity>(
|
|
||||||
*logger,
|
|
||||||
lvlInfo,
|
|
||||||
actBuildWaiting,
|
|
||||||
msg,
|
|
||||||
Logger::Fields{
|
|
||||||
worker.store.printStorePath(drvPath),
|
|
||||||
worker.store.printStorePath(pathResolved),
|
|
||||||
});
|
|
||||||
|
|
||||||
/* TODO https://github.com/NixOS/nix/issues/13247 we should
|
/* TODO https://github.com/NixOS/nix/issues/13247 we should
|
||||||
let the calling goal do this, so it has a change to pass
|
let the calling goal do this, so it has a change to pass
|
||||||
|
|
@ -383,7 +261,7 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
|
||||||
|
|
||||||
/* If we get this far, we know no dynamic drvs inputs */
|
/* If we get this far, we know no dynamic drvs inputs */
|
||||||
|
|
||||||
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map) {
|
for (auto & [depDrvPath, depNode] : drv->inputDrvs.map) {
|
||||||
for (auto & outputName : depNode.value) {
|
for (auto & outputName : depNode.value) {
|
||||||
/* Don't need to worry about `inputGoals`, because
|
/* Don't need to worry about `inputGoals`, because
|
||||||
impure derivations are always resolved above. Can
|
impure derivations are always resolved above. Can
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ DerivationGoal::DerivationGoal(
|
||||||
: Goal(worker, haveDerivation())
|
: Goal(worker, haveDerivation())
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
, wantedOutput(wantedOutput)
|
, wantedOutput(wantedOutput)
|
||||||
|
, drv{std::make_unique<Derivation>(drv)}
|
||||||
, outputHash{[&] {
|
, outputHash{[&] {
|
||||||
auto outputHashes = staticOutputHashes(worker.evalStore, drv);
|
auto outputHashes = staticOutputHashes(worker.evalStore, drv);
|
||||||
if (auto * mOutputHash = get(outputHashes, wantedOutput))
|
if (auto * mOutputHash = get(outputHashes, wantedOutput))
|
||||||
|
|
@ -41,11 +42,8 @@ DerivationGoal::DerivationGoal(
|
||||||
}()}
|
}()}
|
||||||
, buildMode(buildMode)
|
, buildMode(buildMode)
|
||||||
{
|
{
|
||||||
this->drv = std::make_unique<Derivation>(drv);
|
|
||||||
|
|
||||||
name =
|
name = fmt("getting output '%s' from derivation '%s'", wantedOutput, worker.store.printStorePath(drvPath));
|
||||||
fmt("building of '%s' from in-memory derivation",
|
|
||||||
DerivedPath::Built{makeConstantStorePathRef(drvPath), drv.outputNames()}.to_string(worker.store));
|
|
||||||
trace("created");
|
trace("created");
|
||||||
|
|
||||||
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
|
||||||
|
|
@ -54,11 +52,7 @@ DerivationGoal::DerivationGoal(
|
||||||
|
|
||||||
std::string DerivationGoal::key()
|
std::string DerivationGoal::key()
|
||||||
{
|
{
|
||||||
/* Ensure that derivations get built in order of their name,
|
return "db$" + std::string(drvPath.name()) + "$" + SingleDerivedPath::Built{
|
||||||
i.e. a derivation named "aardvark" always comes before
|
|
||||||
"baboon". And substitution goals always happen before
|
|
||||||
derivation goals (due to "b$"). */
|
|
||||||
return "b$" + std::string(drvPath.name()) + "$" + SingleDerivedPath::Built{
|
|
||||||
.drvPath = makeConstantStorePathRef(drvPath),
|
.drvPath = makeConstantStorePathRef(drvPath),
|
||||||
.output = wantedOutput,
|
.output = wantedOutput,
|
||||||
}.to_string(worker.store);
|
}.to_string(worker.store);
|
||||||
|
|
@ -189,18 +183,6 @@ Goal::Co DerivationGoal::haveDerivation()
|
||||||
co_return amDone(g->exitCode, g->ex);
|
co_return amDone(g->exitCode, g->ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for `inputGoals` local variable below
|
|
||||||
*/
|
|
||||||
struct value_comparison
|
|
||||||
{
|
|
||||||
template<typename T>
|
|
||||||
bool operator()(const ref<T> & lhs, const ref<T> & rhs) const
|
|
||||||
{
|
|
||||||
return *lhs < *rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Goal::Co DerivationGoal::repairClosure()
|
Goal::Co DerivationGoal::repairClosure()
|
||||||
{
|
{
|
||||||
assert(!drv->type().isImpure());
|
assert(!drv->type().isImpure());
|
||||||
|
|
|
||||||
191
src/libstore/build/derivation-resolution-goal.cc
Normal file
191
src/libstore/build/derivation-resolution-goal.cc
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
#include "nix/store/build/derivation-resolution-goal.hh"
|
||||||
|
#include "nix/store/build/worker.hh"
|
||||||
|
#include "nix/util/util.hh"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
DerivationResolutionGoal::DerivationResolutionGoal(
|
||||||
|
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode)
|
||||||
|
: Goal(worker, resolveDerivation())
|
||||||
|
, drvPath(drvPath)
|
||||||
|
, drv{std::make_unique<Derivation>(drv)}
|
||||||
|
, buildMode{buildMode}
|
||||||
|
{
|
||||||
|
name = fmt("resolving derivation '%s'", worker.store.printStorePath(drvPath));
|
||||||
|
trace("created");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DerivationResolutionGoal::key()
|
||||||
|
{
|
||||||
|
return "dc$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for `inputGoals` local variable below
|
||||||
|
*/
|
||||||
|
struct value_comparison
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
bool operator()(const ref<T> & lhs, const ref<T> & rhs) const
|
||||||
|
{
|
||||||
|
return *lhs < *rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Goal::Co DerivationResolutionGoal::resolveDerivation()
|
||||||
|
{
|
||||||
|
Goals waitees;
|
||||||
|
|
||||||
|
std::map<ref<const SingleDerivedPath>, GoalPtr, value_comparison> inputGoals;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)>
|
||||||
|
addWaiteeDerivedPath;
|
||||||
|
|
||||||
|
addWaiteeDerivedPath = [&](ref<const SingleDerivedPath> inputDrv,
|
||||||
|
const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||||
|
if (!inputNode.value.empty()) {
|
||||||
|
auto g = worker.makeGoal(
|
||||||
|
DerivedPath::Built{
|
||||||
|
.drvPath = inputDrv,
|
||||||
|
.outputs = inputNode.value,
|
||||||
|
},
|
||||||
|
buildMode == bmRepair ? bmRepair : bmNormal);
|
||||||
|
inputGoals.insert_or_assign(inputDrv, g);
|
||||||
|
waitees.insert(std::move(g));
|
||||||
|
}
|
||||||
|
for (const auto & [outputName, childNode] : inputNode.childMap)
|
||||||
|
addWaiteeDerivedPath(
|
||||||
|
make_ref<SingleDerivedPath>(SingleDerivedPath::Built{inputDrv, outputName}), childNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto & [inputDrvPath, inputNode] : drv->inputDrvs.map) {
|
||||||
|
/* Ensure that pure, non-fixed-output derivations don't
|
||||||
|
depend on impure derivations. */
|
||||||
|
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && !drv->type().isImpure()
|
||||||
|
&& !drv->type().isFixed()) {
|
||||||
|
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
|
||||||
|
if (inputDrv.type().isImpure())
|
||||||
|
throw Error(
|
||||||
|
"pure derivation '%s' depends on impure derivation '%s'",
|
||||||
|
worker.store.printStorePath(drvPath),
|
||||||
|
worker.store.printStorePath(inputDrvPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_await await(std::move(waitees));
|
||||||
|
|
||||||
|
trace("all inputs realised");
|
||||||
|
|
||||||
|
if (nrFailed != 0) {
|
||||||
|
auto msg =
|
||||||
|
fmt("Cannot build '%s'.\n"
|
||||||
|
"Reason: " ANSI_RED "%d %s failed" ANSI_NORMAL ".",
|
||||||
|
Magenta(worker.store.printStorePath(drvPath)),
|
||||||
|
nrFailed,
|
||||||
|
nrFailed == 1 ? "dependency" : "dependencies");
|
||||||
|
msg += showKnownOutputs(worker.store, *drv);
|
||||||
|
co_return amDone(ecFailed, {BuildError(BuildResult::Failure::DependencyFailed, msg)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gather information necessary for computing the closure and/or
|
||||||
|
running the build hook. */
|
||||||
|
|
||||||
|
/* Determine the full set of input paths. */
|
||||||
|
|
||||||
|
/* First, the input derivations. */
|
||||||
|
{
|
||||||
|
auto & fullDrv = *drv;
|
||||||
|
|
||||||
|
auto drvType = fullDrv.type();
|
||||||
|
bool resolveDrv =
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const DerivationType::InputAddressed & ia) {
|
||||||
|
/* must resolve if deferred. */
|
||||||
|
return ia.deferred;
|
||||||
|
},
|
||||||
|
[&](const DerivationType::ContentAddressed & ca) {
|
||||||
|
return !fullDrv.inputDrvs.map.empty()
|
||||||
|
&& (ca.fixed
|
||||||
|
/* Can optionally resolve if fixed, which is good
|
||||||
|
for avoiding unnecessary rebuilds. */
|
||||||
|
? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
|
||||||
|
/* Must resolve if floating and there are any inputs
|
||||||
|
drvs. */
|
||||||
|
: true);
|
||||||
|
},
|
||||||
|
[&](const DerivationType::Impure &) { return true; }},
|
||||||
|
drvType.raw)
|
||||||
|
/* no inputs are outputs of dynamic derivations */
|
||||||
|
|| std::ranges::any_of(fullDrv.inputDrvs.map.begin(), fullDrv.inputDrvs.map.end(), [](auto & pair) {
|
||||||
|
return !pair.second.childMap.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
|
||||||
|
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||||
|
|
||||||
|
/* We are be able to resolve this derivation based on the
|
||||||
|
now-known results of dependencies. If so, we become a
|
||||||
|
stub goal aliasing that resolved derivation goal. */
|
||||||
|
std::optional attempt = fullDrv.tryResolve(
|
||||||
|
worker.store,
|
||||||
|
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
|
||||||
|
auto mEntry = get(inputGoals, drvPath);
|
||||||
|
if (!mEntry)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
auto & buildResult = (*mEntry)->buildResult;
|
||||||
|
return std::visit(
|
||||||
|
overloaded{
|
||||||
|
[](const BuildResult::Failure &) -> std::optional<StorePath> { return std::nullopt; },
|
||||||
|
[&](const BuildResult::Success & success) -> std::optional<StorePath> {
|
||||||
|
auto i = get(success.builtOutputs, outputName);
|
||||||
|
if (!i)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return i->outPath;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buildResult.inner);
|
||||||
|
});
|
||||||
|
if (!attempt) {
|
||||||
|
/* TODO (impure derivations-induced tech debt) (see below):
|
||||||
|
The above attempt should have found it, but because we manage
|
||||||
|
inputDrvOutputs statefully, sometimes it gets out of sync with
|
||||||
|
the real source of truth (store). So we query the store
|
||||||
|
directly if there's a problem. */
|
||||||
|
attempt = fullDrv.tryResolve(worker.store, &worker.evalStore);
|
||||||
|
}
|
||||||
|
assert(attempt);
|
||||||
|
|
||||||
|
auto pathResolved = writeDerivation(worker.store, *attempt, NoRepair, /*readOnly =*/true);
|
||||||
|
|
||||||
|
auto msg =
|
||||||
|
fmt("resolved derivation: '%s' -> '%s'",
|
||||||
|
worker.store.printStorePath(drvPath),
|
||||||
|
worker.store.printStorePath(pathResolved));
|
||||||
|
act = std::make_unique<Activity>(
|
||||||
|
*logger,
|
||||||
|
lvlInfo,
|
||||||
|
actBuildWaiting,
|
||||||
|
msg,
|
||||||
|
Logger::Fields{
|
||||||
|
worker.store.printStorePath(drvPath),
|
||||||
|
worker.store.printStorePath(pathResolved),
|
||||||
|
});
|
||||||
|
|
||||||
|
resolvedDrv =
|
||||||
|
std::make_unique<std::pair<StorePath, BasicDerivation>>(std::move(pathResolved), *std::move(attempt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return amDone(ecSuccess, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -31,7 +31,7 @@ DerivationTrampolineGoal::DerivationTrampolineGoal(
|
||||||
void DerivationTrampolineGoal::commonInit()
|
void DerivationTrampolineGoal::commonInit()
|
||||||
{
|
{
|
||||||
name =
|
name =
|
||||||
fmt("outer obtaining drv from '%s' and then building outputs %s",
|
fmt("obtaining derivation from '%s' and then building outputs %s",
|
||||||
drvReq->to_string(worker.store),
|
drvReq->to_string(worker.store),
|
||||||
std::visit(
|
std::visit(
|
||||||
overloaded{
|
overloaded{
|
||||||
|
|
@ -58,18 +58,12 @@ static StorePath pathPartOfReq(const SingleDerivedPath & req)
|
||||||
|
|
||||||
std::string DerivationTrampolineGoal::key()
|
std::string DerivationTrampolineGoal::key()
|
||||||
{
|
{
|
||||||
/* Ensure that derivations get built in order of their name,
|
return "da$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + DerivedPath::Built{
|
||||||
i.e. a derivation named "aardvark" always comes before "baboon". And
|
|
||||||
substitution goals, derivation goals, and derivation building goals always happen before
|
|
||||||
derivation goals (due to "bt$"). */
|
|
||||||
return "bt$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + DerivedPath::Built{
|
|
||||||
.drvPath = drvReq,
|
.drvPath = drvReq,
|
||||||
.outputs = wantedOutputs,
|
.outputs = wantedOutputs,
|
||||||
}.to_string(worker.store);
|
}.to_string(worker.store);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationTrampolineGoal::timedOut(Error && ex) {}
|
|
||||||
|
|
||||||
Goal::Co DerivationTrampolineGoal::init()
|
Goal::Co DerivationTrampolineGoal::init()
|
||||||
{
|
{
|
||||||
trace("need to load derivation from file");
|
trace("need to load derivation from file");
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,6 @@ Goal::Co DrvOutputSubstitutionGoal::realisationFetched(
|
||||||
|
|
||||||
std::string DrvOutputSubstitutionGoal::key()
|
std::string DrvOutputSubstitutionGoal::key()
|
||||||
{
|
{
|
||||||
/* "a$" ensures substitution goals happen before derivation
|
|
||||||
goals. */
|
|
||||||
return "a$" + std::string(id.to_string());
|
return "a$" + std::string(id.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "nix/store/build/substitution-goal.hh"
|
#include "nix/store/build/substitution-goal.hh"
|
||||||
#include "nix/store/build/drv-output-substitution-goal.hh"
|
#include "nix/store/build/drv-output-substitution-goal.hh"
|
||||||
#include "nix/store/build/derivation-goal.hh"
|
#include "nix/store/build/derivation-goal.hh"
|
||||||
|
#include "nix/store/build/derivation-resolution-goal.hh"
|
||||||
#include "nix/store/build/derivation-building-goal.hh"
|
#include "nix/store/build/derivation-building-goal.hh"
|
||||||
#include "nix/store/build/derivation-trampoline-goal.hh"
|
#include "nix/store/build/derivation-trampoline-goal.hh"
|
||||||
#ifndef _WIN32 // TODO Enable building on Windows
|
#ifndef _WIN32 // TODO Enable building on Windows
|
||||||
|
|
@ -80,6 +81,12 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(
|
||||||
return initGoalIfNeeded(derivationGoals[drvPath][wantedOutput], drvPath, drv, wantedOutput, *this, buildMode);
|
return initGoalIfNeeded(derivationGoals[drvPath][wantedOutput], drvPath, drv, wantedOutput, *this, buildMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DerivationResolutionGoal>
|
||||||
|
Worker::makeDerivationResolutionGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode)
|
||||||
|
{
|
||||||
|
return initGoalIfNeeded(derivationResolutionGoals[drvPath], drvPath, drv, *this, buildMode);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<DerivationBuildingGoal>
|
std::shared_ptr<DerivationBuildingGoal>
|
||||||
Worker::makeDerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode)
|
Worker::makeDerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode)
|
||||||
{
|
{
|
||||||
|
|
@ -158,6 +165,8 @@ void Worker::removeGoal(GoalPtr goal)
|
||||||
nix::removeGoal(drvGoal, derivationTrampolineGoals.map);
|
nix::removeGoal(drvGoal, derivationTrampolineGoals.map);
|
||||||
else if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
|
else if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
|
||||||
nix::removeGoal(drvGoal, derivationGoals);
|
nix::removeGoal(drvGoal, derivationGoals);
|
||||||
|
else if (auto drvResolutionGoal = std::dynamic_pointer_cast<DerivationResolutionGoal>(goal))
|
||||||
|
nix::removeGoal(drvResolutionGoal, derivationResolutionGoals);
|
||||||
else if (auto drvBuildingGoal = std::dynamic_pointer_cast<DerivationBuildingGoal>(goal))
|
else if (auto drvBuildingGoal = std::dynamic_pointer_cast<DerivationBuildingGoal>(goal))
|
||||||
nix::removeGoal(drvBuildingGoal, derivationBuildingGoals);
|
nix::removeGoal(drvBuildingGoal, derivationBuildingGoals);
|
||||||
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
|
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include "nix/store/derivations.hh"
|
||||||
|
#include "nix/store/derivation-options.hh"
|
||||||
|
#include "nix/store/build/derivation-building-misc.hh"
|
||||||
|
#include "nix/store/store-api.hh"
|
||||||
|
#include "nix/store/build/goal.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct BuilderFailureError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A goal for resolving a derivation. Resolving a derivation (@see
|
||||||
|
* `Derivation::tryResolve`) simplifies its inputs, replacing
|
||||||
|
* `inputDrvs` with `inputSrcs`.
|
||||||
|
*
|
||||||
|
* Conceptually, we resolve all derivations. For input-addressed
|
||||||
|
* derivations (that don't transtively depend on content-addressed
|
||||||
|
* derivations), however, we don't actually use the resolved derivation,
|
||||||
|
* because the output paths would appear invalid (if we tried to verify
|
||||||
|
* them), since they are computed from the original, unresolved inputs.
|
||||||
|
*
|
||||||
|
* That said, if we ever made the new flavor of input-addressing as described
|
||||||
|
* in issue #9259, then the input-addressing would be based on the resolved
|
||||||
|
* inputs, and we like the CA case *would* use the output of this goal.
|
||||||
|
*
|
||||||
|
* (The point of this discussion is not to randomly stuff information on
|
||||||
|
* a yet-unimplemented feature (issue #9259) in the codebase, but
|
||||||
|
* rather, to illustrate that there is no inherent tension between
|
||||||
|
* explicit derivation resolution and input-addressing in general. That
|
||||||
|
* tension only exists with the type of input-addressing we've
|
||||||
|
* historically used.)
|
||||||
|
*/
|
||||||
|
struct DerivationResolutionGoal : public Goal
|
||||||
|
{
|
||||||
|
DerivationResolutionGoal(
|
||||||
|
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode = bmNormal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the derivation needed to be resolved, this is resulting
|
||||||
|
* resolved derivations and its path.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<std::pair<StorePath, BasicDerivation>> resolvedDrv;
|
||||||
|
|
||||||
|
void timedOut(Error && ex) override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the derivation.
|
||||||
|
*/
|
||||||
|
StorePath drvPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The derivation stored at drvPath.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Derivation> drv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remainder is state held during the build.
|
||||||
|
*/
|
||||||
|
|
||||||
|
BuildMode buildMode;
|
||||||
|
|
||||||
|
std::unique_ptr<Activity> act;
|
||||||
|
|
||||||
|
std::string key() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The states.
|
||||||
|
*/
|
||||||
|
Co resolveDerivation();
|
||||||
|
|
||||||
|
JobCategory jobCategory() const override
|
||||||
|
{
|
||||||
|
return JobCategory::Administration;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -109,7 +109,7 @@ struct DerivationTrampolineGoal : public Goal
|
||||||
|
|
||||||
virtual ~DerivationTrampolineGoal();
|
virtual ~DerivationTrampolineGoal();
|
||||||
|
|
||||||
void timedOut(Error && ex) override;
|
void timedOut(Error && ex) override {}
|
||||||
|
|
||||||
std::string key() override;
|
std::string key() override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,18 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual void timedOut(Error && ex) = 0;
|
virtual void timedOut(Error && ex) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for comparisons. The order matters a bit for scheduling. We
|
||||||
|
* want:
|
||||||
|
*
|
||||||
|
* 1. Substitution
|
||||||
|
* 2. Derivation administrativia
|
||||||
|
* 3. Actual building
|
||||||
|
*
|
||||||
|
* Also, ensure that derivations get processed in order of their
|
||||||
|
* name, i.e. a derivation named "aardvark" always comes before
|
||||||
|
* "baboon".
|
||||||
|
*/
|
||||||
virtual std::string key() = 0;
|
virtual std::string key() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,6 @@ public:
|
||||||
unreachable();
|
unreachable();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* We prepend "a$" to the key name to ensure substitution goals
|
|
||||||
* happen before derivation goals.
|
|
||||||
*/
|
|
||||||
std::string key() override
|
std::string key() override
|
||||||
{
|
{
|
||||||
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace nix {
|
||||||
/* Forward definition. */
|
/* Forward definition. */
|
||||||
struct DerivationTrampolineGoal;
|
struct DerivationTrampolineGoal;
|
||||||
struct DerivationGoal;
|
struct DerivationGoal;
|
||||||
|
struct DerivationResolutionGoal;
|
||||||
struct DerivationBuildingGoal;
|
struct DerivationBuildingGoal;
|
||||||
struct PathSubstitutionGoal;
|
struct PathSubstitutionGoal;
|
||||||
class DrvOutputSubstitutionGoal;
|
class DrvOutputSubstitutionGoal;
|
||||||
|
|
@ -111,6 +112,7 @@ private:
|
||||||
DerivedPathMap<std::map<OutputsSpec, std::weak_ptr<DerivationTrampolineGoal>>> derivationTrampolineGoals;
|
DerivedPathMap<std::map<OutputsSpec, std::weak_ptr<DerivationTrampolineGoal>>> derivationTrampolineGoals;
|
||||||
|
|
||||||
std::map<StorePath, std::map<OutputName, std::weak_ptr<DerivationGoal>>> derivationGoals;
|
std::map<StorePath, std::map<OutputName, std::weak_ptr<DerivationGoal>>> derivationGoals;
|
||||||
|
std::map<StorePath, std::weak_ptr<DerivationResolutionGoal>> derivationResolutionGoals;
|
||||||
std::map<StorePath, std::weak_ptr<DerivationBuildingGoal>> derivationBuildingGoals;
|
std::map<StorePath, std::weak_ptr<DerivationBuildingGoal>> derivationBuildingGoals;
|
||||||
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
|
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
|
||||||
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
|
||||||
|
|
@ -224,7 +226,13 @@ public:
|
||||||
BuildMode buildMode = bmNormal);
|
BuildMode buildMode = bmNormal);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ref DerivationBuildingGoal "derivation goal"
|
* @ref DerivationResolutionGoal "derivation resolution goal"
|
||||||
|
*/
|
||||||
|
std::shared_ptr<DerivationResolutionGoal>
|
||||||
|
makeDerivationResolutionGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode = bmNormal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ref DerivationBuildingGoal "derivation building goal"
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<DerivationBuildingGoal>
|
std::shared_ptr<DerivationBuildingGoal>
|
||||||
makeDerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode = bmNormal);
|
makeDerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode = bmNormal);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ headers = [ config_pub_h ] + files(
|
||||||
'build/derivation-building-misc.hh',
|
'build/derivation-building-misc.hh',
|
||||||
'build/derivation-env-desugar.hh',
|
'build/derivation-env-desugar.hh',
|
||||||
'build/derivation-goal.hh',
|
'build/derivation-goal.hh',
|
||||||
|
'build/derivation-resolution-goal.hh',
|
||||||
'build/derivation-trampoline-goal.hh',
|
'build/derivation-trampoline-goal.hh',
|
||||||
'build/drv-output-substitution-goal.hh',
|
'build/drv-output-substitution-goal.hh',
|
||||||
'build/goal.hh',
|
'build/goal.hh',
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@ sources = files(
|
||||||
'build/derivation-check.cc',
|
'build/derivation-check.cc',
|
||||||
'build/derivation-env-desugar.cc',
|
'build/derivation-env-desugar.cc',
|
||||||
'build/derivation-goal.cc',
|
'build/derivation-goal.cc',
|
||||||
|
'build/derivation-resolution-goal.cc',
|
||||||
'build/derivation-trampoline-goal.cc',
|
'build/derivation-trampoline-goal.cc',
|
||||||
'build/drv-output-substitution-goal.cc',
|
'build/drv-output-substitution-goal.cc',
|
||||||
'build/entry-points.cc',
|
'build/entry-points.cc',
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,8 @@ test "$(<<<"$out" grep -cE '^error:')" = 4
|
||||||
|
|
||||||
out="$(nix build -f fod-failing.nix -L x4 2>&1)" && status=0 || status=$?
|
out="$(nix build -f fod-failing.nix -L x4 2>&1)" && status=0 || status=$?
|
||||||
test "$status" = 1
|
test "$status" = 1
|
||||||
test "$(<<<"$out" grep -cE '^error:')" = 2
|
# Precise number of errors depends on daemon version / goal refactorings
|
||||||
|
(( "$(<<<"$out" grep -cE '^error:')" >= 2 ))
|
||||||
|
|
||||||
if isDaemonNewer "2.29pre"; then
|
if isDaemonNewer "2.29pre"; then
|
||||||
<<<"$out" grepQuiet -E "error: Cannot build '.*-x4\\.drv'"
|
<<<"$out" grepQuiet -E "error: Cannot build '.*-x4\\.drv'"
|
||||||
|
|
@ -186,11 +187,13 @@ if isDaemonNewer "2.29pre"; then
|
||||||
else
|
else
|
||||||
<<<"$out" grepQuiet -E "error: 1 dependencies of derivation '.*-x4\\.drv' failed to build"
|
<<<"$out" grepQuiet -E "error: 1 dependencies of derivation '.*-x4\\.drv' failed to build"
|
||||||
fi
|
fi
|
||||||
<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x2\\.drv'"
|
# Either x2 or x3 could have failed, x4 depends on both symmetrically
|
||||||
|
<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x[23]\\.drv'"
|
||||||
|
|
||||||
out="$(nix build -f fod-failing.nix -L x4 --keep-going 2>&1)" && status=0 || status=$?
|
out="$(nix build -f fod-failing.nix -L x4 --keep-going 2>&1)" && status=0 || status=$?
|
||||||
test "$status" = 1
|
test "$status" = 1
|
||||||
test "$(<<<"$out" grep -cE '^error:')" = 3
|
# Precise number of errors depends on daemon version / goal refactorings
|
||||||
|
(( "$(<<<"$out" grep -cE '^error:')" >= 3 ))
|
||||||
if isDaemonNewer "2.29pre"; then
|
if isDaemonNewer "2.29pre"; then
|
||||||
<<<"$out" grepQuiet -E "error: Cannot build '.*-x4\\.drv'"
|
<<<"$out" grepQuiet -E "error: Cannot build '.*-x4\\.drv'"
|
||||||
<<<"$out" grepQuiet -E "Reason: 2 dependencies failed."
|
<<<"$out" grepQuiet -E "Reason: 2 dependencies failed."
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue