Skip to content

Commit 5e3986f

Browse files
Ericson2314roberth
andcommitted
Adapt scheduler to work with dynamic derivations
To avoid dealing with an optional `drvPath` (because we might not know it yet) everywhere, make an `CreateDerivationAndRealiseGoal`. 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. The map type is factored out for clarity, and because we will soon hvae a second use for it (`Derivation` itself). Co-authored-by: Robert Hensing <[email protected]>
1 parent 692074f commit 5e3986f

14 files changed

+523
-56
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#include "create-derivation-and-realise-goal.hh"
2+
#include "worker.hh"
3+
4+
namespace nix {
5+
6+
CreateDerivationAndRealiseGoal::CreateDerivationAndRealiseGoal(ref<SingleDerivedPath> drvReq,
7+
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
8+
: Goal(worker, DerivedPath::Built { .drvPath = drvReq, .outputs = wantedOutputs })
9+
, drvReq(drvReq)
10+
, wantedOutputs(wantedOutputs)
11+
, buildMode(buildMode)
12+
{
13+
state = &CreateDerivationAndRealiseGoal::getDerivation;
14+
name = fmt(
15+
"outer obtaining drv from '%s' and then building outputs %s",
16+
drvReq->to_string(worker.store),
17+
std::visit(overloaded {
18+
[&](const OutputsSpec::All) -> std::string {
19+
return "* (all of them)";
20+
},
21+
[&](const OutputsSpec::Names os) {
22+
return concatStringsSep(", ", quoteStrings(os));
23+
},
24+
}, wantedOutputs.raw));
25+
trace("created outer");
26+
27+
worker.updateProgress();
28+
}
29+
30+
31+
CreateDerivationAndRealiseGoal::~CreateDerivationAndRealiseGoal()
32+
{
33+
}
34+
35+
36+
static StorePath pathPartOfReq(const SingleDerivedPath & req)
37+
{
38+
return std::visit(overloaded {
39+
[&](const SingleDerivedPath::Opaque & bo) {
40+
return bo.path;
41+
},
42+
[&](const SingleDerivedPath::Built & bfd) {
43+
return pathPartOfReq(*bfd.drvPath);
44+
},
45+
}, req.raw());
46+
}
47+
48+
49+
std::string CreateDerivationAndRealiseGoal::key()
50+
{
51+
/* Ensure that derivations get built in order of their name,
52+
i.e. a derivation named "aardvark" always comes before "baboon". And
53+
substitution goals and inner derivation goals always happen before
54+
derivation goals (due to "b$"). */
55+
return "c$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + drvReq->to_string(worker.store);
56+
}
57+
58+
59+
void CreateDerivationAndRealiseGoal::timedOut(Error && ex)
60+
{
61+
}
62+
63+
64+
void CreateDerivationAndRealiseGoal::work()
65+
{
66+
(this->*state)();
67+
}
68+
69+
70+
void CreateDerivationAndRealiseGoal::addWantedOutputs(const OutputsSpec & outputs)
71+
{
72+
/* If we already want all outputs, there is nothing to do. */
73+
auto newWanted = wantedOutputs.union_(outputs);
74+
bool needRestart = !newWanted.isSubsetOf(wantedOutputs);
75+
wantedOutputs = newWanted;
76+
77+
if (!needRestart) return;
78+
79+
if (!optDrvPath)
80+
// haven't started steps where the outputs matter yet
81+
return;
82+
worker.makeDerivationGoal(*optDrvPath, outputs, buildMode);
83+
}
84+
85+
86+
void CreateDerivationAndRealiseGoal::getDerivation()
87+
{
88+
trace("outer init");
89+
90+
/* The first thing to do is to make sure that the derivation
91+
exists. If it doesn't, it may be created through a
92+
substitute. */
93+
if (auto optDrvPath = [this]() -> std::optional<StorePath> {
94+
if (buildMode != bmNormal) return std::nullopt;
95+
96+
auto drvPath = StorePath::dummy;
97+
try {
98+
drvPath = resolveDerivedPath(worker.store, *drvReq);
99+
} catch (MissingRealisation) {
100+
return std::nullopt;
101+
}
102+
return worker.evalStore.isValidPath(drvPath) || worker.store.isValidPath(drvPath)
103+
? std::optional { drvPath }
104+
: std::nullopt;
105+
}()) {
106+
trace(fmt("already have drv '%s' for '%s', can go straight to building",
107+
worker.store.printStorePath(*optDrvPath),
108+
drvReq->to_string(worker.store)));
109+
110+
loadAndBuildDerivation();
111+
} else {
112+
trace("need to obtain drv we want to build");
113+
114+
addWaitee(worker.makeGoal(DerivedPath::fromSingle(*drvReq)));
115+
116+
state = &CreateDerivationAndRealiseGoal::loadAndBuildDerivation;
117+
if (waitees.empty()) work();
118+
}
119+
}
120+
121+
122+
void CreateDerivationAndRealiseGoal::loadAndBuildDerivation()
123+
{
124+
trace("outer load and build derivation");
125+
126+
if (nrFailed != 0) {
127+
amDone(ecFailed, Error("cannot build missing derivation '%s'", drvReq->to_string(worker.store)));
128+
return;
129+
}
130+
131+
StorePath drvPath = resolveDerivedPath(worker.store, *drvReq);
132+
/* Build this step! */
133+
concreteDrvGoal = worker.makeDerivationGoal(drvPath, wantedOutputs, buildMode);
134+
addWaitee(upcast_goal(concreteDrvGoal));
135+
state = &CreateDerivationAndRealiseGoal::buildDone;
136+
optDrvPath = std::move(drvPath);
137+
if (waitees.empty()) work();
138+
}
139+
140+
141+
void CreateDerivationAndRealiseGoal::buildDone()
142+
{
143+
trace("outer build done");
144+
145+
buildResult = upcast_goal(concreteDrvGoal)->getBuildResult(DerivedPath::Built {
146+
.drvPath = drvReq,
147+
.outputs = wantedOutputs,
148+
});
149+
150+
if (buildResult.success())
151+
amDone(ecSuccess);
152+
else
153+
amDone(ecFailed, Error("building '%s' failed", drvReq->to_string(worker.store)));
154+
}
155+
156+
157+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#pragma once
2+
3+
#include "parsed-derivations.hh"
4+
#include "lock.hh"
5+
#include "store-api.hh"
6+
#include "pathlocks.hh"
7+
#include "goal.hh"
8+
9+
namespace nix {
10+
11+
struct DerivationGoal;
12+
13+
/**
14+
* This goal type is essentially the serial composition (like function
15+
* composition) of a goal for getting a derivation, and then a
16+
* `DerivationGoal` using the newly-obtained derivation.
17+
*
18+
* In the (currently experimental) general inductive case of derivations
19+
* that are themselves build outputs, that first goal will be *another*
20+
* `CreateDerivationAndRealiseGoal`. In the (much more common) base-case
21+
* where the derivation has no provence and is just referred to by
22+
* (content-addressed) store path, that first goal is a
23+
* `SubstitutionGoal`.
24+
*
25+
* If we already have the derivation (e.g. if the evalutator has created
26+
* the derivation locally and then instructured the store to build it),
27+
* we can skip the first goal entirely as a small optimization.
28+
*/
29+
struct CreateDerivationAndRealiseGoal : public Goal
30+
{
31+
/**
32+
* How to obtain a store path of the derivation to build.
33+
*/
34+
ref<SingleDerivedPath> drvReq;
35+
36+
/**
37+
* The path of the derivation, once obtained.
38+
**/
39+
std::optional<StorePath> optDrvPath;
40+
41+
/**
42+
* The goal for the corresponding concrete derivation.
43+
**/
44+
std::shared_ptr<DerivationGoal> concreteDrvGoal;
45+
46+
/**
47+
* The specific outputs that we need to build.
48+
*/
49+
OutputsSpec wantedOutputs;
50+
51+
typedef void (CreateDerivationAndRealiseGoal::*GoalState)();
52+
GoalState state;
53+
54+
/**
55+
* The final output paths of the build.
56+
*
57+
* - For input-addressed derivations, always the precomputed paths
58+
*
59+
* - For content-addressed derivations, calcuated from whatever the
60+
* hash ends up being. (Note that fixed outputs derivations that
61+
* produce the "wrong" output still install that data under its
62+
* true content-address.)
63+
*/
64+
OutputPathMap finalOutputs;
65+
66+
BuildMode buildMode;
67+
68+
CreateDerivationAndRealiseGoal(ref<SingleDerivedPath> drvReq,
69+
const OutputsSpec & wantedOutputs, Worker & worker,
70+
BuildMode buildMode = bmNormal);
71+
virtual ~CreateDerivationAndRealiseGoal();
72+
73+
void timedOut(Error && ex) override;
74+
75+
std::string key() override;
76+
77+
void work() override;
78+
79+
/**
80+
* Add wanted outputs to an already existing derivation goal.
81+
*/
82+
void addWantedOutputs(const OutputsSpec & outputs);
83+
84+
/**
85+
* The states.
86+
*/
87+
void getDerivation();
88+
void loadAndBuildDerivation();
89+
void buildDone();
90+
91+
JobCategory jobCategory() const override {
92+
return JobCategory::Administration;
93+
};
94+
};
95+
96+
}

src/libstore/build/derivation-goal.cc

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
7171
, wantedOutputs(wantedOutputs)
7272
, buildMode(buildMode)
7373
{
74-
state = &DerivationGoal::getDerivation;
74+
state = &DerivationGoal::loadDerivation;
7575
name = fmt(
7676
"building of '%s' from .drv file",
7777
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
@@ -164,24 +164,6 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
164164
}
165165

166166

167-
void DerivationGoal::getDerivation()
168-
{
169-
trace("init");
170-
171-
/* The first thing to do is to make sure that the derivation
172-
exists. If it doesn't, it may be created through a
173-
substitute. */
174-
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
175-
loadDerivation();
176-
return;
177-
}
178-
179-
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));
180-
181-
state = &DerivationGoal::loadDerivation;
182-
}
183-
184-
185167
void DerivationGoal::loadDerivation()
186168
{
187169
trace("loading derivation");
@@ -1493,7 +1475,7 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
14931475
if (!useDerivation) return;
14941476
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
14951477

1496-
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
1478+
auto * dg = tryGetConcreteDrvGoal(waitee);
14971479
if (!dg) return;
14981480

14991481
auto outputs = fullDrv.inputDrvs.find(dg->drvPath);

src/libstore/build/derivation-goal.hh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ struct InitialOutput {
5050
std::optional<InitialOutputStatus> known;
5151
};
5252

53+
/**
54+
* A goal for building some or all of the outputs of a derivation.
55+
*
56+
* The derivation must already be present, either in the store in a drv
57+
* or in memory. If the derivation itself needs to be gotten first, a
58+
* `CreateDerivationAndRealiseGoal` goal must be used instead.
59+
*/
5360
struct DerivationGoal : public Goal
5461
{
5562
/**
@@ -66,8 +73,7 @@ struct DerivationGoal : public Goal
6673
std::shared_ptr<DerivationGoal> resolvedDrvGoal;
6774

6875
/**
69-
* The specific outputs that we need to build. Empty means all of
70-
* them.
76+
* The specific outputs that we need to build.
7177
*/
7278
OutputsSpec wantedOutputs;
7379

@@ -229,7 +235,6 @@ struct DerivationGoal : public Goal
229235
/**
230236
* The states.
231237
*/
232-
void getDerivation();
233238
void loadDerivation();
234239
void haveDerivation();
235240
void outputsSubstitutionTried();
@@ -334,7 +339,9 @@ struct DerivationGoal : public Goal
334339

335340
StorePathSet exportReferences(const StorePathSet & storePaths);
336341

337-
JobCategory jobCategory() override { return JobCategory::Build; };
342+
JobCategory jobCategory() const override {
343+
return JobCategory::Build;
344+
};
338345
};
339346

340347
MakeError(NotDeterministic, BuildError);

src/libstore/build/drv-output-substitution-goal.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ public:
7373
void work() override;
7474
void handleEOF(int fd) override;
7575

76-
JobCategory jobCategory() override { return JobCategory::Substitution; };
76+
JobCategory jobCategory() const override {
77+
return JobCategory::Substitution;
78+
};
7779
};
7880

7981
}

src/libstore/build/entry-points.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "worker.hh"
22
#include "substitution-goal.hh"
3+
#include "create-derivation-and-realise-goal.hh"
34
#include "derivation-goal.hh"
45
#include "local-store.hh"
56

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

1617
worker.run(goals);
1718

18-
StorePathSet failed;
19+
StringSet failed;
1920
std::optional<Error> ex;
2021
for (auto & i : goals) {
2122
if (i->ex) {
@@ -25,8 +26,10 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
2526
ex = std::move(i->ex);
2627
}
2728
if (i->exitCode != Goal::ecSuccess) {
28-
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
29-
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
29+
if (auto i2 = dynamic_cast<CreateDerivationAndRealiseGoal *>(i.get()))
30+
failed.insert(i2->drvReq->to_string(*this));
31+
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
32+
failed.insert(printStorePath(i2->storePath));
3033
}
3134
}
3235

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

src/libstore/build/goal.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
1111
}
1212

1313

14-
BuildResult Goal::getBuildResult(const DerivedPath & req) {
14+
BuildResult Goal::getBuildResult(const DerivedPath & req) const {
1515
BuildResult res { buildResult };
1616

1717
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {

0 commit comments

Comments
 (0)