|
1 | 1 | #include "nix/store/build/derivation-building-goal.hh" |
2 | 2 | #include "nix/store/build/derivation-env-desugar.hh" |
3 | | -#include "nix/store/build/derivation-trampoline-goal.hh" |
4 | 3 | #ifndef _WIN32 // TODO enable build hook on Windows |
5 | 4 | # include "nix/store/build/hook-instance.hh" |
6 | 5 | # include "nix/store/build/derivation-builder.hh" |
|
27 | 26 | namespace nix { |
28 | 27 |
|
29 | 28 | 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)) |
32 | 31 | , drvPath(drvPath) |
33 | 32 | , buildMode(buildMode) |
34 | 33 | { |
@@ -125,50 +124,10 @@ static void runPostBuildHook( |
125 | 124 |
|
126 | 125 | /* At least one of the output paths could not be |
127 | 126 | produced using a substitute. So we have to build instead. */ |
128 | | -Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution() |
| 127 | +Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation) |
129 | 128 | { |
130 | 129 | Goals waitees; |
131 | 130 |
|
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 | | - |
172 | 131 | /* Copy the input sources from the eval store to the build |
173 | 132 | store. |
174 | 133 |
|
@@ -213,177 +172,17 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution() |
213 | 172 |
|
214 | 173 | /* Determine the full set of input paths. */ |
215 | 174 |
|
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 | + } |
383 | 181 |
|
| 182 | + { |
384 | 183 | /* If we get this far, we know no dynamic drvs inputs */ |
385 | 184 |
|
386 | | - for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map) { |
| 185 | + for (auto & [depDrvPath, depNode] : drv->inputDrvs.map) { |
387 | 186 | for (auto & outputName : depNode.value) { |
388 | 187 | /* Don't need to worry about `inputGoals`, because |
389 | 188 | impure derivations are always resolved above. Can |
|
0 commit comments