#pragma once ///@file #include "async-semaphore.hh" #include "concepts.hh" #include "notifying-counter.hh" #include "types.hh" #include "lock.hh" #include "store-api.hh" #include "goal.hh" #include "realisation.hh" #include #include #include namespace nix { /* Forward definition. */ struct DerivationGoal; struct PathSubstitutionGoal; class DrvOutputSubstitutionGoal; class LocalStore; typedef std::chrono::time_point steady_time_point; /* Forward definition. */ struct HookInstance; class GoalFactory { public: virtual std::pair, kj::Promise>> makeDerivationGoal( const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal ) = 0; virtual std::pair, kj::Promise>> makeBasicDerivationGoal( const StorePath & drvPath, const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal ) = 0; /** * @ref SubstitutionGoal "substitution goal" */ virtual std::pair, kj::Promise>> makePathSubstitutionGoal( const StorePath & storePath, RepairFlag repair = NoRepair, std::optional ca = std::nullopt ) = 0; virtual std::pair, kj::Promise>> makeDrvOutputSubstitutionGoal( const DrvOutput & id, RepairFlag repair = NoRepair, std::optional ca = std::nullopt ) = 0; /** * Make a goal corresponding to the `DerivedPath`. * * It will be a `DerivationGoal` for a `DerivedPath::Built` or * a `SubstitutionGoal` for a `DerivedPath::Opaque`. */ virtual std::pair>> makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) = 0; }; // elaborate hoax to let goals access factory methods while hiding them from the public class WorkerBase : protected GoalFactory { friend struct DerivationGoal; friend struct PathSubstitutionGoal; friend class DrvOutputSubstitutionGoal; protected: GoalFactory & goalFactory() { return *this; } }; /** * The worker class. */ class Worker : public WorkerBase { public: using Targets = std::vector>>>; struct Results { /** Results of individual goals, if available. Goal results will be * added to this map with the index they had in the `Targets` list * returned by the goal factory function passed to `work`. If some * goals did not complete processing, e.g. due to an early exit on * goal failures, not all indices will be set. This may be used to * detect which of the goals were cancelled before they completed. */ std::map goals; /** * The exit status in case of failure. * * In the case of a build failure, returned value follows this * bitmask: * * ``` * 0b1100100 * ^^^^ * |||`- timeout * ||`-- output hash mismatch * |`--- build failure * `---- not deterministic * ``` * * In other words, the failure code is at least 100 (0b1100100), but * might also be greater. * * Otherwise (no build failure, but some other sort of failure by * assumption), this returned value is 1. */ unsigned int failingExitStatus; }; private: bool running = false; template struct CachedGoal { std::shared_ptr goal; kj::ForkedPromise> promise{nullptr}; }; /** * Maps used to prevent multiple instantiations of a goal for the * same derivation / path. */ std::map> derivationGoals; std::map> substitutionGoals; std::map> drvOutputSubstitutionGoals; /** * Cache for pathContentsGood(). */ std::map pathContentsGoodCache; /** * Set if at least one derivation had a BuildError (i.e. permanent * failure). */ bool permanentFailure = false; /** * Set if at least one derivation had a timeout. */ bool timedOut = false; /** * Set if at least one derivation fails with a hash mismatch. */ bool hashMismatch = false; /** * Set if at least one derivation is not deterministic in check mode. */ bool checkMismatch = false; /** * Pass current stats counters to the logger for progress bar updates. */ kj::Promise> updateStatistics(); AsyncSemaphore statisticsUpdateSignal{1}; std::optional statisticsUpdateInhibitor; /** * Mark statistics as outdated, such that `updateStatistics` will be called. */ void updateStatisticsLater() { statisticsUpdateInhibitor = {}; } kj::Promise> runImpl(Targets topGoals); kj::Promise> boopGC(LocalStore & localStore); public: const Activity act; const Activity actDerivations; const Activity actSubstitutions; Store & store; Store & evalStore; kj::AsyncIoContext & aio; AsyncSemaphore substitutions, localBuilds; private: kj::TaskSet children; public: struct HookState { std::unique_ptr instance; /** * Whether to ask the build hook if it can build a derivation. If * it answers with "decline-permanently", we don't try again. */ bool available = true; }; HookState hook; NotifyingCounter expectedBuilds{[this] { updateStatisticsLater(); }}; NotifyingCounter doneBuilds{[this] { updateStatisticsLater(); }}; NotifyingCounter failedBuilds{[this] { updateStatisticsLater(); }}; NotifyingCounter runningBuilds{[this] { updateStatisticsLater(); }}; NotifyingCounter expectedSubstitutions{[this] { updateStatisticsLater(); }}; NotifyingCounter doneSubstitutions{[this] { updateStatisticsLater(); }}; NotifyingCounter failedSubstitutions{[this] { updateStatisticsLater(); }}; NotifyingCounter runningSubstitutions{[this] { updateStatisticsLater(); }}; NotifyingCounter expectedDownloadSize{[this] { updateStatisticsLater(); }}; NotifyingCounter doneDownloadSize{[this] { updateStatisticsLater(); }}; NotifyingCounter expectedNarSize{[this] { updateStatisticsLater(); }}; NotifyingCounter doneNarSize{[this] { updateStatisticsLater(); }}; private: Worker(Store & store, Store & evalStore, kj::AsyncIoContext & aio); ~Worker(); /** * Make a goal (with caching). */ /** * @ref DerivationGoal "derivation goal" */ template G> std::pair, kj::Promise>> makeGoalCommon( std::map> & map, const ID & key, InvocableR> auto create, InvocableR auto modify ); std::pair, kj::Promise>> makeDerivationGoal( const StorePath & drvPath, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override; std::pair, kj::Promise>> makeBasicDerivationGoal( const StorePath & drvPath, const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal) override; /** * @ref SubstitutionGoal "substitution goal" */ std::pair, kj::Promise>> makePathSubstitutionGoal( const StorePath & storePath, RepairFlag repair = NoRepair, std::optional ca = std::nullopt ) override; std::pair, kj::Promise>> makeDrvOutputSubstitutionGoal( const DrvOutput & id, RepairFlag repair = NoRepair, std::optional ca = std::nullopt ) override; /** * Make a goal corresponding to the `DerivedPath`. * * It will be a `DerivationGoal` for a `DerivedPath::Built` or * a `SubstitutionGoal` for a `DerivedPath::Opaque`. */ std::pair>> makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal) override; public: /** * Loop until the specified top-level goals have finished. */ Results run(std::function req); /** * Check whether the given valid path exists and has the right * contents. */ bool pathContentsGood(const StorePath & path); void markContentsGood(const StorePath & path); template friend Results processGoals(Store & store, Store & evalStore, kj::AsyncIoContext & aio, MkGoals && mkGoals); }; template Worker::Results processGoals(Store & store, Store & evalStore, kj::AsyncIoContext & aio, MkGoals && mkGoals) { return Worker(store, evalStore, aio).run(std::forward(mkGoals)); } }