Skip to content

Commit d02dca0

Browse files
authored
Merge pull request #14022 from obsidiansystems/derivation-resolution-goal
Introduce `DerivationResolutionGoal`, fix substituting a single CA drv output
2 parents 9bd0915 + c97b050 commit d02dca0

File tree

12 files changed

+462
-237
lines changed

12 files changed

+462
-237
lines changed

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

Lines changed: 11 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include "nix/store/build/derivation-building-goal.hh"
22
#include "nix/store/build/derivation-env-desugar.hh"
3-
#include "nix/store/build/derivation-trampoline-goal.hh"
43
#ifndef _WIN32 // TODO enable build hook on Windows
54
# include "nix/store/build/hook-instance.hh"
65
# include "nix/store/build/derivation-builder.hh"
@@ -27,8 +26,8 @@
2726
namespace nix {
2827

2928
DerivationBuildingGoal::DerivationBuildingGoal(
30-
const StorePath & drvPath, const Derivation & drv_, Worker & worker, BuildMode buildMode)
31-
: Goal(worker, gaveUpOnSubstitution())
29+
const StorePath & drvPath, const Derivation & drv_, Worker & worker, BuildMode buildMode, bool storeDerivation)
30+
: Goal(worker, gaveUpOnSubstitution(storeDerivation))
3231
, drvPath(drvPath)
3332
, buildMode(buildMode)
3433
{
@@ -125,50 +124,10 @@ static void runPostBuildHook(
125124

126125
/* At least one of the output paths could not be
127126
produced using a substitute. So we have to build instead. */
128-
Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
127+
Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation)
129128
{
130129
Goals waitees;
131130

132-
std::map<ref<const SingleDerivedPath>, GoalPtr, value_comparison> inputGoals;
133-
134-
{
135-
std::function<void(ref<const SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)>
136-
addWaiteeDerivedPath;
137-
138-
addWaiteeDerivedPath = [&](ref<const SingleDerivedPath> inputDrv,
139-
const DerivedPathMap<StringSet>::ChildNode & inputNode) {
140-
if (!inputNode.value.empty()) {
141-
auto g = worker.makeGoal(
142-
DerivedPath::Built{
143-
.drvPath = inputDrv,
144-
.outputs = inputNode.value,
145-
},
146-
buildMode == bmRepair ? bmRepair : bmNormal);
147-
inputGoals.insert_or_assign(inputDrv, g);
148-
waitees.insert(std::move(g));
149-
}
150-
for (const auto & [outputName, childNode] : inputNode.childMap)
151-
addWaiteeDerivedPath(
152-
make_ref<SingleDerivedPath>(SingleDerivedPath::Built{inputDrv, outputName}), childNode);
153-
};
154-
155-
for (const auto & [inputDrvPath, inputNode] : drv->inputDrvs.map) {
156-
/* Ensure that pure, non-fixed-output derivations don't
157-
depend on impure derivations. */
158-
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && !drv->type().isImpure()
159-
&& !drv->type().isFixed()) {
160-
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
161-
if (inputDrv.type().isImpure())
162-
throw Error(
163-
"pure derivation '%s' depends on impure derivation '%s'",
164-
worker.store.printStorePath(drvPath),
165-
worker.store.printStorePath(inputDrvPath));
166-
}
167-
168-
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
169-
}
170-
}
171-
172131
/* Copy the input sources from the eval store to the build
173132
store.
174133
@@ -213,177 +172,17 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
213172

214173
/* Determine the full set of input paths. */
215174

216-
/* First, the input derivations. */
217-
{
218-
auto & fullDrv = *drv;
219-
220-
auto drvType = fullDrv.type();
221-
bool resolveDrv =
222-
std::visit(
223-
overloaded{
224-
[&](const DerivationType::InputAddressed & ia) {
225-
/* must resolve if deferred. */
226-
return ia.deferred;
227-
},
228-
[&](const DerivationType::ContentAddressed & ca) {
229-
return !fullDrv.inputDrvs.map.empty()
230-
&& (ca.fixed
231-
/* Can optionally resolve if fixed, which is good
232-
for avoiding unnecessary rebuilds. */
233-
? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
234-
/* Must resolve if floating and there are any inputs
235-
drvs. */
236-
: true);
237-
},
238-
[&](const DerivationType::Impure &) { return true; }},
239-
drvType.raw)
240-
/* no inputs are outputs of dynamic derivations */
241-
|| std::ranges::any_of(fullDrv.inputDrvs.map.begin(), fullDrv.inputDrvs.map.end(), [](auto & pair) {
242-
return !pair.second.childMap.empty();
243-
});
244-
245-
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
246-
experimentalFeatureSettings.require(Xp::CaDerivations);
247-
248-
/* We are be able to resolve this derivation based on the
249-
now-known results of dependencies. If so, we become a
250-
stub goal aliasing that resolved derivation goal. */
251-
std::optional attempt = fullDrv.tryResolve(
252-
worker.store,
253-
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
254-
auto mEntry = get(inputGoals, drvPath);
255-
if (!mEntry)
256-
return std::nullopt;
257-
258-
auto & buildResult = (*mEntry)->buildResult;
259-
return std::visit(
260-
overloaded{
261-
[](const BuildResult::Failure &) -> std::optional<StorePath> { return std::nullopt; },
262-
[&](const BuildResult::Success & success) -> std::optional<StorePath> {
263-
auto i = get(success.builtOutputs, outputName);
264-
if (!i)
265-
return std::nullopt;
266-
267-
return i->outPath;
268-
},
269-
},
270-
buildResult.inner);
271-
});
272-
if (!attempt) {
273-
/* TODO (impure derivations-induced tech debt) (see below):
274-
The above attempt should have found it, but because we manage
275-
inputDrvOutputs statefully, sometimes it gets out of sync with
276-
the real source of truth (store). So we query the store
277-
directly if there's a problem. */
278-
attempt = fullDrv.tryResolve(worker.store, &worker.evalStore);
279-
}
280-
assert(attempt);
281-
Derivation drvResolved{std::move(*attempt)};
282-
283-
auto pathResolved = writeDerivation(worker.store, drvResolved);
284-
285-
auto msg =
286-
fmt("resolved derivation: '%s' -> '%s'",
287-
worker.store.printStorePath(drvPath),
288-
worker.store.printStorePath(pathResolved));
289-
act = std::make_unique<Activity>(
290-
*logger,
291-
lvlInfo,
292-
actBuildWaiting,
293-
msg,
294-
Logger::Fields{
295-
worker.store.printStorePath(drvPath),
296-
worker.store.printStorePath(pathResolved),
297-
});
298-
299-
/* TODO https://github.com/NixOS/nix/issues/13247 we should
300-
let the calling goal do this, so it has a change to pass
301-
just the output(s) it cares about. */
302-
auto resolvedDrvGoal =
303-
worker.makeDerivationTrampolineGoal(pathResolved, OutputsSpec::All{}, drvResolved, buildMode);
304-
{
305-
Goals waitees{resolvedDrvGoal};
306-
co_await await(std::move(waitees));
307-
}
308-
309-
trace("resolved derivation finished");
310-
311-
auto resolvedResult = resolvedDrvGoal->buildResult;
312-
313-
// No `std::visit` for coroutines yet
314-
if (auto * successP = resolvedResult.tryGetSuccess()) {
315-
auto & success = *successP;
316-
SingleDrvOutputs builtOutputs;
317-
318-
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
319-
auto resolvedHashes = staticOutputHashes(worker.store, drvResolved);
320-
321-
StorePathSet outputPaths;
322-
323-
for (auto & outputName : drvResolved.outputNames()) {
324-
auto outputHash = get(outputHashes, outputName);
325-
auto resolvedHash = get(resolvedHashes, outputName);
326-
if ((!outputHash) || (!resolvedHash))
327-
throw Error(
328-
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolve)",
329-
worker.store.printStorePath(drvPath),
330-
outputName);
331-
332-
auto realisation = [&] {
333-
auto take1 = get(success.builtOutputs, outputName);
334-
if (take1)
335-
return *take1;
336-
337-
/* The above `get` should work. But stateful tracking of
338-
outputs in resolvedResult, this can get out of sync with the
339-
store, which is our actual source of truth. For now we just
340-
check the store directly if it fails. */
341-
auto take2 = worker.evalStore.queryRealisation(DrvOutput{*resolvedHash, outputName});
342-
if (take2)
343-
return *take2;
344-
345-
throw Error(
346-
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/realisation)",
347-
worker.store.printStorePath(pathResolved),
348-
outputName);
349-
}();
350-
351-
if (!drv->type().isImpure()) {
352-
auto newRealisation = realisation;
353-
newRealisation.id = DrvOutput{*outputHash, outputName};
354-
newRealisation.signatures.clear();
355-
if (!drv->type().isFixed()) {
356-
auto & drvStore = worker.evalStore.isValidPath(drvPath) ? worker.evalStore : worker.store;
357-
newRealisation.dependentRealisations =
358-
drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore);
359-
}
360-
worker.store.signRealisation(newRealisation);
361-
worker.store.registerDrvOutput(newRealisation);
362-
}
363-
outputPaths.insert(realisation.outPath);
364-
builtOutputs.emplace(outputName, realisation);
365-
}
366-
367-
runPostBuildHook(worker.store, *logger, drvPath, outputPaths);
368-
369-
auto status = success.status;
370-
if (status == BuildResult::Success::AlreadyValid)
371-
status = BuildResult::Success::ResolvesToAlreadyValid;
372-
373-
co_return doneSuccess(success.status, std::move(builtOutputs));
374-
} else if (resolvedResult.tryGetFailure()) {
375-
co_return doneFailure({
376-
BuildResult::Failure::DependencyFailed,
377-
"build of resolved derivation '%s' failed",
378-
worker.store.printStorePath(pathResolved),
379-
});
380-
} else
381-
assert(false);
382-
}
175+
if (storeDerivation) {
176+
assert(drv->inputDrvs.map.empty());
177+
/* Store the resolved derivation, as part of the record of
178+
what we're actually building */
179+
writeDerivation(worker.store, *drv);
180+
}
383181

