Skip to content

Commit 16e946b

Browse files
authored
Merge pull request #14225 from obsidiansystems/derivation-resolution-goal-2
Reapply the rest of #14022
2 parents 6642ffb + ad893ac commit 16e946b

File tree

9 files changed

+140
-135
lines changed

9 files changed

+140
-135
lines changed

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

Lines changed: 10 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#include "nix/store/build/derivation-building-goal.hh"
2-
#include "nix/store/build/derivation-resolution-goal.hh"
32
#include "nix/store/build/derivation-env-desugar.hh"
4-
#include "nix/store/build/derivation-trampoline-goal.hh"
53
#ifndef _WIN32 // TODO enable build hook on Windows
64
# include "nix/store/build/hook-instance.hh"
75
# include "nix/store/build/derivation-builder.hh"
@@ -28,8 +26,8 @@
2826
namespace nix {
2927

3028
DerivationBuildingGoal::DerivationBuildingGoal(
31-
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode)
32-
: Goal(worker, gaveUpOnSubstitution())
29+
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode, bool storeDerivation)
30+
: Goal(worker, gaveUpOnSubstitution(storeDerivation))
3331
, drvPath(drvPath)
3432
, drv{std::make_unique<Derivation>(drv)}
3533
, buildMode(buildMode)
@@ -109,7 +107,7 @@ static void runPostBuildHook(
109107

110108
/* At least one of the output paths could not be
111109
produced using a substitute. So we have to build instead. */
112-
Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
110+
Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation)
113111
{
114112
Goals waitees;
115113

@@ -157,108 +155,14 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution()
157155

158156
/* Determine the full set of input paths. */
159157

160-
{
161-
auto resolutionGoal = worker.makeDerivationResolutionGoal(drvPath, *drv, buildMode);
162-
{
163-
Goals waitees{resolutionGoal};
164-
co_await await(std::move(waitees));
165-
}
166-
if (nrFailed != 0) {
167-
co_return doneFailure({BuildResult::Failure::DependencyFailed, "resolution failed"});
168-
}
169-
170-
if (resolutionGoal->resolvedDrv) {
171-
auto & [pathResolved, drvResolved] = *resolutionGoal->resolvedDrv;
172-
173-
/* Store the resolved derivation, as part of the record of
174-
what we're actually building */
175-
writeDerivation(worker.store, drvResolved);
176-
177-
/* TODO https://github.com/NixOS/nix/issues/13247 we should
178-
let the calling goal do this, so it has a change to pass
179-
just the output(s) it cares about. */
180-
auto resolvedDrvGoal =
181-
worker.makeDerivationTrampolineGoal(pathResolved, OutputsSpec::All{}, drvResolved, buildMode);
182-
{
183-
Goals waitees{resolvedDrvGoal};
184-
co_await await(std::move(waitees));
185-
}
186-
187-
trace("resolved derivation finished");
188-
189-
auto resolvedResult = resolvedDrvGoal->buildResult;
190-
191-
// No `std::visit` for coroutines yet
192-
if (auto * successP = resolvedResult.tryGetSuccess()) {
193-
auto & success = *successP;
194-
SingleDrvOutputs builtOutputs;
195-
196-
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
197-
auto resolvedHashes = staticOutputHashes(worker.store, drvResolved);
198-
199-
StorePathSet outputPaths;
200-
201-
for (auto & outputName : drvResolved.outputNames()) {
202-
auto outputHash = get(outputHashes, outputName);
203-
auto resolvedHash = get(resolvedHashes, outputName);
204-
if ((!outputHash) || (!resolvedHash))
205-
throw Error(
206-
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolve)",
207-
worker.store.printStorePath(drvPath),
208-
outputName);
209-
210-
auto realisation = [&] {
211-
auto take1 = get(success.builtOutputs, outputName);
212-
if (take1)
213-
return *take1;
214-
215-
/* The above `get` should work. But stateful tracking of
216-
outputs in resolvedResult, this can get out of sync with the
217-
store, which is our actual source of truth. For now we just
218-
check the store directly if it fails. */
219-
auto take2 = worker.evalStore.queryRealisation(DrvOutput{*resolvedHash, outputName});
220-
if (take2)
221-
return *take2;
222-
223-
throw Error(
224-
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/realisation)",
225-
worker.store.printStorePath(pathResolved),
226-
outputName);
227-
}();
228-
229-
if (!drv->type().isImpure()) {
230-
auto newRealisation = realisation;
231-
newRealisation.id = DrvOutput{*outputHash, outputName};
232-
newRealisation.signatures.clear();
233-
if (!drv->type().isFixed()) {
234-
auto & drvStore = worker.evalStore.isValidPath(drvPath) ? worker.evalStore : worker.store;
235-
newRealisation.dependentRealisations =
236-
drvOutputReferences(worker.store, *drv, realisation.outPath, &drvStore);
237-
}
238-
worker.store.signRealisation(newRealisation);
239-
worker.store.registerDrvOutput(newRealisation);
240-
}
241-
outputPaths.insert(realisation.outPath);
242-
builtOutputs.emplace(outputName, realisation);
243-
}
244-
245-
runPostBuildHook(worker.store, *logger, drvPath, outputPaths);
246-
247-
auto status = success.status;
248-
if (status == BuildResult::Success::AlreadyValid)
249-
status = BuildResult::Success::ResolvesToAlreadyValid;
250-
251-
co_return doneSuccess(success.status, std::move(builtOutputs));
252-
} else if (resolvedResult.tryGetFailure()) {
253-
co_return doneFailure({
254-
BuildResult::Failure::DependencyFailed,
255-
"build of resolved derivation '%s' failed",
256-
worker.store.printStorePath(pathResolved),
257-
});
258-
} else
259-
assert(false);
260-
}
158+
if (storeDerivation) {
159+
assert(drv->inputDrvs.map.empty());
160+
/* Store the resolved derivation, as part of the record of
161+
what we're actually building */
162+
writeDerivation(worker.store, *drv);
163+
}
261164

