Skip to content

Commit 37b92ea

Browse files
committed
Restore dynamic derivations!
This method does *not* create a new type of goal. We instead just make `DerivationGoal` more sophisticated, which is much easier to do now that `DerivationBuildingGoal` has been split from it (and so many fields are gone, or or local variables instead). This avoids the need for a secondarily trampoline goal that interacted poorly with `addWantedOutputs`. That, I hope, will mean the bugs from before do not reappear. There may in fact be a reason to introduce such a trampoline in the future, but it would only happen in conjunction with getting rid of `addWantedOutputs`. Restores the functionality (and tests) that was reverted in f4f28cd.
1 parent 3b617e4 commit 37b92ea

File tree

12 files changed

+141
-76
lines changed

12 files changed

+141
-76
lines changed

src/libstore/build/derivation-building-goal.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
296296

297297
// FIXME wanted outputs
298298
auto resolvedDrvGoal = worker.makeDerivationGoal(
299-
pathResolved, OutputsSpec::All{}, buildMode);
299+
makeConstantStorePathRef(pathResolved), OutputsSpec::All{}, buildMode);
300300
{
301301
Goals waitees{resolvedDrvGoal};
302302
co_await await(std::move(waitees));
@@ -338,7 +338,7 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
338338

339339
throw Error(
340340
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/realisation)",
341-
worker.store.printStorePath(resolvedDrvGoal->drvPath), outputName);
341+
resolvedDrvGoal->drvReq->to_string(worker.store), outputName);
342342
}();
343343

