1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-09 03:56:01 +01:00

Add external builders

These are helper programs that execute derivations for specified
system types (e.g. using QEMU to emulate another system type).

To use, set `external-builders`:

  external-builders = [{"systems": ["aarch64-linux"], "program": "/path/to/external-builder.py"}]

The external builder gets one command line argument, the path to a JSON file containing all necessary information about the derivation:

  {
    "args": [...],
    "builder": "/nix/store/kwcyvgdg98n98hqapaz8sw92pc2s78x6-bash-5.2p37/bin/bash",
    "env": {
      "HOME": "/homeless-shelter",
      ...
    },
    "realStoreDir": "/tmp/nix/nix/store",
    "storeDir": "/nix/store",
    "tmpDir": "/tmp/nix-shell.dzQ2hE/nix-build-patchelf-0.14.3.drv-46/build",
    "tmpDirInSandbox": "/build"
  }

Co-authored-by: Cole Helbling <cole.helbling@determinate.systems>
This commit is contained in:
Eelco Dolstra 2025-10-03 14:34:13 +02:00
parent 76ac3758d7
commit 584ef0ffd3
6 changed files with 274 additions and 18 deletions

View file

@ -341,10 +341,15 @@ PathsInChroot BaseSetting<PathsInChroot>::parse(const std::string & str) const
i.pop_back(); i.pop_back();
} }
size_t p = i.find('='); size_t p = i.find('=');
if (p == std::string::npos) std::string inside, outside;
pathsInChroot[i] = {.source = i, .optional = optional}; if (p == std::string::npos) {
else inside = i;
pathsInChroot[i.substr(0, p)] = {.source = i.substr(p + 1), .optional = optional}; outside = i;
} else {
inside = i.substr(0, p);
outside = i.substr(p + 1);
}
pathsInChroot[inside] = {.source = outside, .optional = optional};
} }
return pathsInChroot; return pathsInChroot;
} }
@ -374,6 +379,24 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
} }
} }
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Settings::ExternalBuilder, systems, program, args);
template<>
Settings::ExternalBuilders BaseSetting<Settings::ExternalBuilders>::parse(const std::string & str) const
{
try {
return nlohmann::json::parse(str).template get<Settings::ExternalBuilders>();
} catch (std::exception & e) {
throw UsageError("parsing setting '%s': %s", name, e.what());
}
}
template<>
std::string BaseSetting<Settings::ExternalBuilders>::to_string() const
{
return nlohmann::json(value).dump();
}
template<> template<>
void BaseSetting<PathsInChroot>::appendOrSet(PathsInChroot newValue, bool append) void BaseSetting<PathsInChroot>::appendOrSet(PathsInChroot newValue, bool append)
{ {

View file

@ -1372,6 +1372,105 @@ public:
Default is 0, which disables the warning. Default is 0, which disables the warning.
Set it to 1 to warn on all paths. Set it to 1 to warn on all paths.
)"}; )"};
struct ExternalBuilder
{
std::vector<std::string> systems;
Path program;
std::vector<std::string> args;
};
using ExternalBuilders = std::vector<ExternalBuilder>;
Setting<ExternalBuilders> externalBuilders{
this,
{},
"external-builders",
R"(
Helper programs that execute derivations.
The program is passed a JSON document that describes the build environment as the final argument.
The JSON document looks like this:
{
"args": [
"-e",
"/nix/store/vj1c3wf9…-source-stdenv.sh",
"/nix/store/shkw4qm9…-default-builder.sh"
],
"builder": "/nix/store/s1qkj0ph…-bash-5.2p37/bin/bash",
"env": {
"HOME": "/homeless-shelter",
"NIX_BUILD_CORES": "14",
"NIX_BUILD_TOP": "/build",
"NIX_LOG_FD": "2",
"NIX_STORE": "/nix/store",
"PATH": "/path-not-set",
"PWD": "/build",
"TEMP": "/build",
"TEMPDIR": "/build",
"TERM": "xterm-256color",
"TMP": "/build",
"TMPDIR": "/build",
"__structuredAttrs": "",
"buildInputs": "",
"builder": "/nix/store/s1qkj0ph…-bash-5.2p37/bin/bash",
"cmakeFlags": "",
"configureFlags": "",
"depsBuildBuild": "",
"depsBuildBuildPropagated": "",
"depsBuildTarget": "",
"depsBuildTargetPropagated": "",
"depsHostHost": "",
"depsHostHostPropagated": "",
"depsTargetTarget": "",
"depsTargetTargetPropagated": "",
"doCheck": "1",
"doInstallCheck": "1",
"mesonFlags": "",
"name": "hello-2.12.2",
"nativeBuildInputs": "/nix/store/l31j72f1…-version-check-hook",
"out": "/nix/store/2yx2prgx…-hello-2.12.2",
"outputs": "out",
"patches": "",
"pname": "hello",
"postInstallCheck": "stat \"${!outputBin}/bin/hello\"\n",
"propagatedBuildInputs": "",
"propagatedNativeBuildInputs": "",
"src": "/nix/store/dw402azx…-hello-2.12.2.tar.gz",
"stdenv": "/nix/store/i8bw5nqg…-stdenv-linux",
"strictDeps": "",
"system": "aarch64-linux",
"version": "2.12.2"
},
"realStoreDir": "/nix/store",
"storeDir": "/nix/store",
"system": "aarch64-linux",
"tmpDir": "/private/tmp/nix-build-hello-2.12.2.drv-0/build",
"tmpDirInSandbox": "/build",
"topTmpDir": "/private/tmp/nix-build-hello-2.12.2.drv-0"
}
)",
{}, // aliases
true, // document default
// NOTE(cole-h): even though we can make the experimental feature required here, the errors
// are not as good (it just becomes a warning if you try to use this setting without the
// experimental feature)
//
// With this commented out:
//
// error: experimental Nix feature 'external-builders' is disabled; add '--extra-experimental-features
// external-builders' to enable it
//
// With this uncommented:
//
// warning: Ignoring setting 'external-builders' because experimental feature 'external-builders' is not enabled
// error: Cannot build '/nix/store/vwsp4qd8…-opentofu-1.10.2.drv'.
// Reason: required system or feature not available
// Required system: 'aarch64-linux' with features {}
// Current system: 'aarch64-darwin' with features {apple-virt, benchmark, big-parallel, nixos-test}
// Xp::ExternalBuilders
};
}; };
// FIXME: don't use a global variable. // FIXME: don't use a global variable.

