Skip to content

Commit cba1a21

Browse files
authored
Merge pull request #12567 from obsidiansystems/slightly-rework-drv-resolution
Rework derivation input resolution
2 parents f4fd570 + a58e058 commit cba1a21

File tree

9 files changed

+103
-142
lines changed

9 files changed

+103
-142
lines changed

src/libstore/build/derivation-goal.cc

Lines changed: 64 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,18 @@ Goal::Co DerivationGoal::haveDerivation()
322322
}
323323

324324

325+
/**
326+
* Used for `inputGoals` local variable below
327+
*/
328+
struct value_comparison
329+
{
330+
template <typename T>
331+
bool operator()(const ref<T> & lhs, const ref<T> & rhs) const {
332+
return *lhs < *rhs;
333+
}
334+
};
335+
336+
325337
/* At least one of the output paths could not be
326338
produced using a substitute. So we have to build instead. */
327339
Goal::Co DerivationGoal::gaveUpOnSubstitution()
@@ -330,19 +342,22 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
330342
is no need to restart. */
331343
needRestart = NeedRestartForMoreOutputs::BuildInProgressWillNotNeed;
332344

333-
/* The inputs must be built before we can build this goal. */
334-
inputDrvOutputs.clear();
345+
std::map<ref<const SingleDerivedPath>, GoalPtr, value_comparison> inputGoals;
346+
335347
if (useDerivation) {
336-
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
348+
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
337349

338-
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
339-
if (!inputNode.value.empty())
340-
addWaitee(worker.makeGoal(
350+
addWaiteeDerivedPath = [&](ref<const SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
351+
if (!inputNode.value.empty()) {
352+
auto g = worker.makeGoal(
341353
DerivedPath::Built {
342354
.drvPath = inputDrv,
343355
.outputs = inputNode.value,
344356
},
345-
buildMode == bmRepair ? bmRepair : bmNormal));
357+
buildMode == bmRepair ? bmRepair : bmNormal);
358+
inputGoals.insert_or_assign(inputDrv, g);
359+
addWaitee(std::move(g));
360+
}
346361
for (const auto & [outputName, childNode] : inputNode.childMap)
347362
addWaiteeDerivedPath(
348363
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
@@ -430,15 +445,32 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
430445
[&](const DerivationType::Impure &) {
431446
return true;
432447
}
433-
}, drvType.raw);
448+
}, drvType.raw)
449+
/* no inputs are outputs of dynamic derivations */
450+
|| std::ranges::any_of(
451+
fullDrv.inputDrvs.map.begin(),
452+
fullDrv.inputDrvs.map.end(),
453+
[](auto & pair) { return !pair.second.childMap.empty(); });
434454

435455
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
436456
experimentalFeatureSettings.require(Xp::CaDerivations);
437457

438458
/* We are be able to resolve this derivation based on the
439459
now-known results of dependencies. If so, we become a
440460
stub goal aliasing that resolved derivation goal. */
441-
std::optional attempt = fullDrv.tryResolve(worker.store, inputDrvOutputs);
461+
std::optional attempt = fullDrv.tryResolve(worker.store,
462+
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
463+
auto mEntry = get(inputGoals, drvPath);
464+
if (!mEntry) return std::nullopt;
465+
466+
auto buildResult = (*mEntry)->getBuildResult(DerivedPath::Built{drvPath, OutputsSpec::Names{outputName}});
467+
if (!buildResult.success()) return std::nullopt;
468+
469+
auto i = get(buildResult.builtOutputs, outputName);
470+
if (!i) return std::nullopt;
471+
472+
return i->outPath;
473+
});
442474
if (!attempt) {
443475
/* TODO (impure derivations-induced tech debt) (see below):
444476
The above attempt should have found it, but because we manage
@@ -469,54 +501,31 @@ Goal::Co DerivationGoal::gaveUpOnSubstitution()
469501
co_return resolvedFinished();
470502
}
471503

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

511-
for (auto & outputName : inputNode.value)
512-
worker.store.computeFSClosure(getOutput(outputName), inputPaths);
513-
514-
for (auto & [outputName, childNode] : inputNode.childMap)
515-
accumInputPaths(getOutput(outputName), childNode);
516-
};
517-
518-
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
519-
accumInputPaths(depDrvPath, depNode);
526+
worker.store.computeFSClosure(outMapPath->second, inputPaths);
527+
}
528+
}
520529
}
521530

522531
/* Second, the input sources. */
@@ -1545,34 +1554,4 @@ Goal::Done DerivationGoal::done(
15451554
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
15461555
}
15471556

1548-
1549-
void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
1550-
{
1551-
Goal::waiteeDone(waitee, result);
1552-
1553-
if (!useDerivation || !drv) return;
1554-
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
1555-
1556-
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
1557-
if (!dg) return;
1558-
1559-
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
1560-
if (!nodeP) return;
1561-
auto & outputs = nodeP->value;
1562-
1563-
for (auto & outputName : outputs) {
1564-
auto buildResult = dg->getBuildResult(DerivedPath::Built {
1565-
.drvPath = makeConstantStorePathRef(dg->drvPath),
1566-
.outputs = OutputsSpec::Names { outputName },
1567-
});
1568-
if (buildResult.success()) {
1569-
auto i = buildResult.builtOutputs.find(outputName);
1570-
if (i != buildResult.builtOutputs.end())
1571-
inputDrvOutputs.insert_or_assign(
1572-
{ dg->drvPath, outputName },
1573-
i->second.outPath);
1574-
}
1575-
}
1576-
}
1577-
15781557
}

