Skip to content

Commit

Permalink
Adapt scheduler to work with dynamic derivations
Browse files Browse the repository at this point in the history
To avoid dealing with an optional `drvPath` (because we might not know
it yet) everywhere, make an `OuterDerivationGoal`. This goal just
builds/substitutes the derivation file, and then kicks of a build for
that obtained derivation; in other words it does the chaining of goals
when the drv file is missing (as can already be the case) or computed
(new case).

This also means the `getDerivation` state can be removed from
`DerivationGoal`, which makes the `BasicDerivation` / in memory case and
`Derivation` / drv file file case closer together.
  • Loading branch information
Ericson2314 committed Aug 16, 2023
1 parent bcf0b49 commit 4e3db84
Show file tree
Hide file tree
Showing 9 changed files with 336 additions and 44 deletions.
32 changes: 11 additions & 21 deletions src/libstore/build/derivation-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
, wantedOutputs(wantedOutputs)
, buildMode(buildMode)
{
state = &DerivationGoal::getDerivation;
state = &DerivationGoal::loadDerivation;
name = fmt(
"building of '%s' from .drv file",
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
Expand Down Expand Up @@ -164,24 +164,6 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
}


void DerivationGoal::getDerivation()
{
trace("init");

/* The first thing to do is to make sure that the derivation
exists. If it doesn't, it may be created through a
substitute. */
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
loadDerivation();
return;
}

addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));

state = &DerivationGoal::loadDerivation;
}


void DerivationGoal::loadDerivation()
{
trace("loading derivation");
Expand Down Expand Up @@ -1484,6 +1466,11 @@ void DerivationGoal::done(
amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
}

}

#include "outer-derivation-goal.hh"

namespace nix {

void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
{
Expand All @@ -1492,8 +1479,11 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
if (!useDerivation) return;
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());

auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
if (!dg) return;
auto * odg = dynamic_cast<OuterDerivationGoal *>(&*waitee);
if (!odg) return;
auto * dg = &*odg->concreteDrvGoal;
// By the time we get here, the outer goals should have.
assert(dg);

auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
if (outputs == fullDrv.inputDrvs.end()) return;
Expand Down
1 change: 0 additions & 1 deletion src/libstore/build/derivation-goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ struct DerivationGoal : public Goal
/**
* The states.
*/
void getDerivation();
void loadDerivation();
void haveDerivation();
void outputsSubstitutionTried();
Expand Down
11 changes: 7 additions & 4 deletions src/libstore/build/entry-points.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "worker.hh"
#include "substitution-goal.hh"
#include "outer-derivation-goal.hh"
#include "derivation-goal.hh"
#include "local-store.hh"

Expand All @@ -15,7 +16,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod

worker.run(goals);

StorePathSet failed;
StringSet failed;
std::optional<Error> ex;
for (auto & i : goals) {
if (i->ex) {
Expand All @@ -25,8 +26,10 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
ex = std::move(i->ex);
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
if (auto i2 = dynamic_cast<OuterDerivationGoal *>(i.get()))
failed.insert(i2->drvReq->to_string(*this));
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
failed.insert(printStorePath(i2->storePath));
}
}

Expand All @@ -35,7 +38,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
throw std::move(*ex);
} else if (!failed.empty()) {
if (ex) logError(ex->info());
throw Error(worker.failingExitStatus(), "build of %s failed", showPaths(failed));
throw Error(worker.failingExitStatus(), "build of %s failed", concatStringsSep(", ", quoteStrings(failed)));
}
}

Expand Down
155 changes: 155 additions & 0 deletions src/libstore/build/outer-derivation-goal.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include "outer-derivation-goal.hh"
#include "worker.hh"

namespace nix {

OuterDerivationGoal::OuterDerivationGoal(ref<SingleDerivedPath> drvReq,
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvReq, .outputs = wantedOutputs })
, drvReq(drvReq)
, wantedOutputs(wantedOutputs)
, buildMode(buildMode)
{
state = &OuterDerivationGoal::getDerivation;
name = fmt(
"outer obtaining drv from '%s' and then building outputs %s",
drvReq->to_string(worker.store),
std::visit(overloaded {
[&](const OutputsSpec::All) -> std::string {
return "* (all of them)";
},
[&](const OutputsSpec::Names os) {
return concatStringsSep(", ", quoteStrings(os));
},
}, wantedOutputs.raw()));
trace("created outer");

worker.updateProgress();
}


OuterDerivationGoal::~OuterDerivationGoal()
{
}


