Check requiredSystemFeatures for local builds

For example, this prevents a "kvm" build on machines that don't have
KVM.

Fixes #2012.
This commit is contained in:
Eelco Dolstra 2018-09-28 15:57:27 +02:00
parent 7ae7a38c9a
commit 1e7b8deea7
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
8 changed files with 79 additions and 16 deletions

View file

@ -757,6 +757,33 @@ password <replaceable>my-password</replaceable>
</varlistentry> </varlistentry>
<varlistentry xml:id="conf-system-features"><term><literal>system-features</literal></term>
<listitem><para>A set of system “features” supported by this
machine, e.g. <literal>kvm</literal>. Derivations can express a
dependency on such features through the derivation attribute
<varname>requiredSystemFeatures</varname>. For example, the
attribute
<programlisting>
requiredSystemFeatures = [ "kvm" ];
</programlisting>
ensures that the derivation can only be built on a machine with
the <literal>kvm</literal> feature.</para>
<para>This setting by default includes <literal>kvm</literal> if
<filename>/dev/kvm</filename> is accessible, and the
pseudo-features <literal>nixos-test</literal>,
<literal>benchmark</literal> and <literal>big-parallel</literal>
that are used in Nixpkgs to route builds to specific
machines.</para>
</listitem>
</varlistentry>
<varlistentry xml:id="conf-timeout"><term><literal>timeout</literal></term> <varlistentry xml:id="conf-timeout"><term><literal>timeout</literal></term>
<listitem> <listitem>

View file

@ -12,6 +12,7 @@
</partintro> </partintro>
--> -->
<xi:include href="rl-2.2.xml" />
<xi:include href="rl-2.1.xml" /> <xi:include href="rl-2.1.xml" />
<xi:include href="rl-2.0.xml" /> <xi:include href="rl-2.0.xml" />
<xi:include href="rl-1.11.10.xml" /> <xi:include href="rl-1.11.10.xml" />

View file

@ -1646,18 +1646,13 @@ HookReply DerivationGoal::tryBuildHook()
try { try {
/* Tell the hook about system features (beyond the system type)
required from the build machine. (The hook could parse the
drv file itself, but this is easier.) */
auto features = parsedDrv->getStringsAttr("requiredSystemFeatures").value_or(Strings());
/* Send the request to the hook. */ /* Send the request to the hook. */
worker.hook->sink worker.hook->sink
<< "try" << "try"
<< (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0) << (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0)
<< drv->platform << drv->platform
<< drvPath << drvPath
<< features; << parsedDrv->getRequiredSystemFeatures();
worker.hook->sink.flush(); worker.hook->sink.flush();
/* Read the first line of input, which should be a word indicating /* Read the first line of input, which should be a word indicating
@ -1797,11 +1792,13 @@ static void preloadNSS() {
void DerivationGoal::startBuilder() void DerivationGoal::startBuilder()
{ {
/* Right platform? */ /* Right platform? */
if (!parsedDrv->canBuildLocally()) { if (!parsedDrv->canBuildLocally())
throw Error( throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}",
format("a '%1%' is required to build '%3%', but I am a '%2%'") drv->platform,
% drv->platform % settings.thisSystem % drvPath); concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
} drvPath,
settings.thisSystem,
concatStringsSep(", ", settings.systemFeatures));
if (drv->isBuiltin()) if (drv->isBuiltin())
preloadNSS(); preloadNSS();
@ -2625,7 +2622,7 @@ void DerivationGoal::runChild()
createDirs(chrootRootDir + "/dev/shm"); createDirs(chrootRootDir + "/dev/shm");
createDirs(chrootRootDir + "/dev/pts"); createDirs(chrootRootDir + "/dev/pts");
ss.push_back("/dev/full"); ss.push_back("/dev/full");
if (pathExists("/dev/kvm")) if (settings.systemFeatures.get().count("kvm") && pathExists("/dev/kvm"))
ss.push_back("/dev/kvm"); ss.push_back("/dev/kvm");
ss.push_back("/dev/null"); ss.push_back("/dev/null");
ss.push_back("/dev/random"); ss.push_back("/dev/random");

