Skip to content

Commit 9660ec0

Browse files
committed
Rework derivation input resolution
I refactored the way that input resolution works in `DerivationGoal`. To be honest, it is probably unclear to the reader whether this new way is better or worse. I suppose *intrinsic* motivation, I can say that - the more structured use of `inputGoal` (a local variable) is better than the shotgrun approach with `inputDrvOutputs` - A virtual `waiteeDone` was a hack, and now it's gone. However, the *real* motivation of this is not the above things, but that it is needed for my mammoth refactor fixing NixOS#11897 and NixOS#11928. It is nice that this step could come first, rather than making that refactor even bigger.
1 parent 0607a2e commit 9660ec0

12 files changed

+103
-176
lines changed

src/libstore/build/derivation-creation-and-realisation-goal.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace nix {
55

66
DerivationCreationAndRealisationGoal::DerivationCreationAndRealisationGoal(
7-
ref<SingleDerivedPath> drvReq, const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
7+
ref<const SingleDerivedPath> drvReq, const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
88
: Goal(worker, DerivedPath::Built{.drvPath = drvReq, .outputs = wantedOutputs})
99
, drvReq(drvReq)
1010
, wantedOutputs(wantedOutputs)

src/libstore/build/derivation-creation-and-realisation-goal.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct DerivationCreationAndRealisationGoal : public Goal
3030
/**
3131
* How to obtain a store path of the derivation to build.
3232
*/
33-
ref<SingleDerivedPath> drvReq;
33+
ref<const SingleDerivedPath> drvReq;
3434

3535
/**
3636
* The path of the derivation, once obtained.
@@ -67,7 +67,7 @@ struct DerivationCreationAndRealisationGoal : public Goal
6767
BuildMode buildMode;
6868

6969
DerivationCreationAndRealisationGoal(
70-
ref<SingleDerivedPath> drvReq,
70+
ref<const SingleDerivedPath> drvReq,
7171
const OutputsSpec & wantedOutputs,
7272
Worker & worker,
7373
BuildMode buildMode = bmNormal);

src/libstore/build/derivation-goal.cc

Lines changed: 64 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ Goal::Co DerivationGoal::haveDerivation()
309309
}
310310

311311

312+
/**
313+
* Used for `inputGoals` local variable below
314+
*/
315+
struct value_comparison
316+
{
317+
template <typename T>
318+
bool operator()(const ref<T> & lhs, const ref<T> & rhs) const {
319+
return *lhs < *rhs;
320+
}
321+
};
322+
323+
312324
/* At least one of the output paths could not be
313325
produced using a substitute. So we have to build instead. */
314326
Goal::Co DerivationGoal::gaveUpOnSubstitution()
@@ -317,19 +329,22 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
317329
is no need to restart. */
318330
needRestart = NeedRestartForMoreOutputs::BuildInProgressWillNotNeed;
319331

320-
/* The inputs must be built before we can build this goal. */
321-
inputDrvOutputs.clear();
332+
std::map<ref<const SingleDerivedPath>, GoalPtr, value_comparison> inputGoals;
333+
322334
if (useDerivation) {
323-
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
335+
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
324336

325-
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
326-
if (!inputNode.value.empty())
327-
addWaitee(worker.makeGoal(
337+
addWaiteeDerivedPath = [&](ref<const SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
338+
if (!inputNode.value.empty()) {
339+
auto g = worker.makeGoal(
328340
DerivedPath::Built {
329341
.drvPath = inputDrv,
330342
.outputs = inputNode.value,
331343
},
332-
buildMode == bmRepair ? bmRepair : bmNormal));
344+
buildMode == bmRepair ? bmRepair : bmNormal);
345+
inputGoals.insert_or_assign(inputDrv, g);
346+
addWaitee(std::move(g));
347+
}
333348
for (const auto & [outputName, childNode] : inputNode.childMap)
334349
addWaiteeDerivedPath(
335350
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
@@ -417,15 +432,32 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
417432
[&](const DerivationType::Impure &) {
418433
return true;
419434
}
420-
}, drvType.raw);
435+
}, drvType.raw)
436+
/* no inputs are outputs of dynamic derivations */
437+
|| std::ranges::any_of(
438+
fullDrv.inputDrvs.map.begin(),
439+
fullDrv.inputDrvs.map.end(),
440+
[](auto & pair) { return !pair.second.childMap.empty(); });
421441

422442
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
423443
experimentalFeatureSettings.require(Xp::CaDerivations);
424444

425445
/* We are be able to resolve this derivation based on the
426446
now-known results of dependencies. If so, we become a
427447
stub goal aliasing that resolved derivation goal. */
428-
std::optional attempt = fullDrv.tryResolve(worker.store, inputDrvOutputs);
448+
std::optional attempt = fullDrv.tryResolve(worker.store,
449+
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
450+
auto mEntry = get(inputGoals, drvPath);
451+
if (!mEntry) return std::nullopt;
452+
453+
auto buildResult = (*mEntry)->getBuildResult(DerivedPath::Built{drvPath, OutputsSpec::Names{outputName}});
454+
if (!buildResult.success()) return std::nullopt;
455+
456+
auto i = get(buildResult.builtOutputs, outputName);
457+
if (!i) return std::nullopt;
458+
459+
return i->outPath;
460+
});
429461
if (!attempt) {
430462
/* TODO (impure derivations-induced tech debt) (see below):
431463
The above attempt should have found it, but because we manage
@@ -456,54 +488,31 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
456488
co_return resolvedFinished();
457489
}
458490

459-
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
460-
461-
accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
462-
/* Add the relevant output closures of the input derivation
463-
`i' as input paths. Only add the closures of output paths
464-
that are specified as inputs. */
465-
auto getOutput = [&](const std::string & outputName) {
466-
/* TODO (impure derivations-induced tech debt):
467-
Tracking input derivation outputs statefully through the
468-
goals is error prone and has led to bugs.
469-
For a robust nix, we need to move towards the `else` branch,
470-
which does not rely on goal state to match up with the
471-
reality of the store, which is our real source of truth.
472-
However, the impure derivations feature still relies on this
473-
fragile way of doing things, because its builds do not have
474-
a representation in the store, which is a usability problem
475-
in itself. When implementing this logic entirely with lookups
476-
make sure that they're cached. */
477-
if (auto outPath = get(inputDrvOutputs, { depDrvPath, outputName })) {
478-
return *outPath;
491+
/* If we get this far, we know no dynamic drvs inputs */
492+
493+
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map) {
494+
for (auto & outputName : depNode.value) {
495+
/* Don't need to worry about `inputGoals`, because
496+
impure derivations are always resolved above. Can
497+
just use DB. This case only happens in the (older)
498+
input addressed and fixed output derivation cases. */
499+
auto outMap = [&]{
500+
for (auto * drvStore : { &worker.evalStore, &worker.store })
501+
if (drvStore->isValidPath(depDrvPath))
502+
return worker.store.queryDerivationOutputMap(depDrvPath, drvStore);
503+
assert(false);
504+
}();
505+
506+
auto outMapPath = outMap.find(outputName);
507+
if (outMapPath == outMap.end()) {
508+
throw Error(
509+
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
510+
worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
479511
}
480-
else {
481-
auto outMap = [&]{
482-
for (auto * drvStore : { &worker.evalStore, &worker.store })
483-
if (drvStore->isValidPath(depDrvPath))
484-
return worker.store.queryDerivationOutputMap(depDrvPath, drvStore);
485-
assert(false);
486-
}();
487-
488-
auto outMapPath = outMap.find(outputName);
489-
if (outMapPath == outMap.end()) {
490-
throw Error(
491-
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
492-
worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
493-
}
494-
return outMapPath->second;
495-
}
496-
};
497512

498-
for (auto & outputName : inputNode.value)
499-
worker.store.computeFSClosure(getOutput(outputName), inputPaths);
500-
501-
for (auto & [outputName, childNode] : inputNode.childMap)
502-
accumInputPaths(getOutput(outputName), childNode);
503-
};
504-
505-
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
506-
accumInputPaths(depDrvPath, depNode);
513+
worker.store.computeFSClosure(outMapPath->second, inputPaths);
514+
}
515+
}
507516
}
508517

509518
/* Second, the input sources. */
@@ -1532,35 +1541,4 @@ Goal::Done DerivationGoal::done(
15321541
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
15331542
}
15341543

1535-
1536-
void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
1537-
{
1538-
Goal::waiteeDone(waitee, result);
1539-
1540-
if (!useDerivation || !drv) return;
1541-
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
1542-
1543-
std::optional info = tryGetConcreteDrvGoal(waitee);
1544-
if (!info) return;
1545-
const auto & [dg, drvReq] = *info;
1546-
1547-
auto * nodeP = fullDrv.inputDrvs.findSlot(drvReq.get());
1548-
if (!nodeP) return;
1549-
auto & outputs = nodeP->value;
1550-
1551-
for (auto & outputName : outputs) {
1552-
auto buildResult = dg.get().getBuildResult(DerivedPath::Built {
1553-
.drvPath = makeConstantStorePathRef(dg.get().drvPath),
1554-
.outputs = OutputsSpec::Names { outputName },
1555-
});
1556-
if (buildResult.success()) {
1557-
auto i = buildResult.builtOutputs.find(outputName);
1558-
if (i != buildResult.builtOutputs.end())
1559-
inputDrvOutputs.insert_or_assign(
1560-
{ dg.get().drvPath, outputName },
1561-
i->second.outPath);
1562-
}
1563-
}
1564-
}
1565-
15661544
}

