Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/libstore/build/derivation-building-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ struct LogFile
~LogFile();
};

/**
* The local build limit that applies to `drv`.
*
* Builtin builders do no user work and cannot be offloaded, since their
* platform is `builtin`, which no machine advertises. So `max-jobs = 0`, which
* means "build everything remotely", must not make them unbuildable: `nix
* profile` and `nix-env` both need `builtin:buildenv` to run.
*/
size_t DerivationBuildingGoal::buildSlotLimit() const
{
auto limit = worker.settings.maxBuildJobs.get();
return drv->isBuiltin() ? std::max(1u, limit) : limit;
}

struct LocalBuildRejection
{
bool maxJobsZero = false;
Expand Down Expand Up @@ -346,7 +360,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild(StorePathSet inputPaths)
checkPathValidity(initialOutputs);

auto localBuildResult = [&]() -> std::variant<LocalBuildCapability, LocalBuildRejection> {
bool maxJobsZero = worker.settings.maxBuildJobs.get() == 0;
bool maxJobsZero = buildSlotLimit() == 0;

auto * localStoreP = dynamic_cast<LocalStore *>(&worker.store);
if (!localStoreP)
Expand Down Expand Up @@ -854,7 +868,7 @@ Goal::Co DerivationBuildingGoal::buildLocally(
while (true) {

unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= worker.settings.maxBuildJobs) {
if (curBuilds >= buildSlotLimit()) {
outputLocks.unlock();
co_await waitForBuildSlot();
co_return tryToBuild(std::move(inputPaths));
Expand Down
6 changes: 6 additions & 0 deletions src/libstore/build/goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ void TimedOut::anchor() {}

void Goal::anchor() {}

size_t Goal::buildSlotLimit() const
{
assert(jobCategory() == JobCategory::Build);
return worker.settings.maxBuildJobs;
}

using Co = nix::Goal::Co;
using promise_type = nix::Goal::promise_type;

Expand Down
22 changes: 15 additions & 7 deletions src/libstore/build/worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ void Worker::waitForBuildSlot(GoalPtr goal)
if (goal->jobCategory() == JobCategory::Substitution)
return getNrSubstitutions() < settings.maxSubstitutionJobs;
else
return getNrLocalBuilds() < settings.maxBuildJobs;
return getNrLocalBuilds() < goal->buildSlotLimit();
}();

if (slotAvailable)
Expand Down Expand Up @@ -324,21 +324,29 @@ void Worker::run(const Goals & _topGoals)
break; // stuff may have been cancelled
}

auto wakeSlotWaiters = [this](WeakGoals & waiting, size_t running, size_t limit) {
auto wakeSlotWaiters = [this](WeakGoals & waiting, size_t running, auto getLimit) {
auto it = waiting.begin();
while (it != waiting.end() && running < limit) {
while (it != waiting.end()) {
auto goal = it->lock();
it = waiting.erase(it);
if (!goal)
if (!goal) {
it = waiting.erase(it);
continue;
}
if (running >= getLimit(*goal)) {
++it;
continue;
}
it = waiting.erase(it);
wakeUp(goal);
++running;
}
};

wakeSlotWaiters(wantingToSubstitute, getNrSubstitutions(), [&](const Goal &) {
return std::max<std::size_t>(1, settings.maxSubstitutionJobs);
});
wakeSlotWaiters(
wantingToSubstitute, getNrSubstitutions(), std::max<std::size_t>(1, settings.maxSubstitutionJobs));
wakeSlotWaiters(wantingToBuild, getNrLocalBuilds(), settings.maxBuildJobs);
wantingToBuild, getNrLocalBuilds(), [&](const Goal & goal) { return goal.buildSlotLimit(); });
}

if (topGoals.empty())
Expand Down
76 changes: 55 additions & 21 deletions src/libstore/builtins/buildenv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "nix/store/builtins.hh"
#include "nix/store/derivations.hh"
#include "nix/util/signals.hh"
#include "nix/util/util.hh"

#include <sys/stat.h>
#include <sys/types.h>
Expand All @@ -12,6 +13,52 @@ namespace nix {

void BuildEnvFileConflictError::anchor() {}

std::string encodeBuildenvPackages(const Packages & pkgs)
{
std::string res;
for (auto & pkg : pkgs) {
if (!res.empty())
res += ' ';
/* A record is `active priority numOutputs path...`; we emit one output
per record, which decodes to the same flat list of packages. */
res += fmt("%s %d 1 %s", pkg.active ? "true" : "false", pkg.priority, pkg.path.string());
}
return res;
}

Packages decodeBuildenvPackages(std::string_view s)
{
Packages pkgs;

auto tokens = tokenizeString<Strings>(s);
auto it = tokens.begin();

auto next = [&]() -> std::string {
if (it == tokens.end())
throw Error("'derivations' attribute of a buildenv derivation ends unexpectedly");
return std::move(*it++);
};

auto nextInt = [&]() {
auto tok = next();
if (auto n = string2Int<int>(tok))
return *n;
throw Error("'derivations' attribute of a buildenv derivation has '%s' where a number was expected", tok);
};

while (it != tokens.end()) {
const bool active = next() != "false";
const int priority = nextInt();
const int outputs = nextInt();
if (outputs < 0)
throw Error("'derivations' attribute of a buildenv derivation has a negative output count");
for (int n = 0; n < outputs; n++)
pkgs.emplace_back(next(), active, priority);
}

return pkgs;
}

RegisterBuiltinBuilder::BuiltinBuilders & RegisterBuiltinBuilder::builtinBuilders()
{
static RegisterBuiltinBuilder::BuiltinBuilders builders;
Expand Down Expand Up @@ -182,28 +229,15 @@ static void builtinBuildenv(const BuiltinBuilderContext & ctx)
auto out = ctx.outputs.at("out");
createDirs(out);

/* Convert the stuff we get from the environment back into a
* coherent data type. */
Packages pkgs;
{
auto derivations = tokenizeString<Strings>(getAttr("derivations"));

auto itemIt = derivations.begin();
while (itemIt != derivations.end()) {
/* !!! We're trusting the caller to structure derivations env var correctly */
const bool active = "false" != *itemIt++;
const int priority = stoi(*itemIt++);
const size_t outputs = stoul(*itemIt++);

for (size_t n{0}; n < outputs; n++) {
pkgs.emplace_back(std::move(*itemIt++), active, priority);
}
}
}

buildProfile(out, std::move(pkgs));
buildProfile(out, decodeBuildenvPackages(getAttr("derivations")));

createSymlink(getAttr("manifest"), out + "/manifest.nix");
/* `nix profile` passes the manifest inline because it may be building
into a store that isn't mounted on this filesystem; `nix-env` passes
a store path to a Nix expression. Exactly one of the two is set. */
if (auto manifestJSON = ctx.drv.env.find("manifestJSON"); manifestJSON != ctx.drv.env.end())
writeFile(out + "/manifest.json", manifestJSON->second);
else
createSymlink(getAttr("manifest"), out + "/manifest.nix");
}

static RegisterBuiltinBuilder registerBuildenv("buildenv", builtinBuildenv);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ private:
{
return JobCategory::Build;
};

size_t buildSlotLimit() const override;
};

} // namespace nix
9 changes: 9 additions & 0 deletions src/libstore/include/nix/store/build/goal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,15 @@ public:
*/
virtual JobCategory jobCategory() const = 0;

/**
* The number of build slots available to this goal.
*
* This is only meaningful for goals in the Build job category. Most build
* goals use the configured maximum directly, but goals that cannot be
* offloaded may override it.
*/
virtual size_t buildSlotLimit() const;

protected:
Co await(Goals waitees);

Expand Down
21 changes: 19 additions & 2 deletions src/libstore/include/nix/store/builtins/buildenv.hh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ struct Package
}
};

typedef std::vector<Package> Packages;

/**
* Encode `pkgs` for the `derivations` environment variable that
* `builtin:buildenv` reads.
*
* This lives next to `decodeBuildenvPackages` so that the two cannot drift.
* `src/nix/nix-env/buildenv.nix` produces the same format from the evaluator,
* and has to be kept in sync by hand.
*/
std::string encodeBuildenvPackages(const Packages & pkgs);

/**
* Inverse of `encodeBuildenvPackages`.
*
* @throws Error if the encoding is malformed.
*/
Packages decodeBuildenvPackages(std::string_view s);

class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
{
private:
Expand All @@ -48,8 +67,6 @@ public:
}
};

typedef std::vector<Package> Packages;

void buildProfile(const std::filesystem::path & out, Packages && pkgs);

} // namespace nix
Loading
Loading