1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-24 22:11:15 +02:00

WIP rework goals

This commit is contained in:
John Ericson 2025-02-15 13:34:38 -05:00
parent d80d68034c
commit 7b01c8b8cd
14 changed files with 498 additions and 98 deletions

View file

@ -0,0 +1,208 @@
#include "nix/store/build/build-trace-goal.hh"
#include "nix/util/finally.hh"
#include "nix/store/build/worker.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/util/callback.hh"
#include "nix/util/util.hh"
#include "nix/store/derivations.hh"
#include "nix/store/store-open.hh"
#include "nix/store/build/derivation-resolution-goal.hh"
namespace nix {
BuildTraceGoal::BuildTraceGoal(const SingleDerivedPath::Built & id, Worker & worker)
: Goal{worker, init()}
, id{id}
{
name = fmt("substitution of '%s'", id.to_string(worker.store));
trace("created");
}
Goal::Co BuildTraceGoal::init()
{
trace("init");
DrvOutput id2{
.drvPath = StorePath::dummy,
.outputName = id.output,
};
// No `std::visit` with coroutines :(
if (const auto * path = std::get_if<SingleDerivedPath::Opaque>(&*id.drvPath)) {
// At least we know the drv path statically, can procede
id2.drvPath = path->path;
} else if (const auto * outputDeriving = std::get_if<SingleDerivedPath::Built>(&*id.drvPath)) {
// Dynamic derivation case, need to resolve that first.
auto g = worker.makeBuildTraceGoal({
outputDeriving->drvPath,
outputDeriving->output,
});
co_await await(Goals{upcast_goal(g)});
if (nrFailed > 0) {
debug("The output deriving path '%s' could not be resolved", outputDeriving->to_string(worker.store));
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
}
id2.drvPath = g->outputInfo->outPath;
}
/* If the derivation already exists, were done */
if ((outputInfo = worker.store.queryRealisation(id2))) {
co_return amDone(ecSuccess);
}
/**
* Firstly, whether we know the status, secondly, what it is
*/
std::optional<bool> drvIsResolved;
/* If the derivation has statically-known output paths */
if (worker.evalStore.isValidPath(id2.drvPath)) {
auto drv = worker.evalStore.readDerivation(id2.drvPath);
auto os = drv.outputsAndOptPaths(worker.store);
/* Mark what we now know */
drvIsResolved = {drv.inputDrvs.map.empty()};
if (auto * p = get(os, id2.outputName)) {
if (auto & outPath = p->second) {
outputInfo = std::make_shared<UnkeyedRealisation>(*outPath);
co_return amDone(ecSuccess);
} else {
/* Otherwise, not failure, just looking up build trace below. */
}
} else {
debug(
"Derivation '%s' does not have output '%s', impossible to find build trace key-value pair",
worker.store.printStorePath(id2.drvPath),
id2.outputName);
co_return amDone(ecFailed);
}
}
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
bool substituterFailed = false;
if (!drvIsResolved || *drvIsResolved) {
/* Since derivation might be resolved --- isn't known to be
not-resolved, it might have entries. So, let's try querying
the substituters. */
for (const auto & sub : subs) {
trace("trying next substituter");
/* The callback of the curl download below can outlive `this` (if
some other error occurs), so it must not touch `this`. So put
the shared state in a separate refcounted object. */
auto outPipe = std::make_shared<MuxablePipe>();
#ifndef _WIN32
outPipe->create();
#else
outPipe->createAsyncPipe(worker.ioport.get());
#endif
auto promise = std::make_shared<std::promise<std::shared_ptr<const UnkeyedRealisation>>>();
sub->queryRealisation(
id2, {[outPipe(outPipe), promise(promise)](std::future<std::shared_ptr<const UnkeyedRealisation>> res) {
try {
Finally updateStats([&]() { outPipe->writeSide.close(); });
promise->set_value(res.get());
} catch (...) {
promise->set_exception(std::current_exception());
}
}});
worker.childStarted(
shared_from_this(),
{
#ifndef _WIN32
outPipe->readSide.get()
#else
&*outPipe
#endif
},
true,
false);
co_await Suspend{};
worker.childTerminated(this);
std::shared_ptr<const UnkeyedRealisation> outputInfo;
try {
outputInfo = promise->get_future().get();
} catch (std::exception & e) {
printError(e.what());
substituterFailed = true;
}
if (!outputInfo)
continue;
worker.store.registerDrvOutput({*outputInfo, id2});
trace("finished");
co_return amDone(ecSuccess);
}
}
/* Derivation might not be resolved, let's try doing that */
trace("trying resolving derivation in build-trace goal");
auto g = worker.makeDerivationResolutionGoal(id2.drvPath);
co_await await(Goals{g});
if (nrFailed > 0) {
/* None left. Terminate this goal and let someone else deal
with it. */
debug(
"derivation output '%s' is required, but there is no substituter that can provide it",
id2.render(worker.store));
if (substituterFailed) {
worker.failedSubstitutions++;
worker.updateProgress();
}
/* Hack: don't indicate failure if there were no substituters.
In that case the calling derivation should just do a
build. */
co_return amDone(substituterFailed ? ecFailed : ecNoSubstituters);
}
/* This should be set if the goal succeeded */
assert(g->resolvedDrv);
/* Try everything again, now with a resolved derivation */
auto bt2 = worker.makeBuildTraceGoal({
makeConstantStorePathRef(g->resolvedDrvPath),
id2.outputName,
});
co_await await(Goals{bt2});
/* Set the build trace value as our own. Note the signure will not
match our key since we're the unresolved derivation, but that's
fine. We're not writing it to the DB; that's `bt2`' job. */
if (bt2->outputInfo)
outputInfo = bt2->outputInfo;
co_return amDone(bt2->exitCode, bt2->ex);
}
std::string BuildTraceGoal::key()
{
/* "a$" ensures substitution goals happen before derivation
goals. */
return "a$" + std::string(id.to_string(worker.store));
}
void BuildTraceGoal::handleEOF(Descriptor fd)
{
worker.wakeUp(shared_from_this());
}
}

