|
| 1 | +#include "nix/store/build/build-trace-goal.hh" |
| 2 | +#include "nix/store/build/derivation-resolution-goal.hh" |
| 3 | +#include "nix/store/build/substitution-goal.hh" |
| 4 | +#include "nix/store/build/worker.hh" |
| 5 | +#include "nix/store/derivations.hh" |
| 6 | +#include "nix/store/store-open.hh" |
| 7 | +#include "nix/util/callback.hh" |
| 8 | +#include "nix/util/finally.hh" |
| 9 | +#include "nix/util/util.hh" |
| 10 | + |
| 11 | +namespace nix { |
| 12 | + |
| 13 | +BuildTraceGoal::BuildTraceGoal(const SingleDerivedPath::Built & id, Worker & worker) |
| 14 | + : Goal{worker, init()} |
| 15 | + , id{id} |
| 16 | +{ |
| 17 | + name = fmt("substitution of '%s'", id.to_string(worker.store)); |
| 18 | + trace("created"); |
| 19 | +} |
| 20 | + |
| 21 | +Goal::Co BuildTraceGoal::init() |
| 22 | +{ |
| 23 | + trace("init"); |
| 24 | + |
| 25 | + DrvOutput id2{ |
| 26 | + .drvHash = Hash::dummy, |
| 27 | + .outputName = id.output, |
| 28 | + }; |
| 29 | + |
| 30 | + StorePath drvPath = StorePath::dummy; |
| 31 | + |
| 32 | + // No `std::visit` with coroutines :( |
| 33 | + if (const auto * path = std::get_if<SingleDerivedPath::Opaque>(&*id.drvPath)) { |
| 34 | + // At least we know the drv path statically, can procede |
| 35 | + drvPath = path->path; |
| 36 | + } else if (const auto * outputDeriving = std::get_if<SingleDerivedPath::Built>(&*id.drvPath)) { |
| 37 | + // Dynamic derivation case, need to resolve that first. |
| 38 | + |
| 39 | + auto g = worker.makeBuildTraceGoal({ |
| 40 | + outputDeriving->drvPath, |
| 41 | + outputDeriving->output, |
| 42 | + }); |
| 43 | + |
| 44 | + co_await await(Goals{upcast_goal(g)}); |
| 45 | + |
| 46 | + if (nrFailed > 0) { |
| 47 | + co_return amDone( |
| 48 | + nrNoSubstituters > 0 ? ecNoSubstituters : ecFailed, |
| 49 | + Error("The output deriving path '%s' could not be resolved", outputDeriving->to_string(worker.store))); |
| 50 | + } |
| 51 | + |
| 52 | + drvPath = g->outputInfo->outPath; |
| 53 | + } |
| 54 | + |
| 55 | + { |
| 56 | + auto getStaticHash = [&](Store & store, const Derivation & drv) { |
| 57 | + // TODO slow |
| 58 | + auto hashes = staticOutputHashes(store, drv); |
| 59 | + auto * p = get(hashes, id.output); |
| 60 | + if (!p) |
| 61 | + throw Error("Missing output '%s' for derivation '%s'", id.output, worker.store.printStorePath(drvPath)); |
| 62 | + id2.drvHash = *p; |
| 63 | + }; |
| 64 | + |
| 65 | + if (worker.evalStore.isValidPath(drvPath)) { |
| 66 | + auto drv = worker.evalStore.readDerivation(drvPath); |
| 67 | + getStaticHash(worker.evalStore, drv); |
| 68 | + } else if (worker.store.isValidPath(drvPath)) { |
| 69 | + auto drv = worker.store.readDerivation(drvPath); |
| 70 | + getStaticHash(worker.evalStore, drv); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + /* If the derivation already exists, we’re done */ |
| 75 | + if ((outputInfo = worker.store.queryRealisation(id2))) { |
| 76 | + co_return amDone(ecSuccess); |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * Firstly, whether we know the status, secondly, what it is |
| 81 | + */ |
| 82 | + std::optional<bool> drvIsResolved; |
| 83 | + |
| 84 | + auto checkStaticOutputs = [&](Store & store) -> std::optional<Goal::Done> { |
| 85 | + auto drv = store.readDerivation(drvPath); |
| 86 | + auto os = drv.outputsAndOptPaths(worker.store); |
| 87 | + /* Mark what we now know */ |
| 88 | + drvIsResolved = {drv.inputDrvs.map.empty()}; |
| 89 | + if (auto * p = get(os, id2.outputName)) { |
| 90 | + if (auto & outPath = p->second) { |
| 91 | + outputInfo = std::make_shared<Realisation>(id2, *outPath); |
| 92 | + return {amDone(ecSuccess)}; |
| 93 | + } else { |
| 94 | + /* Otherwise, not failure, just looking up build trace below. */ |
| 95 | + return std::nullopt; |
| 96 | + } |
| 97 | + } else { |
| 98 | + return {amDone( |
| 99 | + ecFailed, |
| 100 | + Error( |
| 101 | + "Derivation '%s' does not have output '%s', impossible to find build trace key-value pair", |
| 102 | + worker.store.printStorePath(drvPath), |
| 103 | + id2.outputName))}; |
| 104 | + } |
| 105 | + }; |
| 106 | + |
| 107 | + /* If we have the derivaiton, and the derivation has statically-known output paths */ |
| 108 | + if (worker.evalStore.isValidPath(drvPath)) { |
| 109 | + if (auto co = checkStaticOutputs(worker.evalStore)) |
| 110 | + co_return *co; |
| 111 | + } else if (worker.store.isValidPath(drvPath)) { |
| 112 | + if (auto co = checkStaticOutputs(worker.store)) |
| 113 | + co_return *co; |
| 114 | + } |
| 115 | + |
| 116 | + auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>(); |
| 117 | + |
| 118 | + bool substituterFailed = false; |
| 119 | + |
| 120 | + if (!drvIsResolved || *drvIsResolved) { |
| 121 | + /* Since derivation might be resolved --- isn't known to be |
| 122 | + not-resolved, it might have entries. So, let's try querying |
| 123 | + the substituters. */ |
| 124 | + for (const auto & sub : subs) { |
| 125 | + trace("trying next substituter"); |
| 126 | + |
| 127 | + /* The callback of the curl download below can outlive `this` (if |
| 128 | + some other error occurs), so it must not touch `this`. So put |
| 129 | + the shared state in a separate refcounted object. */ |
| 130 | + auto outPipe = std::make_shared<MuxablePipe>(); |
| 131 | +#ifndef _WIN32 |
| 132 | + outPipe->create(); |
| 133 | +#else |
| 134 | + outPipe->createAsyncPipe(worker.ioport.get()); |
| 135 | +#endif |
| 136 | + |
| 137 | + auto promise = std::make_shared<std::promise<std::shared_ptr<const Realisation>>>(); |
| 138 | + |
| 139 | + sub->queryRealisation( |
| 140 | + id2, {[outPipe(outPipe), promise(promise)](std::future<std::shared_ptr<const Realisation>> res) { |
| 141 | + try { |
| 142 | + Finally updateStats([&]() { outPipe->writeSide.close(); }); |
| 143 | + promise->set_value(res.get()); |
| 144 | + } catch (...) { |
| 145 | + promise->set_exception(std::current_exception()); |
| 146 | + } |
| 147 | + }}); |
| 148 | + |
| 149 | + worker.childStarted( |
| 150 | + shared_from_this(), |
| 151 | + { |
| 152 | +#ifndef _WIN32 |
| 153 | + outPipe->readSide.get() |
| 154 | +#else |
| 155 | + &*outPipe |
| 156 | +#endif |
| 157 | + }, |
| 158 | + true, |
| 159 | + false); |
| 160 | + |
| 161 | + co_await Suspend{}; |
| 162 | + |
| 163 | + worker.childTerminated(this); |
| 164 | + |
| 165 | + std::shared_ptr<const Realisation> outputInfo; |
| 166 | + try { |
| 167 | + outputInfo = promise->get_future().get(); |
| 168 | + } catch (std::exception & e) { |
| 169 | + printError(e.what()); |
| 170 | + substituterFailed = true; |
| 171 | + } |
| 172 | + |
| 173 | + if (!outputInfo) |
| 174 | + continue; |
| 175 | + |
| 176 | + worker.store.registerDrvOutput(*outputInfo); |
| 177 | + |
| 178 | + trace("finished"); |
| 179 | + co_return amDone(ecSuccess); |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + /* Derivation might not be resolved, let's try doing that */ |
| 184 | + trace("trying resolving derivation in build-trace goal"); |
| 185 | + |
| 186 | + auto g = worker.makeDerivationResolutionGoal(drvPath); |
| 187 | + g->preserveException = true; |
| 188 | + co_await await(Goals{g}); |
| 189 | + |
| 190 | + if (nrFailed > 0) { |
| 191 | + /* None left. Terminate this goal and let someone else deal |
| 192 | + with it. */ |
| 193 | + |
| 194 | + if (substituterFailed) { |
| 195 | + worker.failedSubstitutions++; |
| 196 | + worker.updateProgress(); |
| 197 | + } |
| 198 | + |
| 199 | + /* Hack: don't indicate failure if there were no substituters. |
| 200 | + In that case the calling derivation should just do a |
| 201 | + build. */ |
| 202 | + co_return amDone(substituterFailed ? ecFailed : ecNoSubstituters, g->ex); |
| 203 | + } |
| 204 | + |
| 205 | + /* This should be set if the goal succeeded */ |
| 206 | + assert(g->resolvedDrv); |
| 207 | + |
| 208 | + if (g->resolvedDrvPath != drvPath) { |
| 209 | + /* Try everything again, now with a resolved derivation */ |
| 210 | + auto bt2 = worker.makeBuildTraceGoal({ |
| 211 | + makeConstantStorePathRef(g->resolvedDrvPath), |
| 212 | + id2.outputName, |
| 213 | + }); |
| 214 | + |
| 215 | + /* No longer need 'g' */ |
| 216 | + g = nullptr; |
| 217 | + |
| 218 | + co_await await(Goals{bt2}); |
| 219 | + |
| 220 | + /* Set the build trace value as our own. Note the signure will not |
| 221 | + match our key since we're the unresolved derivation, but that's |
| 222 | + fine. We're not writing it to the DB; that's `bt2`' job. */ |
| 223 | + if (bt2->outputInfo) |
| 224 | + outputInfo = bt2->outputInfo; |
| 225 | + |
| 226 | + co_return amDone(bt2->exitCode, bt2->ex); |
| 227 | + } else { |
| 228 | + /* None left. Terminate this goal and let someone else deal |
| 229 | + with it. */ |
| 230 | + debug("build trace is not known for '%s', derivation is already resolved", id2.to_string()); |
| 231 | + |
| 232 | + if (substituterFailed) { |
| 233 | + worker.failedSubstitutions++; |
| 234 | + worker.updateProgress(); |
| 235 | + } |
| 236 | + |
| 237 | + /* Hack: don't indicate failure if there were no substituters. |
| 238 | + In that case the calling derivation should just do a |
| 239 | + build. */ |
| 240 | + co_return amDone( |
| 241 | + substituterFailed ? ecFailed : ecNoSubstituters, |
| 242 | + Error("build trace is not known for '%s', derivation is already resolved", id2.to_string())); |
| 243 | + } |
| 244 | +} |
| 245 | + |
| 246 | +std::string BuildTraceGoal::key() |
| 247 | +{ |
| 248 | + /* "a$" ensures substitution goals happen before derivation |
| 249 | + goals. */ |
| 250 | + return "a$" + std::string(id.to_string(worker.store)); |
| 251 | +} |
| 252 | + |
| 253 | +void BuildTraceGoal::handleEOF(Descriptor fd) |
| 254 | +{ |
| 255 | + worker.wakeUp(shared_from_this()); |
| 256 | +} |
| 257 | + |
| 258 | +} |
0 commit comments