182+
{
384183
/* If we get this far, we know no dynamic drvs inputs */
385184

386-
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map) {
185+
for (auto & [depDrvPath, depNode] : drv->inputDrvs.map) {
387186
for (auto & outputName : depNode.value) {
388187
/* Don't need to worry about `inputGoals`, because
389188
impure derivations are always resolved above. Can

src/libstore/build/derivation-goal.cc

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "nix/store/build/derivation-goal.hh"
22
#include "nix/store/build/derivation-building-goal.hh"
3+
#include "nix/store/build/derivation-resolution-goal.hh"
34
#ifndef _WIN32 // TODO enable build hook on Windows
45
# include "nix/store/build/hook-instance.hh"
56
# include "nix/store/build/derivation-builder.hh"
@@ -29,8 +30,9 @@ DerivationGoal::DerivationGoal(
2930
const Derivation & drv,
3031
const OutputName & wantedOutput,
3132
Worker & worker,
32-
BuildMode buildMode)
33-
: Goal(worker, haveDerivation())
33+
BuildMode buildMode,
34+
bool storeDerivation)
35+
: Goal(worker, haveDerivation(storeDerivation))
3436
, drvPath(drvPath)
3537
, wantedOutput(wantedOutput)
3638
, outputHash{[&] {
@@ -64,7 +66,7 @@ std::string DerivationGoal::key()
6466
}.to_string(worker.store);
6567
}
6668

67-
Goal::Co DerivationGoal::haveDerivation()
69+
Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
6870
{
6971
trace("have derivation");
7072

@@ -146,9 +148,96 @@ Goal::Co DerivationGoal::haveDerivation()
146148
worker.store.printStorePath(drvPath));
147149
}
148150

151+
auto resolutionGoal = worker.makeDerivationResolutionGoal(drvPath, *drv, buildMode);
152+
{
153+
Goals waitees{resolutionGoal};
154+
co_await await(std::move(waitees));
155+
}
156+
if (nrFailed != 0) {
157+
co_return doneFailure({BuildResult::Failure::DependencyFailed, "resolution failed"});
158+
}
159+
160+
if (resolutionGoal->resolvedDrv) {
161+
auto & [pathResolved, drvResolved] = *resolutionGoal->resolvedDrv;
162+
163+
auto resolvedDrvGoal =
164+
worker.makeDerivationGoal(pathResolved, drvResolved, wantedOutput, buildMode, /*storeDerivation=*/true);
165+
{
166+
Goals waitees{resolvedDrvGoal};
167+
co_await await(std::move(waitees));
168+
}
169+
170+
trace("resolved derivation finished");
171+
172+
auto resolvedResult = resolvedDrvGoal->buildResult;
173+
174+
// No `std::visit` for coroutines yet
175+
if (auto * successP = resolvedResult.tryGetSuccess()) {
176+
auto & success = *successP;
177+
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
178+
auto resolvedHashes = staticOutputHashes(worker.store, drvResolved);
179+
180+
StorePathSet outputPaths;
181+
182+
auto outputHash = get(outputHashes, wantedOutput);
183+
auto resolvedHash = get(resolvedHashes, wantedOutput);
184+
if ((!outputHash) || (!resolvedHash))
185+
throw Error(
186+
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolve)",
187+
worker.store.printStorePath(drvPath),
188+
wantedOutput);
189+
190+
auto realisation = [&] {
191+
auto take1 = get(success.builtOutputs, wantedOutput);
192+
if (take1)
193+
return *take1;
194+
195+
/* The above `get` should work. But stateful tracking of
196+
outputs in resolvedResult, this can get out of sync with the
197+
store, which is our actual source of truth. For now we just
198+
check the store directly if it fails. */
199+
auto take2 = worker.evalStore.queryRealisation(DrvOutput{*resolvedHash, wantedOutput});
200+
if (take2)
201+
return *take2;
202+
203+
throw Error(
204+
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/realisation)",
205+
worker.store.printStorePath(pathResolved),
206+
wantedOutput);
207+
}();
208+
209+
if (!drv->type().isImpure()) {
210+
auto newRealisation = realisation;
211+
newRealisation.id = DrvOutput{*outputHash, wantedOutput};
212+
newRealisation.signatures.clear();
213+
if (!drv->type().isFixed()) {
214+
auto & drvStore = worker.evalStore.isValidPath(drvPath) ? worker.evalStore : worker.store;
215+
newRealisation.dependentRealisations =
216+
drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore);
217+
}
218+
worker.store.signRealisation(newRealisation);
219+
worker.store.registerDrvOutput(newRealisation);
220+
}
221+
outputPaths.insert(realisation.outPath);
222+
223+
auto status = success.status;
224+
if (status == BuildResult::Success::AlreadyValid)
225+
status = BuildResult::Success::ResolvesToAlreadyValid;
226+
227+
co_return doneSuccess(status, std::move(realisation));
228+
} else if (resolvedResult.tryGetFailure()) {
229+
co_return doneFailure({
230+
BuildResult::Failure::DependencyFailed,
231+
"build of resolved derivation '%s' failed",
232+
worker.store.printStorePath(pathResolved),
233+
});
234+
} else
235+
assert(false);
236+
}
237+
149238
/* Give up on substitution for the output we want, actually build this derivation */
150239

151-
auto g = worker.makeDerivationBuildingGoal(drvPath, *drv, buildMode);
240+
auto g = worker.makeDerivationBuildingGoal(drvPath, *drv, buildMode, storeDerivation);
152241

153242
/* We will finish with it ourselves, as if we were the derivational goal. */
154243
g->preserveException = true;

0 commit comments

Comments
 (0)