View file

@ -229,6 +229,12 @@ protected:
return acquireUserLock(1, false); return acquireUserLock(1, false);
} }
/**
* Throw an exception if we can't do this derivation because of
* missing system features.
*/
virtual void checkSystem();
/** /**
* Return the paths that should be made available in the sandbox. * Return the paths that should be made available in the sandbox.
* This includes: * This includes:
@ -666,21 +672,8 @@ static bool checkNotWorldWritable(std::filesystem::path path)
return true; return true;
} }
std::optional<Descriptor> DerivationBuilderImpl::startBuild() void DerivationBuilderImpl::checkSystem()
{ {
if (useBuildUsers()) {
if (!buildUser)
buildUser = getBuildUser();
if (!buildUser)
return std::nullopt;
}
/* Make sure that no other processes are executing under the
sandbox uids. This must be done before any chownToBuilder()
calls. */
prepareUser();
/* Right platform? */ /* Right platform? */
if (!drvOptions.canBuildLocally(store, drv)) { if (!drvOptions.canBuildLocally(store, drv)) {
auto msg = auto msg =
@ -704,6 +697,24 @@ std::optional<Descriptor> DerivationBuilderImpl::startBuild()
throw BuildError(BuildResult::Failure::InputRejected, msg); throw BuildError(BuildResult::Failure::InputRejected, msg);
} }
}
std::optional<Descriptor> DerivationBuilderImpl::startBuild()
{
if (useBuildUsers()) {
if (!buildUser)
buildUser = getBuildUser();
if (!buildUser)
return std::nullopt;
}
checkSystem();
/* Make sure that no other processes are executing under the
sandbox uids. This must be done before any chownToBuilder()
calls. */
prepareUser();
auto buildDir = store.config->getBuildDir(); auto buildDir = store.config->getBuildDir();
@ -1909,12 +1920,16 @@ StorePath DerivationBuilderImpl::makeFallbackPath(const StorePath & path)
#include "chroot-derivation-builder.cc" #include "chroot-derivation-builder.cc"
#include "linux-derivation-builder.cc" #include "linux-derivation-builder.cc"
#include "darwin-derivation-builder.cc" #include "darwin-derivation-builder.cc"
#include "external-derivation-builder.cc"
namespace nix { namespace nix {
std::unique_ptr<DerivationBuilder> makeDerivationBuilder( std::unique_ptr<DerivationBuilder> makeDerivationBuilder(
LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params) LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params)
{ {
if (auto builder = ExternalDerivationBuilder::newIfSupported(store, miscMethods, params))
return builder;
bool useSandbox = false; bool useSandbox = false;
/* Are we doing a sandboxed build? */ /* Are we doing a sandboxed build? */

View file

@ -0,0 +1,110 @@
namespace nix {
struct ExternalDerivationBuilder : DerivationBuilderImpl
{
Settings::ExternalBuilder externalBuilder;
ExternalDerivationBuilder(
LocalStore & store,
std::unique_ptr<DerivationBuilderCallbacks> miscMethods,
DerivationBuilderParams params,
Settings::ExternalBuilder externalBuilder)
: DerivationBuilderImpl(store, std::move(miscMethods), std::move(params))
, externalBuilder(std::move(externalBuilder))
{
experimentalFeatureSettings.require(Xp::ExternalBuilders);
}
static std::unique_ptr<ExternalDerivationBuilder> newIfSupported(
LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> & miscMethods, DerivationBuilderParams & params)
{
for (auto & handler : settings.externalBuilders.get()) {
for (auto & system : handler.systems)
if (params.drv.platform == system)
return std::make_unique<ExternalDerivationBuilder>(
store, std::move(miscMethods), std::move(params), handler);
}
return {};
}
Path tmpDirInSandbox() override
{
/* In a sandbox, for determinism, always use the same temporary
directory. */
return "/build";
}
void setBuildTmpDir() override
{
tmpDir = topTmpDir + "/build";
createDir(tmpDir, 0700);
}
void checkSystem() override {}
void startChild() override
{
if (drvOptions.getRequiredSystemFeatures(drv).count("recursive-nix"))
throw Error("'recursive-nix' is not supported yet by external derivation builders");
auto json = nlohmann::json::object();
json.emplace("builder", drv.builder);
{
auto l = nlohmann::json::array();
for (auto & i : drv.args)
l.push_back(rewriteStrings(i, inputRewrites));
json.emplace("args", std::move(l));
}
{
auto j = nlohmann::json::object();
for (auto & [name, value] : env)
j.emplace(name, rewriteStrings(value, inputRewrites));
json.emplace("env", std::move(j));
}
json.emplace("topTmpDir", topTmpDir);
json.emplace("tmpDir", tmpDir);
json.emplace("tmpDirInSandbox", tmpDirInSandbox());
json.emplace("storeDir", store.storeDir);
json.emplace("realStoreDir", store.config->realStoreDir.get());
json.emplace("system", drv.platform);
// TODO(cole-h): writing this to stdin is too much effort right now, if we want to revisit
// that, see this comment by Eelco about how to make it not suck:
// https://github.com/DeterminateSystems/nix-src/pull/141#discussion_r2205493257
auto jsonFile = std::filesystem::path{topTmpDir} / "build.json";
writeFile(jsonFile, json.dump());
pid = startProcess([&]() {
openSlave();
try {
commonChildInit();
Strings args = {externalBuilder.program};
if (!externalBuilder.args.empty()) {
args.insert(args.end(), externalBuilder.args.begin(), externalBuilder.args.end());
}
args.insert(args.end(), jsonFile);
if (chdir(tmpDir.c_str()) == -1)
throw SysError("changing into '%1%'", tmpDir);
chownToBuilder(topTmpDir);
setUser();
debug("executing external builder: %s", concatStringsSep(" ", args));
execv(externalBuilder.program.c_str(), stringsToCharPtrs(args).data());
throw SysError("executing '%s'", externalBuilder.program);
} catch (...) {
handleChildException(true);
_exit(1);
}
});
}
};
} // namespace nix

View file

@ -304,6 +304,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
)", )",
.trackingUrl = "https://github.com/NixOS/nix/milestone/55", .trackingUrl = "https://github.com/NixOS/nix/milestone/55",
}, },
{
.tag = Xp::ExternalBuilders,
.name = "external-builders",
.description = R"(
Enables support for external builders / sandbox providers.
)",
.trackingUrl = "",
},
{ {
.tag = Xp::BLAKE3Hashes, .tag = Xp::BLAKE3Hashes,
.name = "blake3-hashes", .name = "blake3-hashes",

View file

@ -37,6 +37,7 @@ enum struct ExperimentalFeature {
MountedSSHStore, MountedSSHStore,
VerifiedFetches, VerifiedFetches,
PipeOperators, PipeOperators,
ExternalBuilders,
BLAKE3Hashes, BLAKE3Hashes,
}; };