static StorePath pathPartOfReq(const SingleDerivedPath & req)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) {
return bo.path;
},
[&](const SingleDerivedPath::Built & bfd) {
return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}


std::string OuterDerivationGoal::key()
{
/* Ensure that derivations get built in order of their name,
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()) + "$" + drvReq->to_string(worker.store);
}


void OuterDerivationGoal::timedOut(Error && ex)
{
}


void OuterDerivationGoal::work()
{
(this->*state)();
}


void OuterDerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
{
/* If we already want all outputs, there is nothing to do. */
auto newWanted = wantedOutputs.union_(outputs);
bool needRestart = !newWanted.isSubsetOf(wantedOutputs);
wantedOutputs = newWanted;

if (!needRestart) return;

if (!optDrvPath)
// haven't started steps where the outputs matter yet
return;
worker.makeDerivationGoal(*optDrvPath, outputs, buildMode);
}


void OuterDerivationGoal::getDerivation()
{
trace("outer init");

/* The first thing to do is to make sure that the derivation
exists. If it doesn't, it may be created through a
substitute. */
{
if (buildMode != bmNormal) goto load;
auto drvReq2 = tryResolveDerivedPath(worker.store, *drvReq);
auto drvPathP = std::get_if<DerivedPath::Opaque>(&drvReq2);
if (!drvPathP) goto load;
auto & drvPath = drvPathP->path;
if (!worker.evalStore.isValidPath(drvPath) && !worker.store.isValidPath(drvPath))
goto load;

trace(fmt("already have drv '%s' for '%s', can go straight to building",
worker.store.printStorePath(drvPath),
drvReq->to_string(worker.store)));

loadAndBuildDerivation();
return;
}

load:
trace("need to obtain drv we want to build");

addWaitee(worker.makeGoal(DerivedPath::fromSingle(*drvReq)));

state = &OuterDerivationGoal::loadAndBuildDerivation;
if (waitees.empty()) work();
}


void OuterDerivationGoal::loadAndBuildDerivation()
{
trace("outer load and build derivation");

if (nrFailed != 0) {
amDone(ecFailed, Error("cannot build missing derivation '%s'", drvReq->to_string(worker.store)));
return;
}

StorePath drvPath = resolveDerivedPath(worker.store, *drvReq);
/* Build this step! */
concreteDrvGoal = worker.makeDerivationGoal(drvPath, wantedOutputs, buildMode);
addWaitee(upcast_goal(concreteDrvGoal));
state = &OuterDerivationGoal::buildDone;
optDrvPath = std::move(drvPath);
if (waitees.empty()) work();
}


void OuterDerivationGoal::buildDone()
{
trace("outer build done");

buildResult = upcast_goal(concreteDrvGoal)->getBuildResult(DerivedPath::Built {
.drvPath = drvReq,
.outputs = wantedOutputs,
});

if (buildResult.success())
amDone(ecSuccess);
else
amDone(ecFailed, Error("building '%s' failed", drvReq->to_string(worker.store)));
}


}
65 changes: 65 additions & 0 deletions src/libstore/build/outer-derivation-goal.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

#include "parsed-derivations.hh"
#include "lock.hh"
#include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh"

namespace nix {

struct DerivationGoal;

struct OuterDerivationGoal : public Goal
{
/* How to obtain a store path of the derivation to build. */
ref<SingleDerivedPath> drvReq;

/* The path of the derivation, once obtained. */
std::optional<StorePath> optDrvPath;

/* The goal for the corresponding concrete derivation */
std::shared_ptr<DerivationGoal> concreteDrvGoal;

/* The specific outputs that we need to build. Empty means all of
them. */
OutputsSpec wantedOutputs;

typedef void (OuterDerivationGoal::*GoalState)();
GoalState state;

/* The final output paths of the build.
- For input-addressed derivations, always the precomputed paths
- For content-addressed derivations, calcuated from whatever the hash
ends up being. (Note that fixed outputs derivations that produce the
"wrong" output still install that data under its true content-address.)
*/
OutputPathMap finalOutputs;

BuildMode buildMode;

OuterDerivationGoal(ref<SingleDerivedPath> drvReq,
const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
virtual ~OuterDerivationGoal();

void timedOut(Error && ex) override;

std::string key() override;

void work() override;

/* Add wanted outputs to an already existing derivation goal. */
void addWantedOutputs(const OutputsSpec & outputs);

/* The states. */
void getDerivation();
void loadAndBuildDerivation();
void buildDone();

JobCategory jobCategory() override { return JobCategory::Build; };
};

}
Loading

0 comments on commit 4e3db84

Please sign in to comment.