src/libstore/build/derivation-goal.hh

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,6 @@ struct DerivationGoal : public Goal
7878
*/
7979
OutputsSpec wantedOutputs;
8080

81-
/**
82-
* Mapping from input derivations + output names to actual store
83-
* paths. This is filled in by waiteeDone() as each dependency
84-
* finishes, before `trace("all inputs realised")` is reached.
85-
*/
86-
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
87-
8881
/**
8982
* See `needRestart`; just for that field.
9083
*/
@@ -331,8 +324,6 @@ struct DerivationGoal : public Goal
331324
SingleDrvOutputs builtOutputs = {},
332325
std::optional<Error> ex = {});
333326

334-
void waiteeDone(GoalPtr waitee, ExitCode result) override;
335-
336327
StorePathSet exportReferences(const StorePathSet & storePaths);
337328

338329
JobCategory jobCategory() const override {

src/libstore/build/goal.hh

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

397397
void addWaitee(GoalPtr waitee);
398398

399-
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
399+
void waiteeDone(GoalPtr waitee, ExitCode result);
400400

401401
virtual void handleChildOutput(Descriptor fd, std::string_view data)
402402
{

src/libstore/build/worker.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,4 +552,9 @@ GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal)
552552
return subGoal;
553553
}
554554

555+
GoalPtr upcast_goal(std::shared_ptr<DerivationGoal> subGoal)
556+
{
557+
return subGoal;
558+
}
559+
555560
}

src/libstore/derivations.cc

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,49 +1052,36 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
10521052

10531053
std::optional<BasicDerivation> Derivation::tryResolve(Store & store, Store * evalStore) const
10541054
{
1055-
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
1056-
1057-
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accum;
1058-
accum = [&](auto & inputDrv, auto & node) {
1059-
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(inputDrv, evalStore)) {
1060-
if (outputPath) {
1061-
inputDrvOutputs.insert_or_assign({inputDrv, outputName}, *outputPath);
1062-
if (auto p = get(node.childMap, outputName))
1063-
accum(*outputPath, *p);
1055+
return tryResolve(
1056+
store,
1057+
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
1058+
try {
1059+
return resolveDerivedPath(store, SingleDerivedPath::Built{drvPath, outputName}, evalStore);
1060+
} catch (Error &) {
1061+
return std::nullopt;
10641062
}
1065-
}
1066-
};
1067-
1068-
for (auto & [inputDrv, node] : inputDrvs.map)
1069-
accum(inputDrv, node);
1070-
1071-
return tryResolve(store, inputDrvOutputs);
1063+
});
10721064
}
10731065

10741066
static bool tryResolveInput(
10751067
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
10761068
const DownstreamPlaceholder * placeholderOpt,
1077-
const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode,
1078-
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs)
1069+
ref<const SingleDerivedPath> drvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode,
1070+
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)> queryResolutionChain)
10791071
{
1080-
auto getOutput = [&](const std::string & outputName) {
1081-
auto * actualPathOpt = get(inputDrvOutputs, { inputDrv, outputName });
1082-
if (!actualPathOpt)
1083-
warn("output %s of input %s missing, aborting the resolving",
1084-
outputName,
1085-
store.printStorePath(inputDrv)
1086-
);
1087-
return actualPathOpt;
1088-
};
1089-
10901072
auto getPlaceholder = [&](const std::string & outputName) {
10911073
return placeholderOpt
10921074
? DownstreamPlaceholder::unknownDerivation(*placeholderOpt, outputName)
1093-
: DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName);
1075+
: [&]{
1076+
auto * p = std::get_if<SingleDerivedPath::Opaque>(&drvPath->raw());
1077+
// otherwise we should have had a placeholder to build-upon already
1078+
assert(p);
1079+
return DownstreamPlaceholder::unknownCaOutput(p->path, outputName);
1080+
}();
10941081
};
10951082