View file

@ -62,7 +62,7 @@ std::string DerivationCreationAndRealisationGoal::key()
i.e. a derivation named "aardvark" always comes before "baboon". And
substitution goals and inner derivation goals always happen before
derivation goals (due to "b$"). */
return "c$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + DerivedPath::Built{
return "d$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + DerivedPath::Built{
.drvPath = drvReq,
.outputs = wantedOutputs,
}.to_string(worker.store);

View file

@ -50,7 +50,7 @@ std::string DerivationGoal::key()
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{
return "c$" + std::string(drvPath.name()) + "$" + SingleDerivedPath::Built{
.drvPath = makeConstantStorePathRef(drvPath),
.output = wantedOutput,
}.to_string(worker.store);

View file

@ -0,0 +1,82 @@
#include "nix/store/build/derivation-resolution-goal.hh"
#include "nix/util/finally.hh"
#include "nix/store/build/worker.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/util/callback.hh"
#include "nix/store/derivations.hh"
namespace nix {
DerivationResolutionGoal::DerivationResolutionGoal(const StorePath & drvPath, Worker & worker)
: Goal(worker, init())
, drvPath(drvPath)
{
name = fmt("resolution of '%s'", worker.store.printStorePath(drvPath));
trace("created");
}
Goal::Co DerivationResolutionGoal::init()
{
trace("init");
std::unique_ptr<Derivation> drv;
if (worker.evalStore.isValidPath(drvPath)) {
drv = std::make_unique<Derivation>(worker.evalStore.readDerivation(drvPath));
} else if (worker.store.isValidPath(drvPath)) {
drv = std::make_unique<Derivation>(worker.store.readDerivation(drvPath));
} else {
auto goal0 = worker.makePathSubstitutionGoal(drvPath);
goal0->preserveException = true;
co_await await(Goals{goal0});
if (nrFailed > 0)
co_return amDone(goal0->exitCode, goal0->ex);
drv = std::make_unique<Derivation>(worker.store.readDerivation(drvPath));
}
trace("output path substituted");
std::set<std::shared_ptr<BuildTraceGoal>> goals;
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
accumInputPaths = [&](ref<SingleDerivedPath> depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
for (auto & outputName : inputNode.value)
goals.insert(worker.makeBuildTraceGoal(SingleDerivedPath::Built{depDrvPath, outputName}));
for (auto & [outputName, childNode] : inputNode.childMap)
accumInputPaths(make_ref<SingleDerivedPath>(SingleDerivedPath::Built{depDrvPath, outputName}), childNode);
};
for (auto & [depDrvPath, depNode] : drv->inputDrvs.map)
accumInputPaths(makeConstantStorePathRef(depDrvPath), depNode);
if (nrFailed > 0) {
debug("TODO message");
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
}
if (true /*auto d = drv.tryResolve(....)*/) {
//resolvedDerivation = d.take();
trace("finished");
co_return amDone(ecSuccess);
} else {
// fail
}
}
std::string DerivationResolutionGoal::key()
{
/* "a$" ensures substitution goals happen before derivation
goals. */
return "b$" + worker.store.printStorePath(drvPath);
}
void DerivationResolutionGoal::handleEOF(Descriptor fd)
{
worker.wakeUp(shared_from_this());
}
}

View file

@ -3,7 +3,7 @@
#include "nix/store/build/worker.hh"
#include "nix/store/build/substitution-goal.hh"
#include "nix/util/callback.hh"
#include "nix/store/store-open.hh"
#include "nix/store/build/build-trace-goal.hh"
namespace nix {
@ -20,101 +20,32 @@ Goal::Co DrvOutputSubstitutionGoal::init()
{
trace("init");
/* If the derivation already exists, were done */
if (worker.store.queryRealisation(id)) {
co_return amDone(ecSuccess);
auto goal0 = worker.makeBuildTraceGoal({
makeConstantStorePathRef(id.drvPath),
id.outputName,
});
co_await await(Goals{upcast_goal(goal0)});
trace("output path substituted");
if (nrFailed > 0) {
debug("The output path of the derivation output '%s' could not be substituted", id.render(worker.store));
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
}
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
auto goal1 = worker.makePathSubstitutionGoal(goal0->outputInfo->outPath);
goal0.reset();
bool substituterFailed = false;
for (const auto & sub : subs) {
trace("trying next substituter");
/* The callback of the curl download below can outlive `this` (if
some other error occurs), so it must not touch `this`. So put
the shared state in a separate refcounted object. */
auto outPipe = std::make_shared<MuxablePipe>();
#ifndef _WIN32
outPipe->create();
#else
outPipe->createAsyncPipe(worker.ioport.get());
#endif
auto promise = std::make_shared<std::promise<std::shared_ptr<const UnkeyedRealisation>>>();
sub->queryRealisation(
id, {[outPipe(outPipe), promise(promise)](std::future<std::shared_ptr<const UnkeyedRealisation>> res) {
try {
Finally updateStats([&]() { outPipe->writeSide.close(); });
promise->set_value(res.get());
} catch (...) {
promise->set_exception(std::current_exception());
}
}});
worker.childStarted(
shared_from_this(),
{
#ifndef _WIN32
outPipe->readSide.get()
#else
&*outPipe
#endif
},
true,
false);
co_await Suspend{};
worker.childTerminated(this);
try {
outputInfo = promise->get_future().get();
} catch (std::exception & e) {
printError(e.what());
substituterFailed = true;
}
if (!outputInfo)
continue;
co_await await(Goals{worker.makePathSubstitutionGoal(outputInfo->outPath)});
trace("output path substituted");
if (nrFailed > 0) {
debug("The output path of the derivation output '%s' could not be substituted", id.render(worker.store));
co_return amDone(nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed);
}
worker.store.registerDrvOutput({*outputInfo, id});
trace("finished");
co_return amDone(ecSuccess);
}
/* None left. Terminate this goal and let someone else deal
with it. */
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.render(worker.store));
if (substituterFailed) {
worker.failedSubstitutions++;
worker.updateProgress();
}
/* Hack: don't indicate failure if there were no substituters.
In that case the calling derivation should just do a
build. */
co_return amDone(substituterFailed ? ecFailed : ecNoSubstituters);
goal1->preserveException = true;
co_await await(Goals{upcast_goal(goal1)});
co_return amDone(goal1->exitCode, goal1->ex);
}
std::string DrvOutputSubstitutionGoal::key()
{
/* "a$" ensures substitution goals happen before derivation
goals. */
return "a$" + std::string(id.render(worker.store));
return "b$" + std::string(id.render(worker.store));
}
void DrvOutputSubstitutionGoal::handleEOF(Descriptor fd)

View file

@ -9,6 +9,8 @@
#ifndef _WIN32 // TODO Enable building on Windows
# include "nix/store/build/hook-instance.hh"
#endif
#include "nix/store/build/build-trace-goal.hh"
#include "nix/store/build/derivation-resolution-goal.hh"
#include "nix/util/signals.hh"
namespace nix {
@ -103,6 +105,33 @@ std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal
}
std::shared_ptr<BuildTraceGoal> Worker::makeBuildTraceGoal(
const SingleDerivedPath::Built & req)
{
std::weak_ptr<BuildTraceGoal> & goal_weak = buildTraceGoals.ensureSlot(req).value;
std::shared_ptr<BuildTraceGoal> goal = goal_weak.lock();
if (!goal) {
goal = std::make_shared<BuildTraceGoal>(req, *this);
goal_weak = goal;
wakeUp(goal);
}
return goal;
}
std::shared_ptr<DerivationResolutionGoal> Worker::makeDerivationResolutionGoal(const StorePath & drvPath)
{
std::weak_ptr<DerivationResolutionGoal> & goal_weak = derivationResolutionGoals[drvPath];
auto goal = goal_weak.lock(); // FIXME
if (!goal) {
goal = std::make_shared<DerivationResolutionGoal>(drvPath, *this);
goal_weak = goal;
wakeUp(goal);
}
return goal;
}
GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
{
return std::visit(overloaded {
@ -576,4 +605,14 @@ GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal)
return subGoal;
}
GoalPtr upcast_goal(std::shared_ptr<BuildTraceGoal> subGoal)
{
return subGoal;
}
GoalPtr upcast_goal(std::shared_ptr<DerivationResolutionGoal> subGoal)
{
return subGoal;
}
}

View file

@ -8,7 +8,11 @@ create table if not exists Realisations (
outputName text not null, -- symbolic output id, usually "out"
outputPath integer not null,
signatures text, -- space-separated list
foreign key (outputPath) references ValidPaths(id) on delete cascade
-- No such foreign key because we may well want realisations for
-- garbage-collected dependencies
--foreign key (outputPath) references ValidPaths(id) on delete cascade
);
create index if not exists IndexRealisations on Realisations(drvPath, outputName);

View file

@ -52,7 +52,9 @@ typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const Single
// instantiations
#include "nix/store/build/build-trace-goal.hh"
#include "nix/store/build/derivation-creation-and-realisation-goal.hh"
namespace nix {
template<>
@ -69,7 +71,7 @@ std::strong_ordering DerivedPathMap<StringSet>::ChildNode::operator <=> (
template struct DerivedPathMap<StringSet>::ChildNode;
template struct DerivedPathMap<StringSet>;
template struct DerivedPathMap<std::weak_ptr<BuildTraceGoal>>;
template struct DerivedPathMap<std::map<OutputsSpec, std::weak_ptr<DerivationCreationAndRealisationGoal>>>;
};

View file

@ -0,0 +1,54 @@
#pragma once
///@file
#include <thread>
#include <future>
#include "nix/store/store-api.hh"
#include "nix/store/build/goal.hh"
#include "nix/store/realisation.hh"
#include "nix/util/muxable-pipe.hh"
namespace nix {
class Worker;
/**
* Try to recursively obtain build trace key-value pairs in order to
* resolve the given output deriving path.
*/
class BuildTraceGoal : public Goal
{
/**
* The output derivation path we're trying to reasolve.
*/
SingleDerivedPath::Built id;
public:
BuildTraceGoal(const SingleDerivedPath::Built & id, Worker & worker);
/**
* The realisation corresponding to the given output id.
* Will be filled once we can get it.
*/
std::shared_ptr<const UnkeyedRealisation> outputInfo;
Co init();
void timedOut(Error && ex) override
{
unreachable();
};
std::string key() override;
void handleEOF(Descriptor fd) override;
JobCategory jobCategory() const override
{
return JobCategory::Substitution;
};
};
}

View file

@ -0,0 +1,60 @@
#pragma once
///@file
#include <thread>
#include <future>
#include "nix/store/store-api.hh"
#include "nix/store/build/goal.hh"
#include "nix/store/realisation.hh"
#include "nix/util/muxable-pipe.hh"
namespace nix {
class Worker;
/**
* The purpose of this is to resolve the given derivation, so that it
* only has constant deriving paths as inputs.
*/
class DerivationResolutionGoal : public Goal
{
/**
* The derivation we're trying to substitute
*/
StorePath drvPath;
public:
DerivationResolutionGoal(const StorePath & storePath, Worker & worker);
/**
* The resolved derivation, if we succeeded.
*/
std::shared_ptr<BasicDerivation> resolvedDrv;
/**
* The path to derivation above, if we succeeded.
*
* Garbage that should not be read otherwise.
*/
StorePath resolvedDrvPath = StorePath::dummy;
Co init();
void timedOut(Error && ex) override
{
unreachable();
};
std::string key() override;
void handleEOF(Descriptor fd) override;
JobCategory jobCategory() const override
{
return JobCategory::Substitution;
};
};
}

View file

@ -35,12 +35,6 @@ public:
RepairFlag repair = NoRepair,
std::optional<ContentAddress> ca = std::nullopt);
/**
* The realisation corresponding to the given output id.
* Will be filled once we can get it.
*/
std::shared_ptr<const UnkeyedRealisation> outputInfo;
Co init();
void timedOut(Error && ex) override

View file

@ -19,6 +19,8 @@ struct DerivationGoal;
struct DerivationBuildingGoal;
struct PathSubstitutionGoal;
class DrvOutputSubstitutionGoal;
class BuildTraceGoal;
class DerivationResolutionGoal;
/**
* Workaround for not being able to declare a something like
@ -35,6 +37,8 @@ class DrvOutputSubstitutionGoal;
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal);
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal);
GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal);
GoalPtr upcast_goal(std::shared_ptr<BuildTraceGoal> subGoal);
GoalPtr upcast_goal(std::shared_ptr<DerivationResolutionGoal> subGoal);
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
@ -108,12 +112,14 @@ private:
* same derivation / path.
*/
DerivedPathMap<std::weak_ptr<BuildTraceGoal>> buildTraceGoals;
DerivedPathMap<std::map<OutputsSpec, std::weak_ptr<DerivationCreationAndRealisationGoal>>> derivationCreationAndRealisationGoals;
std::map<StorePath, std::map<OutputName, std::weak_ptr<DerivationGoal>>> derivationGoals;
std::map<StorePath, std::weak_ptr<DerivationBuildingGoal>> derivationBuildingGoals;
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
std::map<StorePath, std::weak_ptr<DerivationResolutionGoal>> derivationResolutionGoals;
/**
* Goals waiting for busy paths to be unlocked.
@ -230,11 +236,26 @@ public:
BuildMode buildMode = bmNormal);
/**
* @ref PathSubstitutionGoal "substitution goal"
* @ref PathSubstitutionGoal "path substitution goal"
*/
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
/**
* @ref DrvOutputSubstitutionGoal "derivation output substitution goal"
*/
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
/**
* @ref BuildTraceGoal "derivation output substitution goal"
*/
std::shared_ptr<BuildTraceGoal> makeBuildTraceGoal(
const SingleDerivedPath::Built & key);
/**
* @ref DerivationResolutionGoal "derivation resolution goal"
*/
std::shared_ptr<DerivationResolutionGoal> makeDerivationResolutionGoal(const StorePath & drvPath);
/**
* Make a goal corresponding to the `DerivedPath`.
*

View file

@ -12,10 +12,12 @@ config_pub_h = configure_file(
headers = [config_pub_h] + files(
'binary-cache-store.hh',
'build-result.hh',
'build/build-trace-goal.hh',
'build/derivation-goal.hh',
'build/derivation-building-goal.hh',
'build/derivation-building-misc.hh',
'build/derivation-creation-and-realisation-goal.hh',
'build/derivation-resolution-goal.hh',
'build/drv-output-substitution-goal.hh',
'build/goal.hh',
'build/substitution-goal.hh',

View file

@ -253,9 +253,12 @@ subdir('nix-meson-build-support/common')
sources = files(
'binary-cache-store.cc',
'build-result.cc',
'build/build-trace-goal.cc',
'build/derivation-goal.cc',
'build/derivation-building-goal.cc',
'build/derivation-creation-and-realisation-goal.cc',
'build/derivation-goal.cc',
'build/derivation-resolution-goal.cc',
'build/drv-output-substitution-goal.cc',
'build/entry-points.cc',
'build/goal.cc',