diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml index 89b8aac78..329d2e485 100644 --- a/doc/manual/command-ref/conf-file.xml +++ b/doc/manual/command-ref/conf-file.xml @@ -562,6 +562,39 @@ flag, e.g. --option gc-keep-outputs false. + pre-build-hook + + + + If set, the path to a program that can set extra + derivation-specific settings for this system. This is used for settings + that can't be captured by the derivation model itself and are too + variable between different versions of the same system to be hard-coded + into nix. + + The hook listens on stdin for a derivation path. + It can then send a series of commands to modify various settings, followed + by an empty line to indicate completion. The currently recognized commands + are: + + + extra-chroot-dirs + + + + Pass a list of files and directories to be included in the + chroot for this build. One entry per line, terminated by an empty + line. + + + + + + + + + + diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 1fc5d4181..de33e1154 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -89,6 +89,7 @@ static string pathNullDevice = "/dev/null"; /* Forward definition. */ class Worker; struct HookInstance; +struct PreBuildHookInstance; /* A pointer to a goal. */ @@ -269,6 +270,8 @@ public: std::shared_ptr hook; + std::shared_ptr preBuildHook; + Worker(LocalStore & store); ~Worker(); @@ -649,6 +652,72 @@ HookInstance::~HookInstance() ////////////////////////////////////////////////////////////////////// +struct PreBuildHookInstance +{ + /* Pipes for talking to the build hook. */ + Pipe toHook; + + /* Pipe for the hook's standard output/error. */ + Pipe fromHook; + + /* The process ID of the hook. */ + Pid pid; + + PreBuildHookInstance(); + + ~PreBuildHookInstance(); +}; + + +PreBuildHookInstance::PreBuildHookInstance() +{ + debug("starting pre-build hook"); + + /* Create a pipe to get the output of the child. */ + fromHook.create(); + + /* Create the communication pipes. */ + toHook.create(); + + /* Fork the hook. */ + pid = startProcess([&]() { + + commonChildInit(fromHook); + + if (chdir("/") == -1) throw SysError("changing into /"); + + /* Dup the communication pipes. */ + if (dup2(toHook.readSide, STDIN_FILENO) == -1) + throw SysError("dupping to-hook read side"); + + setenv("_NIX_OPTIONS", settings.pack().c_str(), 1); + + execl(settings.preBuildHook.c_str(), settings.preBuildHook.c_str(), + NULL); + + throw SysError(format("executing ‘%1%’") % settings.preBuildHook); + }); + + pid.setSeparatePG(true); + fromHook.writeSide.close(); + toHook.readSide.close(); +} + + +PreBuildHookInstance::~PreBuildHookInstance() +{ + try { + toHook.writeSide.close(); + pid.kill(true); + } catch (...) { + ignoreException(); + } +} + + +////////////////////////////////////////////////////////////////////// + + typedef map HashRewrites; @@ -813,6 +882,13 @@ private: /* Is the build hook willing to perform the build? */ HookReply tryBuildHook(); + /* Run the pre-build hook, which can set system-specific + per-derivation settings too complex/volatile to hard-code + in nix itself */ + void runPreBuildHook(); + + PathSet extraChrootDirs; + /* Start building a derivation. */ void startBuilder(); @@ -1178,6 +1254,9 @@ void DerivationGoal::inputsRealised() foreach (DerivationOutputs::iterator, i, drv.outputs) if (i->second.hash == "") fixedOutput = false; + /* Ask the pre-build hook for any required settings */ + runPreBuildHook(); + /* 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 build hook. */ @@ -1794,6 +1873,7 @@ void DerivationGoal::startBuilder() PathSet dirs = tokenizeString(settings.get("build-chroot-dirs", defaultChrootDirs)); PathSet dirs2 = tokenizeString(settings.get("build-extra-chroot-dirs", string(""))); dirs.insert(dirs2.begin(), dirs2.end()); + dirs.insert(extraChrootDirs.begin(), extraChrootDirs.end()); for (auto & i : dirs) { size_t p = i.find('='); @@ -2794,6 +2874,33 @@ Path DerivationGoal::addHashRewrite(const Path & path) } +void DerivationGoal::runPreBuildHook() +{ + if (settings.preBuildHook == "") + return; + + if (!worker.preBuildHook) + worker.preBuildHook = std::make_shared(); + + writeLine(worker.preBuildHook->toHook.writeSide, drvPath); + while (true) { + string s = readLine(worker.preBuildHook->fromHook.readSide); + if (s == "extra-chroot-dirs") { + while (true) { + string s = readLine(worker.preBuildHook->fromHook.readSide); + if (s == "") + break; + extraChrootDirs.emplace(std::move(s)); + } + } else if (s == "") { + break; + } else { + throw Error(format("unknown pre-build hook command ‘%1%’") % s); + } + } +} + + ////////////////////////////////////////////////////////////////////// diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index f900fb290..143260674 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -181,6 +181,7 @@ void Settings::update() _get(logServers, "log-servers"); _get(enableImportNative, "allow-unsafe-native-code-during-evaluation"); _get(useCaseHack, "use-case-hack"); + _get(preBuildHook, "pre-build-hook"); string subs = getEnv("NIX_SUBSTITUTERS", "default"); if (subs == "default") { diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 0a1072e36..7add7cf7c 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -204,6 +204,10 @@ struct Settings { /* Whether the importNative primop should be enabled */ bool enableImportNative; + /* The hook to run just before a build to set derivation-specific + build settings */ + Path preBuildHook; + private: SettingsMap settings, overrides;