10961083
for (auto & outputName : inputNode.value) {
1097-
auto actualPathOpt = getOutput(outputName);
1084+
auto actualPathOpt = queryResolutionChain(drvPath, outputName);
10981085
if (!actualPathOpt) return false;
10991086
auto actualPath = *actualPathOpt;
11001087
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
@@ -1106,21 +1093,20 @@ static bool tryResolveInput(
11061093
}
11071094

11081095
for (auto & [outputName, childNode] : inputNode.childMap) {
1109-
auto actualPathOpt = getOutput(outputName);
1110-
if (!actualPathOpt) return false;
1111-
auto actualPath = *actualPathOpt;
11121096
auto nextPlaceholder = getPlaceholder(outputName);
11131097
if (!tryResolveInput(store, inputSrcs, inputRewrites,
1114-
&nextPlaceholder, actualPath, childNode,
1115-
inputDrvOutputs))
1098+
&nextPlaceholder,
1099+
make_ref<const SingleDerivedPath>(SingleDerivedPath::Built{drvPath, outputName}),
1100+
childNode,
1101+
queryResolutionChain))
11161102
return false;
11171103
}
11181104
return true;
11191105
}
11201106

11211107
std::optional<BasicDerivation> Derivation::tryResolve(
11221108
Store & store,
1123-
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const
1109+
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)> queryResolutionChain) const
11241110
{
11251111
BasicDerivation resolved { *this };
11261112

@@ -1129,7 +1115,7 @@ std::optional<BasicDerivation> Derivation::tryResolve(
11291115

11301116
for (auto & [inputDrv, inputNode] : inputDrvs.map)
11311117
if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites,
1132-
nullptr, inputDrv, inputNode, inputDrvOutputs))
1118+
nullptr, make_ref<const SingleDerivedPath>(SingleDerivedPath::Opaque{inputDrv}), inputNode, queryResolutionChain))
11331119
return std::nullopt;
11341120

11351121
rewriteDerivation(store, resolved, inputRewrites);

src/libstore/derivations.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ struct Derivation : BasicDerivation
369369
*/
370370
std::optional<BasicDerivation> tryResolve(
371371
Store & store,
372-
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;
372+
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)> queryResolutionChain) const;
373373

374374
/**
375375
* Check that the derivation is valid and does not present any

src/libstore/derived-path.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ void drvRequireExperiment(
170170
}
171171

172172
SingleDerivedPath::Built SingleDerivedPath::Built::parse(
173-
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
173+
const StoreDirConfig & store, ref<const SingleDerivedPath> drv,
174174
OutputNameView output,
175175
const ExperimentalFeatureSettings & xpSettings)
176176
{
@@ -182,7 +182,7 @@ SingleDerivedPath::Built SingleDerivedPath::Built::parse(
182182
}
183183

184184
DerivedPath::Built DerivedPath::Built::parse(
185-
const StoreDirConfig & store, ref<SingleDerivedPath> drv,
185+
const StoreDirConfig & store, ref<const SingleDerivedPath> drv,
186186
OutputNameView outputsS,
187187
const ExperimentalFeatureSettings & xpSettings)
188188
{
@@ -201,7 +201,7 @@ static SingleDerivedPath parseWithSingle(
201201
return n == s.npos
202202
? (SingleDerivedPath) SingleDerivedPath::Opaque::parse(store, s)
203203
: (SingleDerivedPath) SingleDerivedPath::Built::parse(store,
204-
make_ref<SingleDerivedPath>(parseWithSingle(
204+
make_ref<const SingleDerivedPath>(parseWithSingle(
205205
store,
206206
s.substr(0, n),
207207
separator,
@@ -234,7 +234,7 @@ static DerivedPath parseWith(
234234
return n == s.npos
235235
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
236236
: (DerivedPath) DerivedPath::Built::parse(store,
237-
make_ref<SingleDerivedPath>(parseWithSingle(
237+
make_ref<const SingleDerivedPath>(parseWithSingle(
238238
store,
239239
s.substr(0, n),
240240
separator,

0 commit comments

Comments
 (0)