344344
if (!drv->type().isImpure()) {

src/libstore/build/derivation-goal.cc

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@
2424

2525
namespace nix {
2626

27-
DerivationGoal::DerivationGoal(const StorePath & drvPath,
27+
DerivationGoal::DerivationGoal(ref<const SingleDerivedPath> drvReq,
2828
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
2929
: Goal(worker, loadDerivation())
30-
, drvPath(drvPath)
30+
, drvReq(drvReq)
3131
, wantedOutputs(wantedOutputs)
3232
, buildMode(buildMode)
3333
{
3434
name = fmt(
3535
"building of '%s' from .drv file",
36-
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
36+
DerivedPath::Built { drvReq, wantedOutputs }.to_string(worker.store));
3737
trace("created");
3838

3939
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@@ -43,16 +43,16 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
4343

4444
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
4545
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
46-
: Goal(worker, haveDerivation())
47-
, drvPath(drvPath)
46+
: Goal(worker, haveDerivation(drvPath))
47+
, drvReq(makeConstantStorePathRef(drvPath))
4848
, wantedOutputs(wantedOutputs)
4949
, buildMode(buildMode)
5050
{
5151
this->drv = std::make_unique<Derivation>(drv);
5252

5353
name = fmt(
5454
"building of '%s' from in-memory derivation",
55-
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
55+
DerivedPath::Built { drvReq, drv.outputNames() }.to_string(worker.store));
5656
trace("created");
5757

5858
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@@ -61,13 +61,24 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
6161
}
6262

6363

64+
static StorePath pathPartOfReq(const SingleDerivedPath & req)
65+
{
66+
return std::visit(
67+
overloaded{
68+
[&](const SingleDerivedPath::Opaque & bo) { return bo.path; },
69+
[&](const SingleDerivedPath::Built & bfd) { return pathPartOfReq(*bfd.drvPath); },
70+
},
71+
req.raw());
72+
}
73+
74+
6475
std::string DerivationGoal::key()
6576
{
6677
/* Ensure that derivations get built in order of their name,
6778
i.e. a derivation named "aardvark" always comes before
6879
"baboon". And substitution goals always happen before
6980
derivation goals (due to "b$"). */
70-
return "b$" + std::string(drvPath.name()) + "$" + worker.store.printStorePath(drvPath);
81+
return "b$" + std::string(pathPartOfReq(*drvReq).name()) + "$" + drvReq->to_string(worker.store);
7182
}
7283

7384

@@ -99,18 +110,37 @@ Goal::Co DerivationGoal::loadDerivation() {
99110
/* The first thing to do is to make sure that the derivation
100111
exists. If it doesn't, it may be created through a
101112
substitute. */
102-
103-
if (buildMode != bmNormal || !worker.evalStore.isValidPath(drvPath)) {
104-
Goals waitees{upcast_goal(worker.makePathSubstitutionGoal(drvPath))};
113+
if (auto optDrvPath = [this]() -> std::optional<StorePath> {
114+
if (buildMode != bmNormal)
115+
return std::nullopt;
116+
117+
auto drvPath = StorePath::dummy;
118+
try {
119+
drvPath = resolveDerivedPath(worker.store, *drvReq);
120+
} catch (MissingRealisation &) {
121+
return std::nullopt;
122+
}
123+
auto cond = worker.evalStore.isValidPath(drvPath) || worker.store.isValidPath(drvPath);
124+
return cond ? std::optional{drvPath} : std::nullopt;
125+
}()) {
126+
trace(
127+
fmt("already have drv '%s' for '%s', can go straight to building",
128+
worker.store.printStorePath(*optDrvPath),
129+
drvReq->to_string(worker.store)));
130+
} else {
131+
trace("need to obtain drv we want to build");
132+
Goals waitees{worker.makeGoal(DerivedPath::fromSingle(*drvReq))};
105133
co_await await(std::move(waitees));
106134
}
107135

108-
trace("loading derivation");
136+
trace("outer load and build derivation");
109137

110138
if (nrFailed != 0) {
111-
co_return done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
139+
co_return amDone(ecFailed, Error("cannot build missing derivation '%s'", drvReq->to_string(worker.store)));
112140
}
113141

142+
StorePath drvPath = resolveDerivedPath(worker.store, *drvReq);
143+
114144
/* `drvPath' should already be a root, but let's be on the safe
115145
side: if the user forgot to make it a root, we wouldn't want
116146
things being garbage collected while we're busy. */
@@ -129,13 +159,13 @@ Goal::Co DerivationGoal::loadDerivation() {
129159
}
130160
}
131161
assert(drv);
132-
}
133162

134-
co_return haveDerivation();
163+
co_return haveDerivation(drvPath);
164+
}
135165
}
136166

137167

138-
Goal::Co DerivationGoal::haveDerivation()
168+
Goal::Co DerivationGoal::haveDerivation(StorePath drvPath)
139169
{
140170
trace("have derivation");
141171

@@ -221,11 +251,11 @@ Goal::Co DerivationGoal::haveDerivation()
221251

222252
{
223253
/* Check what outputs paths are not already valid. */
224-
auto [allValid, validOutputs] = checkPathValidity();
254+
auto [allValid, validOutputs] = checkPathValidity(drvPath);
225255

226256
/* If they are all valid, then we're done. */
227257
if (allValid && buildMode == bmNormal) {
228-
co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
258+
co_return done(drvPath, BuildResult::AlreadyValid, std::move(validOutputs));
229259
}
230260
}
231261

@@ -262,7 +292,7 @@ Goal::Co DerivationGoal::haveDerivation()
262292
assert(!drv->type().isImpure());
263293

264294
if (nrFailed > 0 && nrFailed > nrNoSubstituters && !settings.tryFallback) {
265-
co_return done(BuildResult::TransientFailure, {},
295+
co_return done(drvPath, BuildResult::TransientFailure, {},
266296
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
267297
worker.store.printStorePath(drvPath)));
268298
}
@@ -271,16 +301,16 @@ Goal::Co DerivationGoal::haveDerivation()
271301

272302
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
273303
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
274-
co_return haveDerivation();
304+
co_return haveDerivation(std::move(drvPath));
275305
}
276306

277-
auto [allValid, validOutputs] = checkPathValidity();
307+
auto [allValid, validOutputs] = checkPathValidity(drvPath);
278308

279309
if (buildMode == bmNormal && allValid) {
280-
co_return done(BuildResult::Substituted, std::move(validOutputs));
310+
co_return done(drvPath, BuildResult::Substituted, std::move(validOutputs));
281311
}
282312
if (buildMode == bmRepair && allValid) {
283-
co_return repairClosure();
313+
co_return repairClosure(std::move(drvPath));
284314
}
285315
if (buildMode == bmCheck && !allValid)
286316
throw Error("some outputs of '%s' are not valid, so checking is not possible",
@@ -303,7 +333,7 @@ struct value_comparison
303333
};
304334

305335

306-
Goal::Co DerivationGoal::repairClosure()
336+
Goal::Co DerivationGoal::repairClosure(StorePath drvPath)
307337
{
308338
assert(!drv->type().isImpure());
309339

@@ -313,7 +343,7 @@ Goal::Co DerivationGoal::repairClosure()
313343
that produced those outputs. */
314344

315345
/* Get the output closure. */
316-
auto outputs = queryDerivationOutputMap();
346+
auto outputs = queryDerivationOutputMap(drvPath);
317347
StorePathSet outputClosure;
318348
for (auto & i : outputs) {
319349
if (!wantedOutputs.contains(i.first)) continue;
@@ -371,11 +401,11 @@ Goal::Co DerivationGoal::repairClosure()
371401
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
372402
worker.store.printStorePath(drvPath));
373403
}
374-
co_return done(BuildResult::AlreadyValid, assertPathValidity());
404+
co_return done(drvPath, BuildResult::AlreadyValid, assertPathValidity(drvPath));
375405
}
376406

377407

378-
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
408+
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap(const StorePath & drvPath)
379409
{
380410
assert(!drv->type().isImpure());
381411

@@ -391,7 +421,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
391421
return res;
392422
}
393423

394-
OutputPathMap DerivationGoal::queryDerivationOutputMap()
424+
OutputPathMap DerivationGoal::queryDerivationOutputMap(const StorePath & drvPath)
395425
{
396426
assert(!drv->type().isImpure());
397427

@@ -407,7 +437,7 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
407437
}
408438

409439

410-
std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
440+
std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity(const StorePath & drvPath)
411441
{
412442
if (drv->type().isImpure()) return { false, {} };
413443

@@ -422,7 +452,7 @@ std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
422452
}, wantedOutputs.raw);
423453
SingleDrvOutputs validOutputs;
424454

425-
for (auto & i : queryPartialDerivationOutputMap()) {
455+
for (auto & i : queryPartialDerivationOutputMap(drvPath)) {
426456
auto initialOutput = get(initialOutputs, i.first);
427457
if (!initialOutput)
428458
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
@@ -487,16 +517,17 @@ std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
487517
}
488518

489519

490-
SingleDrvOutputs DerivationGoal::assertPathValidity()
520+
SingleDrvOutputs DerivationGoal::assertPathValidity(const StorePath & drvPath)
491521
{
492-
auto [allValid, validOutputs] = checkPathValidity();
522+
auto [allValid, validOutputs] = checkPathValidity(drvPath);
493523
if (!allValid)
494524
throw Error("some outputs are unexpectedly invalid");
495525
return validOutputs;
496526
}
497527

498528

499529
Goal::Done DerivationGoal::done(
530+
const StorePath & drvPath,
500531
BuildResult::Status status,
501532
SingleDrvOutputs builtOutputs,
502533
std::optional<Error> ex)

src/libstore/build/entry-points.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
3030
if (i->exitCode != Goal::ecSuccess) {
3131
#ifndef _WIN32 // TODO Enable building on Windows
3232
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
33-
failed.insert(printStorePath(i2->drvPath));
33+
failed.insert(i2->drvReq->to_string(*this));
3434
else
3535
#endif
3636
if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))

src/libstore/build/worker.cc

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ std::shared_ptr<G> Worker::initGoalIfNeeded(std::weak_ptr<G> & goal_weak, Args &
5454
}
5555

5656
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
57-
const StorePath & drvPath,
57+
ref<const SingleDerivedPath> drvReq,
5858
const OutputsSpec & wantedOutputs,
5959
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
6060
{
61-
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
61+
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals.ensureSlot(*drvReq).value;
6262
std::shared_ptr<DerivationGoal> goal = goal_weak.lock();
6363
if (!goal) {
6464
goal = mkDrvGoal();
@@ -71,18 +71,18 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
7171
}
7272

7373

74-
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
74+
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(ref<const SingleDerivedPath> drvReq,
7575
const OutputsSpec & wantedOutputs, BuildMode buildMode)
7676
{
77-
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
78-
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
77+
return makeDerivationGoalCommon(drvReq, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
78+
return std::make_shared<DerivationGoal>(drvReq, wantedOutputs, *this, buildMode);
7979
});
8080
}
8181

8282
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
8383
const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
8484
{
85-
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
85+
return makeDerivationGoalCommon(makeConstantStorePathRef(drvPath), wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
8686
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
8787
});
8888
}
@@ -118,10 +118,7 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
118118
{
119119
return std::visit(overloaded {
120120
[&](const DerivedPath::Built & bfd) -> GoalPtr {
121-
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
122-
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
123-
else
124-
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
121+
return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
125122
},
126123
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
127124
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
@@ -130,25 +127,45 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
130127
}
131128

132129

130+
template<typename K, typename V, typename F>
131+
static void cullMap(std::map<K, V> & goalMap, F f)
132+
{
133+
for (auto i = goalMap.begin(); i != goalMap.end();)
134+
if (!f(i->second))
135+
i = goalMap.erase(i);
136+
else ++i;
137+
}
138+
139+
133140
template<typename K, typename G>
134141
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
135142
{
136143
/* !!! inefficient */
137-
for (auto i = goalMap.begin();
138-
i != goalMap.end(); )
139-
if (i->second.lock() == goal) {
140-
auto j = i; ++j;
141-
goalMap.erase(i);
142-
i = j;
143-
}
144-
else ++i;
144+
cullMap(goalMap, [&](const std::weak_ptr<G> & gp) -> bool {
145+
return gp.lock() != goal;
146+
});
147+
}
148+
149+
template<typename K>
150+
static void removeGoal(std::shared_ptr<DerivationGoal> goal, std::map<K, DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode> & goalMap);
151+
152+
template<typename K>
153+
static void removeGoal(std::shared_ptr<DerivationGoal> goal, std::map<K, DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode> & goalMap)
154+
{
155+
/* !!! inefficient */
156+
cullMap(goalMap, [&](DerivedPathMap<std::weak_ptr<DerivationGoal>>::ChildNode & node) -> bool {
157+
if (node.value.lock() == goal)
158+
node.value.reset();
159+
removeGoal(goal, node.childMap);
160+
return !node.value.expired() || !node.childMap.empty();
161+
});
145162
}
146163

147164

148165
void Worker::removeGoal(GoalPtr goal)
149166
{
150167
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
151-
nix::removeGoal(drvGoal, derivationGoals);
168+
nix::removeGoal(drvGoal, derivationGoals.map);
152169
else if (auto drvBuildingGoal = std::dynamic_pointer_cast<DerivationBuildingGoal>(goal))
153170
nix::removeGoal(drvBuildingGoal, derivationBuildingGoals);
154171
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
@@ -297,7 +314,7 @@ void Worker::run(const Goals & _topGoals)
297314
topGoals.insert(i);
298315
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
299316
topPaths.push_back(DerivedPath::Built {
300-
.drvPath = makeConstantStorePathRef(goal->drvPath),
317+
.drvPath = goal->drvReq,
301318
.outputs = goal->wantedOutputs,
302319
});
303320
} else

0 commit comments

Comments
 (0)