libstore: turn DerivationGoal::work into *one* promise

Change-Id: Ic2f7bc2bd6a1879ad614e4be81a7214f64eb0e85
This commit is contained in:
eldritch horrors 2024-09-30 01:31:30 +02:00
parent 3edc272341
commit 7752927660
4 changed files with 121 additions and 131 deletions

View file

@ -71,7 +71,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
, wantedOutputs(wantedOutputs) , wantedOutputs(wantedOutputs)
, buildMode(buildMode) , buildMode(buildMode)
{ {
state = &DerivationGoal::getDerivation;
name = fmt( name = fmt(
"building of '%s' from .drv file", "building of '%s' from .drv file",
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store)); DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
@ -91,7 +90,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
{ {
this->drv = std::make_unique<Derivation>(drv); this->drv = std::make_unique<Derivation>(drv);
state = &DerivationGoal::haveDerivation;
name = fmt( name = fmt(
"building of '%s' from in-memory derivation", "building of '%s' from in-memory derivation",
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store)); DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
@ -129,7 +127,7 @@ Goal::Finished DerivationGoal::timedOut(Error && ex)
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::work() noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::work() noexcept
{ {
return (this->*state)(slotToken.valid()); return useDerivation ? getDerivation() : haveDerivation();
} }
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs) void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
@ -153,7 +151,7 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::getDerivation(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::getDerivation() noexcept
try { try {
trace("init"); trace("init");
@ -161,18 +159,17 @@ try {
exists. If it doesn't, it may be created through a exists. If it doesn't, it may be created through a
substitute. */ substitute. */
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) { if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
return loadDerivation(inBuildSlot); co_return co_await loadDerivation();
} }
(co_await waitForGoals(worker.goalFactory().makePathSubstitutionGoal(drvPath))).value();
state = &DerivationGoal::loadDerivation; co_return co_await loadDerivation();
return waitForGoals(worker.goalFactory().makePathSubstitutionGoal(drvPath));
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::loadDerivation(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::loadDerivation() noexcept
try { try {
trace("loading derivation"); trace("loading derivation");
@ -203,13 +200,13 @@ try {
} }
assert(drv); assert(drv);
return haveDerivation(inBuildSlot); return haveDerivation();
} catch (...) { } catch (...) {
return {std::current_exception()}; return {std::current_exception()};
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::haveDerivation(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::haveDerivation() noexcept
try { try {
trace("have derivation"); trace("have derivation");
@ -237,7 +234,7 @@ try {
}); });
} }
return gaveUpOnSubstitution(inBuildSlot); co_return co_await gaveUpOnSubstitution();
} }
for (auto & i : drv->outputsAndOptPaths(worker.store)) for (auto & i : drv->outputsAndOptPaths(worker.store))
@ -259,7 +256,7 @@ try {
/* If they are all valid, then we're done. */ /* If they are all valid, then we're done. */
if (allValid && buildMode == bmNormal) { if (allValid && buildMode == bmNormal) {
return {done(BuildResult::AlreadyValid, std::move(validOutputs))}; co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
} }
/* We are first going to try to create the invalid output paths /* We are first going to try to create the invalid output paths
@ -290,17 +287,15 @@ try {
} }
} }
if (dependencies.empty()) { /* to prevent hang (no wake-up event) */ if (!dependencies.empty()) { /* to prevent hang (no wake-up event) */
return outputsSubstitutionTried(inBuildSlot); (co_await waitForGoals(dependencies.releaseAsArray())).value();
} else {
state = &DerivationGoal::outputsSubstitutionTried;
return waitForGoals(dependencies.releaseAsArray());
} }
co_return co_await outputsSubstitutionTried();
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::outputsSubstitutionTried(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::outputsSubstitutionTried() noexcept
try { try {
trace("all outputs substituted (maybe)"); trace("all outputs substituted (maybe)");
@ -350,7 +345,7 @@ try {
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) { if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed; needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
return haveDerivation(inBuildSlot); return haveDerivation();
} }
auto [allValid, validOutputs] = checkPathValidity(); auto [allValid, validOutputs] = checkPathValidity();
@ -366,7 +361,7 @@ try {
worker.store.printStorePath(drvPath)); worker.store.printStorePath(drvPath));
/* Nothing to wait for; tail call */ /* Nothing to wait for; tail call */
return gaveUpOnSubstitution(inBuildSlot); return gaveUpOnSubstitution();
} catch (...) { } catch (...) {
return {std::current_exception()}; return {std::current_exception()};
} }
@ -374,7 +369,7 @@ try {
/* At least one of the output paths could not be /* At least one of the output paths could not be
produced using a substitute. So we have to build instead. */ produced using a substitute. So we have to build instead. */
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::gaveUpOnSubstitution(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::gaveUpOnSubstitution() noexcept
try { try {
kj::Vector<std::pair<GoalPtr, kj::Promise<void>>> dependencies; kj::Vector<std::pair<GoalPtr, kj::Promise<void>>> dependencies;
@ -437,14 +432,12 @@ try {
dependencies.add(worker.goalFactory().makePathSubstitutionGoal(i)); dependencies.add(worker.goalFactory().makePathSubstitutionGoal(i));
} }
if (dependencies.empty()) {/* to prevent hang (no wake-up event) */ if (!dependencies.empty()) {/* to prevent hang (no wake-up event) */
return inputsRealised(inBuildSlot); (co_await waitForGoals(dependencies.releaseAsArray())).value();
} else {
state = &DerivationGoal::inputsRealised;
return waitForGoals(dependencies.releaseAsArray());
} }
co_return co_await inputsRealised();
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
@ -503,17 +496,17 @@ try {
} }
if (dependencies.empty()) { if (dependencies.empty()) {
return {done(BuildResult::AlreadyValid, assertPathValidity())}; co_return done(BuildResult::AlreadyValid, assertPathValidity());
} }
state = &DerivationGoal::closureRepaired; (co_await waitForGoals(dependencies.releaseAsArray())).value();
return waitForGoals(dependencies.releaseAsArray()); co_return co_await closureRepaired();
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::closureRepaired(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::closureRepaired() noexcept
try { try {
trace("closure repaired"); trace("closure repaired");
if (nrFailed > 0) if (nrFailed > 0)
@ -525,14 +518,14 @@ try {
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::inputsRealised(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::inputsRealised() noexcept
try { try {
trace("all inputs realised"); trace("all inputs realised");
if (nrFailed != 0) { if (nrFailed != 0) {
if (!useDerivation) if (!useDerivation)
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath)); throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
return {done( co_return done(
BuildResult::DependencyFailed, BuildResult::DependencyFailed,
{}, {},
Error( Error(
@ -540,12 +533,12 @@ try {
nrFailed, nrFailed,
worker.store.printStorePath(drvPath) worker.store.printStorePath(drvPath)
) )
)}; );
} }
if (retrySubstitution == RetrySubstitution::YesNeed) { if (retrySubstitution == RetrySubstitution::YesNeed) {
retrySubstitution = RetrySubstitution::AlreadyRetried; retrySubstitution = RetrySubstitution::AlreadyRetried;
return haveDerivation(inBuildSlot); co_return co_await haveDerivation();
} }
/* Gather information necessary for computing the closure and/or /* Gather information necessary for computing the closure and/or
@ -611,8 +604,8 @@ try {
pathResolved, wantedOutputs, buildMode); pathResolved, wantedOutputs, buildMode);
resolvedDrvGoal = dependency.first; resolvedDrvGoal = dependency.first;
state = &DerivationGoal::resolvedFinished; (co_await waitForGoals(std::move(dependency))).value();
return waitForGoals(std::move(dependency)); co_return co_await resolvedFinished();
} }
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths; std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
@ -676,10 +669,9 @@ try {
/* Okay, try to build. Note that here we don't wait for a build /* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a slot to become available, since we don't need one if there is a
build hook. */ build hook. */
state = &DerivationGoal::tryToBuild; co_return co_await tryToBuild();
return tryToBuild(inBuildSlot);
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
void DerivationGoal::started() void DerivationGoal::started()
@ -695,8 +687,9 @@ void DerivationGoal::started()
mcRunningBuilds = worker.runningBuilds.addTemporarily(1); mcRunningBuilds = worker.runningBuilds.addTemporarily(1);
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryToBuild(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryToBuild() noexcept
try { try {
retry:
trace("trying to build"); trace("trying to build");
/* Obtain locks on all output paths, if the paths are known a priori. /* Obtain locks on all output paths, if the paths are known a priori.
@ -730,7 +723,9 @@ try {
if (!actLock) if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting, actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles)))); fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
return waitForAWhile(); (co_await waitForAWhile()).value();
// we can loop very often, and `co_return co_await` always allocates a new frame
goto retry;
} }
actLock.reset(); actLock.reset();
@ -747,7 +742,7 @@ try {
if (buildMode != bmCheck && allValid) { if (buildMode != bmCheck && allValid) {
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath)); debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
outputLocks.setDeletion(true); outputLocks.setDeletion(true);
return {done(BuildResult::AlreadyValid, std::move(validOutputs))}; co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
} }
/* If any of the outputs already exist but are not valid, delete /* If any of the outputs already exist but are not valid, delete
@ -767,47 +762,56 @@ try {
&& settings.maxBuildJobs.get() != 0; && settings.maxBuildJobs.get() != 0;
if (!buildLocally) { if (!buildLocally) {
auto hookReply = tryBuildHook(inBuildSlot); auto hookReply = tryBuildHook();
auto result = std::visit( switch (hookReply.index()) {
overloaded{ case 0: {
[&](HookReply::Accept & a) -> std::optional<kj::Promise<Result<WorkResult>>> { HookReply::Accept & a = std::get<0>(hookReply);
/* Yes, it has started doing so. Wait until we get /* Yes, it has started doing so. Wait until we get
EOF from the hook. */ EOF from the hook. */
actLock.reset(); actLock.reset();
buildResult.startTime = time(0); // inexact buildResult.startTime = time(0); // inexact
state = &DerivationGoal::buildDone; started();
started(); auto r = co_await a.promise;
return continueOrError(std::move(a.promise)); if (r.has_value()) {
}, co_return co_await buildDone();
[&](HookReply::Postpone) -> std::optional<kj::Promise<Result<WorkResult>>> { } else if (r.has_error()) {
/* Not now; wait until at least one child finishes or co_return r.assume_error();
the wake-up timeout expires. */ } else {
if (!actLock) co_return r.assume_exception();
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting, }
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath)))); }
outputLocks.unlock();
return waitForAWhile(); case 1: {
}, HookReply::Decline _ [[gnu::unused]] = std::get<1>(hookReply);
[&](HookReply::Decline) -> std::optional<kj::Promise<Result<WorkResult>>> { break;
/* We should do it ourselves. */ }
return std::nullopt;
}, case 2: {
}, HookReply::Postpone _ [[gnu::unused]] = std::get<2>(hookReply);
hookReply); /* Not now; wait until at least one child finishes or
if (result) { the wake-up timeout expires. */
return std::move(*result); if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
outputLocks.unlock();
(co_await waitForAWhile()).value();
goto retry;
}
default:
// can't static_assert this because HookReply *subclasses* variant and std::variant_size breaks
assert(false && "unexpected hook reply");
} }
} }
actLock.reset(); actLock.reset();
state = &DerivationGoal::tryLocalBuild; co_return co_await tryLocalBuild();
return tryLocalBuild(inBuildSlot);
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryLocalBuild(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::tryLocalBuild() noexcept
try { try {
throw Error( throw Error(
"unable to build with a primary store that isn't a local store; " "unable to build with a primary store that isn't a local store; "
@ -970,7 +974,7 @@ void runPostBuildHook(
proc.getStdout()->drainInto(sink); proc.getStdout()->drainInto(sink);
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::buildDone(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::buildDone() noexcept
try { try {
trace("build done"); trace("build done");
@ -1090,7 +1094,7 @@ try {
return {std::current_exception()}; return {std::current_exception()};
} }
kj::Promise<Result<Goal::WorkResult>> DerivationGoal::resolvedFinished(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> DerivationGoal::resolvedFinished() noexcept
try { try {
trace("resolved derivation finished"); trace("resolved derivation finished");
@ -1163,7 +1167,7 @@ try {
return {std::current_exception()}; return {std::current_exception()};
} }
HookReply DerivationGoal::tryBuildHook(bool inBuildSlot) HookReply DerivationGoal::tryBuildHook()
{ {
if (!worker.hook.available || !useDerivation) return HookReply::Decline{}; if (!worker.hook.available || !useDerivation) return HookReply::Decline{};
@ -1175,7 +1179,7 @@ HookReply DerivationGoal::tryBuildHook(bool inBuildSlot)
/* Send the request to the hook. */ /* Send the request to the hook. */
worker.hook.instance->sink worker.hook.instance->sink
<< "try" << "try"
<< (inBuildSlot ? 1 : 0) << (slotToken.valid() ? 1 : 0)
<< drv->platform << drv->platform
<< worker.store.printStorePath(drvPath) << worker.store.printStorePath(drvPath)
<< parsedDrv->getRequiredSystemFeatures(); << parsedDrv->getRequiredSystemFeatures();
@ -1745,18 +1749,4 @@ void DerivationGoal::waiteeDone(GoalPtr waitee)
} }
} }
} }
kj::Promise<Result<Goal::WorkResult>>
DerivationGoal::continueOrError(kj::Promise<Outcome<void, Goal::Finished>> p)
{
return p.then([](auto r) -> Result<WorkResult> {
if (r.has_value()) {
return StillAlive{};
} else if (r.has_error()) {
return r.assume_error();
} else {
return r.assume_exception();
}
});
}
} }

View file

@ -216,9 +216,6 @@ struct DerivationGoal : public Goal
*/ */
std::optional<DerivationType> derivationType; std::optional<DerivationType> derivationType;
typedef kj::Promise<Result<WorkResult>> (DerivationGoal::*GoalState)(bool inBuildSlot) noexcept;
GoalState state;
BuildMode buildMode; BuildMode buildMode;
NotifyingCounter<uint64_t>::Bump mcExpectedBuilds, mcRunningBuilds; NotifyingCounter<uint64_t>::Bump mcExpectedBuilds, mcRunningBuilds;
@ -257,23 +254,23 @@ struct DerivationGoal : public Goal
/** /**
* The states. * The states.
*/ */
kj::Promise<Result<WorkResult>> getDerivation(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> getDerivation() noexcept;
kj::Promise<Result<WorkResult>> loadDerivation(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> loadDerivation() noexcept;
kj::Promise<Result<WorkResult>> haveDerivation(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> haveDerivation() noexcept;
kj::Promise<Result<WorkResult>> outputsSubstitutionTried(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> outputsSubstitutionTried() noexcept;
kj::Promise<Result<WorkResult>> gaveUpOnSubstitution(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> gaveUpOnSubstitution() noexcept;
kj::Promise<Result<WorkResult>> closureRepaired(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> closureRepaired() noexcept;
kj::Promise<Result<WorkResult>> inputsRealised(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> inputsRealised() noexcept;
kj::Promise<Result<WorkResult>> tryToBuild(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> tryToBuild() noexcept;
virtual kj::Promise<Result<WorkResult>> tryLocalBuild(bool inBuildSlot) noexcept; virtual kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept;
kj::Promise<Result<WorkResult>> buildDone(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> buildDone() noexcept;
kj::Promise<Result<WorkResult>> resolvedFinished(bool inBuildSlot) noexcept; kj::Promise<Result<WorkResult>> resolvedFinished() noexcept;
/** /**
* Is the build hook willing to perform the build? * Is the build hook willing to perform the build?
*/ */
HookReply tryBuildHook(bool inBuildSlot); HookReply tryBuildHook();
virtual int getChildStatus(); virtual int getChildStatus();
@ -325,8 +322,6 @@ protected:
Finished tooMuchLogs(); Finished tooMuchLogs();
void flushLine(); void flushLine();
static kj::Promise<Result<WorkResult>> continueOrError(kj::Promise<Outcome<void, Goal::Finished>> p);
public: public:
/** /**
* Wrappers around the corresponding Store methods that first consult the * Wrappers around the corresponding Store methods that first consult the

View file

@ -147,20 +147,18 @@ void LocalDerivationGoal::killSandbox(bool getStats)
} }
kj::Promise<Result<Goal::WorkResult>> LocalDerivationGoal::tryLocalBuild(bool inBuildSlot) noexcept kj::Promise<Result<Goal::WorkResult>> LocalDerivationGoal::tryLocalBuild() noexcept
try { try {
retry:
#if __APPLE__ #if __APPLE__
additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or(""); additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or("");
#endif #endif
if (!inBuildSlot) { if (!slotToken.valid()) {
state = &DerivationGoal::tryToBuild;
outputLocks.unlock(); outputLocks.unlock();
if (worker.localBuilds.capacity() > 0) { if (worker.localBuilds.capacity() > 0) {
return worker.localBuilds.acquire().then([this](auto token) { slotToken = co_await worker.localBuilds.acquire();
slotToken = std::move(token); co_return co_await tryToBuild();
return work();
});
} }
if (getMachines().empty()) { if (getMachines().empty()) {
throw Error( throw Error(
@ -215,7 +213,9 @@ try {
if (!actLock) if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting, actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for a free build user ID for '%s'", Magenta(worker.store.printStorePath(drvPath)))); fmt("waiting for a free build user ID for '%s'", Magenta(worker.store.printStorePath(drvPath))));
return waitForAWhile(); (co_await waitForAWhile()).value();
// we can loop very often, and `co_return co_await` always allocates a new frame
goto retry;
} }
} }
@ -246,22 +246,27 @@ try {
/* Okay, we have to build. */ /* Okay, we have to build. */
auto promise = startBuilder(); auto promise = startBuilder();
/* This state will be reached when we get EOF on the child's
log pipe. */
state = &DerivationGoal::buildDone;
started(); started();
return continueOrError(std::move(promise)); auto r = co_await promise;
if (r.has_value()) {
// all good so far
} else if (r.has_error()) {
co_return r.assume_error();
} else {
co_return r.assume_exception();
}
} catch (BuildError & e) { } catch (BuildError & e) {
outputLocks.unlock(); outputLocks.unlock();
buildUser.reset(); buildUser.reset();
auto report = done(BuildResult::InputRejected, {}, std::move(e)); auto report = done(BuildResult::InputRejected, {}, std::move(e));
report.permanentFailure = true; report.permanentFailure = true;
return {std::move(report)}; co_return report;
} }
co_return co_await buildDone();
} catch (...) { } catch (...) {
return {std::current_exception()}; co_return result::failure(std::current_exception());
} }

View file

@ -213,7 +213,7 @@ struct LocalDerivationGoal : public DerivationGoal
/** /**
* The additional states. * The additional states.
*/ */
kj::Promise<Result<WorkResult>> tryLocalBuild(bool inBuildSlot) noexcept override; kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept override;
/** /**
* Start building a derivation. * Start building a derivation.