View file

@ -86,6 +86,21 @@ unsigned int Settings::getDefaultCores()
return std::max(1U, std::thread::hardware_concurrency()); return std::max(1U, std::thread::hardware_concurrency());
} }
StringSet Settings::getDefaultSystemFeatures()
{
/* For backwards compatibility, accept some "features" that are
used in Nixpkgs to route builds to certain machines but don't
actually require anything special on the machines. */
StringSet features{"nixos-test", "benchmark", "big-parallel"};
#if __linux__
if (access("/dev/kvm", R_OK | W_OK) == 0)
features.insert("kvm");
#endif
return features;
}
const string nixVersion = PACKAGE_VERSION; const string nixVersion = PACKAGE_VERSION;
template<> void BaseSetting<SandboxMode>::set(const std::string & str) template<> void BaseSetting<SandboxMode>::set(const std::string & str)

View file

@ -32,6 +32,8 @@ class Settings : public Config {
unsigned int getDefaultCores(); unsigned int getDefaultCores();
StringSet getDefaultSystemFeatures();
public: public:
Settings(); Settings();
@ -261,6 +263,10 @@ public:
"These may be supported natively (e.g. armv7 on some aarch64 CPUs " "These may be supported natively (e.g. armv7 on some aarch64 CPUs "
"or using hacks like qemu-user."}; "or using hacks like qemu-user."};
Setting<StringSet> systemFeatures{this, getDefaultSystemFeatures(),
"system-features",
"Optional features that this system implements (like \"kvm\")."};
Setting<Strings> substituters{this, Setting<Strings> substituters{this,
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(), nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
"substituters", "substituters",

View file

@ -82,11 +82,25 @@ std::experimental::optional<Strings> ParsedDerivation::getStringsAttr(const std:
} }
} }
StringSet ParsedDerivation::getRequiredSystemFeatures() const
{
StringSet res;
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
res.insert(i);
return res;
}
bool ParsedDerivation::canBuildLocally() const bool ParsedDerivation::canBuildLocally() const
{ {
return drv.platform == settings.thisSystem if (drv.platform != settings.thisSystem.get()
|| settings.extraPlatforms.get().count(drv.platform) > 0 && !settings.extraPlatforms.get().count(drv.platform)
|| drv.isBuiltin(); && !drv.isBuiltin())
return false;
for (auto & feature : getRequiredSystemFeatures())
if (!settings.systemFeatures.get().count(feature)) return false;
return true;
} }
bool ParsedDerivation::willBuildLocally() const bool ParsedDerivation::willBuildLocally() const

View file

@ -25,6 +25,8 @@ public:
std::experimental::optional<Strings> getStringsAttr(const std::string & name) const; std::experimental::optional<Strings> getStringsAttr(const std::string & name) const;
StringSet getRequiredSystemFeatures() const;
bool canBuildLocally() const; bool canBuildLocally() const;
bool willBuildLocally() const; bool willBuildLocally() const;

View file

@ -11,7 +11,8 @@ rm -rf $TEST_ROOT/store0 $TEST_ROOT/store1
nix build -f build-hook.nix -o $TEST_ROOT/result --max-jobs 0 \ nix build -f build-hook.nix -o $TEST_ROOT/result --max-jobs 0 \
--sandbox-paths /nix/store --sandbox-build-dir /build-tmp \ --sandbox-paths /nix/store --sandbox-build-dir /build-tmp \
--builders "$TEST_ROOT/store0; $TEST_ROOT/store1 - - 1 1 foo" --builders "$TEST_ROOT/store0; $TEST_ROOT/store1 - - 1 1 foo" \
--system-features foo
outPath=$TEST_ROOT/result outPath=$TEST_ROOT/result