165+
{
262166
/* If we get this far, we know no dynamic drvs inputs */
263167

264168
for (auto & [depDrvPath, depNode] : drv->inputDrvs.map) {

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
, drv{std::make_unique<Derivation>(drv)}
@@ -58,7 +60,7 @@ std::string DerivationGoal::key()
5860
}.to_string(worker.store);
5961
}
6062

61-
Goal::Co DerivationGoal::haveDerivation()
63+
Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
6264
{
6365
trace("have derivation");
6466

@@ -140,9 +142,96 @@ Goal::Co DerivationGoal::haveDerivation()
140142
worker.store.printStorePath(drvPath));
141143
}
142144

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

145-
auto g = worker.makeDerivationBuildingGoal(drvPath, *drv, buildMode);
234+
auto g = worker.makeDerivationBuildingGoal(drvPath, *drv, buildMode, storeDerivation);
146235

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Goal::Co DerivationTrampolineGoal::haveDerivation(StorePath drvPath, Derivation
145145
/* Build this step! */
146146

147147
for (auto & output : resolvedWantedOutputs) {
148-
auto g = upcast_goal(worker.makeDerivationGoal(drvPath, drv, output, buildMode));
148+
auto g = upcast_goal(worker.makeDerivationGoal(drvPath, drv, output, buildMode, false));
149149
g->preserveException = true;
150150
/* We will finish with it ourselves, as if we were the derivational goal. */
151151
concreteDrvGoals.insert(std::move(g));

src/libstore/build/worker.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,14 @@ std::shared_ptr<DerivationTrampolineGoal> Worker::makeDerivationTrampolineGoal(
7676
}
7777

7878
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(
79-
const StorePath & drvPath, const Derivation & drv, const OutputName & wantedOutput, BuildMode buildMode)
79+
const StorePath & drvPath,
80+
const Derivation & drv,
81+
const OutputName & wantedOutput,
82+
BuildMode buildMode,
83+
bool storeDerivation)
8084
{
81-
return initGoalIfNeeded(derivationGoals[drvPath][wantedOutput], drvPath, drv, wantedOutput, *this, buildMode);
85+
return initGoalIfNeeded(
86+
derivationGoals[drvPath][wantedOutput], drvPath, drv, wantedOutput, *this, buildMode, storeDerivation);
8287
}
8388

8489
std::shared_ptr<DerivationResolutionGoal>
@@ -87,10 +92,10 @@ Worker::makeDerivationResolutionGoal(const StorePath & drvPath, const Derivation
8792
return initGoalIfNeeded(derivationResolutionGoals[drvPath], drvPath, drv, *this, buildMode);
8893
}
8994

90-
std::shared_ptr<DerivationBuildingGoal>
91-
Worker::makeDerivationBuildingGoal(const StorePath & drvPath, const Derivation & drv, BuildMode buildMode)
95+
std::shared_ptr<DerivationBuildingGoal> Worker::makeDerivationBuildingGoal(
96+
const StorePath & drvPath, const Derivation & drv, BuildMode buildMode, bool storeDerivation)
9297
{
93-
return initGoalIfNeeded(derivationBuildingGoals[drvPath], drvPath, drv, *this, buildMode);
98+
return initGoalIfNeeded(derivationBuildingGoals[drvPath], drvPath, drv, *this, buildMode, storeDerivation);
9499
}
95100

96101
std::shared_ptr<PathSubstitutionGoal>

src/libstore/include/nix/store/build/derivation-building-goal.hh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,17 @@ typedef enum { rpAccept, rpDecline, rpPostpone } HookReply;
2929
*/
3030
struct DerivationBuildingGoal : public Goal
3131
{
32+
/**
33+
* @param storeDerivation Whether to store the derivation in
34+
* `worker.store`. This is useful for newly-resolved derivations. In this
35+
* case, the derivation was not created a priori, e.g. purely (or close
36+
* enough) from evaluation of the Nix language, but also depends on the
37+
* exact content produced by upstream builds. It is strongly advised to
38+
* have a permanent record of such a resolved derivation in order to
39+
* faithfully reconstruct the build history.
40+
*/
3241
DerivationBuildingGoal(
33-
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode = bmNormal);
42+
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode, bool storeDerivation);
3443
~DerivationBuildingGoal();
3544

3645
private:
@@ -100,7 +109,7 @@ private:
100109
/**
101110
* The states.
102111
*/
103-
Co gaveUpOnSubstitution();
112+
Co gaveUpOnSubstitution(bool storeDerivation);
104113
Co tryToBuild();
105114

106115
/**

src/libstore/include/nix/store/build/derivation-goal.hh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,16 @@ struct DerivationGoal : public Goal
4040
*/
4141
OutputName wantedOutput;
4242

43+
/**
44+
* @param storeDerivation See `DerivationBuildingGoal`. This is just passed along.
45+
*/
4346
DerivationGoal(
4447
const StorePath & drvPath,
4548
const Derivation & drv,
4649
const OutputName & wantedOutput,
4750
Worker & worker,
48-
BuildMode buildMode = bmNormal);
51+
BuildMode buildMode,
52+
bool storeDerivation);
4953
~DerivationGoal() = default;
5054

5155
void timedOut(Error && ex) override
@@ -80,7 +84,7 @@ private:
8084
/**
8185
* The states.
8286
*/
83-
Co haveDerivation();
87+
Co haveDerivation(bool storeDerivation);
8488

8589
/**
8690
* Return `std::nullopt` if the output is unknown, e.g. un unbuilt

src/libstore/include/nix/store/build/derivation-resolution-goal.hh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ struct BuilderFailureError;
3535
*/
3636
struct DerivationResolutionGoal : public Goal
3737
{
38-
DerivationResolutionGoal(
39-
const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode = bmNormal);
38+
DerivationResolutionGoal(const StorePath & drvPath, const Derivation & drv, Worker & worker, BuildMode buildMode);
4039

4140
/**
4241
* If the derivation needed to be resolved, this is resulting

0 commit comments

Comments
 (0)