From 1e4a4a2e9fc382f47f58b448f3ee034cdd28218a Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sat, 4 Apr 2015 21:53:17 -0400 Subject: [PATCH] Add the pre-build hook. This hook can be used to set system specific per-derivation build settings that don't fit into the derivation model and are too complex or volatile to be hard-coded into nix. Currently, the pre-build hook can only add chroot dirs/files. The specific use case for this is systems where the operating system ABI is more complex than just the kernel-supported system calls. For example, on OS X there is a set of system-provided frameworks that can reliably be accessed by any program linked to them, no matter the version the program is running on. Unfortunately, those frameworks do not necessarily live in the same locations on each version of OS X, nor do their dependencies, and thus nix needs to know the specific version of OS X currently running in order to make those frameworks available. The pre-build hook is a perfect mechanism for doing just that. --- doc/manual/command-ref/conf-file.xml | 33 +++++++++ src/libstore/build.cc | 107 +++++++++++++++++++++++++++ src/libstore/globals.cc | 1 + src/libstore/globals.hh | 4 + 4 files changed, 145 insertions(+) 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;