src/libstore/build/derivation-goal.hh

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,6 @@ struct DerivationGoal : public Goal
8282
*/
8383
OutputsSpec wantedOutputs;
8484

85-
/**
86-
* Mapping from input derivations + output names to actual store
87-
* paths. This is filled in by waiteeDone() as each dependency
88-
* finishes, before `trace("all inputs realised")` is reached.
89-
*/
90-
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
91-
9285
/**
9386
* See `needRestart`; just for that field.
9487
*/
@@ -335,8 +328,6 @@ struct DerivationGoal : public Goal
335328
SingleDrvOutputs builtOutputs = {},
336329
std::optional<Error> ex = {});
337330

338-
void waiteeDone(GoalPtr waitee, ExitCode result) override;
339-
340331
StorePathSet exportReferences(const StorePathSet & storePaths);
341332

342333
JobCategory jobCategory() const override {

src/libstore/build/goal.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ public:
429429

430430
void addWaitee(GoalPtr waitee);
431431

432-
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
432+
void waiteeDone(GoalPtr waitee, ExitCode result);
433433

434434
virtual void handleChildOutput(Descriptor fd, std::string_view data)
435435
{

src/libstore/build/worker.cc

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Worker::~Worker()
4545

4646

4747
std::shared_ptr<DerivationCreationAndRealisationGoal> Worker::makeDerivationCreationAndRealisationGoal(
48-
ref<SingleDerivedPath> drvReq,
48+
ref<const SingleDerivedPath> drvReq,
4949
const OutputsSpec & wantedOutputs,
5050
BuildMode buildMode)
5151
{
@@ -601,17 +601,4 @@ GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal)
601601
return subGoal;
602602
}
603603

604-
std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee)
605-
{
606-
auto * odg = dynamic_cast<DerivationCreationAndRealisationGoal *>(&*waitee);
607-
if (!odg) return std::nullopt;
608-
/* If we failed to obtain the concrete drv, we won't have created
609-
the concrete derivation goal. */
610-
if (!odg->concreteDrvGoal) return std::nullopt;
611-
return {{
612-
std::cref(*odg->concreteDrvGoal),
613-
std::cref(*odg->drvReq),
614-
}};
615-
}
616-
617604
}

src/libstore/build/worker.hh

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,6 @@ GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal);
3737

3838
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
3939

40-
/**
41-
* The current implementation of impure derivations has
42-
* `DerivationGoal`s accumulate realisations from their waitees.
43-
* Unfortunately, `DerivationGoal`s don't directly depend on other
44-
* goals, but instead depend on `DerivationCreationAndRealisationGoal`s.
45-
*
46-
* We try not to share any of the details of any goal type with any
47-
* other, for sake of modularity and quicker rebuilds. This means we
48-
* cannot "just" downcast and fish out the field. So as an escape hatch,
49-
* we have made the function, written in `worker.cc` where all the goal
50-
* types are visible, and use it instead.
51-
*/
52-
53-
std::optional<std::pair<std::reference_wrapper<const DerivationGoal>, std::reference_wrapper<const SingleDerivedPath>>> tryGetConcreteDrvGoal(GoalPtr waitee);
54-
5540
/**
5641
* A mapping used to remember for each child process to what goal it
5742
* belongs, and comm channels for receiving log data and output
@@ -218,7 +203,7 @@ public:
218203
*/
219204
private:
220205
std::shared_ptr<DerivationCreationAndRealisationGoal> makeDerivationCreationAndRealisationGoal(
221-
ref<SingleDerivedPath> drvPath,
206+
ref<const SingleDerivedPath> drvPath,
222207
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
223208
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
224209
const StorePath & drvPath, const OutputsSpec & wantedOutputs,

0 commit comments

Comments
 (0)