about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2015-07-17T15·57+0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2015-07-17T15·57+0200
commit1511aa9f488ba0762c2da0bf8ab61b5fde47305d (patch)
treefc394f398be8d2aa4a040794618713a22179e9e0
parentf39979c6d3e49b09aa82fea5e167d4253f63d71f (diff)
Allow remote builds without sending the derivation closure
Previously, to build a derivation remotely, we had to copy the entire
closure of the .drv file to the remote machine, even though we only
need the top-level derivation. This is very wasteful: the closure can
contain thousands of store paths, and in some Hydra use cases, include
source paths that are very large (e.g. Git/Mercurial checkouts).

So now there is a new operation, StoreAPI::buildDerivation(), that
performs a build from an in-memory representation of a derivation
(BasicDerivation) rather than from a on-disk .drv file. The only files
that need to be in the Nix store are the sources of the derivation
(drv.inputSrcs), and the needed output paths of the dependencies (as
described by drv.inputDrvs). "nix-store --serve" exposes this
interface.

Note that this is a privileged operation, because you can construct a
derivation that builds any store path whatsoever. Fixing this will
require changing the hashing scheme (i.e., the output paths should be
computed from the other fields in BasicDerivation, allowing them to be
verified without access to other derivations). However, this would be
quite nice because it would allow .drv-free building (e.g. "nix-env
-i" wouldn't have to write any .drv files to disk).

Fixes #173.
-rw-r--r--src/libstore/build.cc343
-rw-r--r--src/libstore/derivations.cc43
-rw-r--r--src/libstore/derivations.hh18
-rw-r--r--src/libstore/local-store.hh3
-rw-r--r--src/libstore/misc.hh4
-rw-r--r--src/libstore/remote-store.cc7
-rw-r--r--src/libstore/remote-store.hh3
-rw-r--r--src/libstore/store-api.hh22
-rw-r--r--src/libutil/serialise.cc32
-rw-r--r--src/libutil/serialise.hh8
-rw-r--r--src/nix-store/nix-store.cc40
-rw-r--r--src/nix-store/serve-protocol.hh3
12 files changed, 372 insertions, 154 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 8c4412f11a8b..5e2e7d89a3ee 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -275,6 +275,7 @@ public:
 
     /* Make a goal (with caching). */
     GoalPtr makeDerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+    GoalPtr makeBasicDerivationGoal(const Path & drvPath, const BasicDerivation & drv, BuildMode buildMode = bmNormal);
     GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false);
 
     /* Remove a dead goal. */
@@ -690,6 +691,9 @@ class SubstitutionGoal;
 class DerivationGoal : public Goal
 {
 private:
+    /* Whether to use an on-disk .drv file. */
+    bool useDerivation;
+
     /* The path of the derivation. */
     Path drvPath;
 
@@ -698,14 +702,14 @@ private:
     StringSet wantedOutputs;
 
     /* Whether additional wanted outputs have been added. */
-    bool needRestart;
+    bool needRestart = false;
 
     /* Whether to retry substituting the outputs after building the
        inputs. */
-    bool retrySubstitution;
+    bool retrySubstitution = false;
 
     /* The derivation stored at drvPath. */
-    Derivation drv;
+    std::unique_ptr<BasicDerivation> drv;
 
     /* The remainder is state held during the build. */
 
@@ -736,8 +740,8 @@ private:
     Path tmpDir;
 
     /* File descriptor for the log file. */
-    FILE * fLogFile;
-    BZFILE * bzLogFile;
+    FILE * fLogFile = 0;
+    BZFILE * bzLogFile = 0;
     AutoCloseFD fdLogFile;
 
     /* Number of bytes received from the builder's stdout/stderr. */
@@ -750,7 +754,7 @@ private:
     std::shared_ptr<HookInstance> hook;
 
     /* Whether we're currently doing a chroot build. */
-    bool useChroot;
+    bool useChroot = false;
 
     Path chrootRootDir;
 
@@ -790,7 +794,10 @@ private:
     InodesSeen inodesSeen;
 
 public:
-    DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal);
+    DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
+        Worker & worker, BuildMode buildMode = bmNormal);
+    DerivationGoal(const Path & drvPath, const BasicDerivation & drv,
+        Worker & worker, BuildMode buildMode = bmNormal);
     ~DerivationGoal();
 
     void cancel(bool timeout);
@@ -816,7 +823,8 @@ public:
 
 private:
     /* The states. */
-    void init();
+    void getDerivation();
+    void loadDerivation();
     void haveDerivation();
     void outputsSubstituted();
     void closureRepaired();
@@ -867,23 +875,34 @@ private:
 };
 
 
-DerivationGoal::DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
+DerivationGoal::DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
+    Worker & worker, BuildMode buildMode)
     : Goal(worker)
+    , useDerivation(true)
+    , drvPath(drvPath)
     , wantedOutputs(wantedOutputs)
-    , needRestart(false)
-    , retrySubstitution(false)
-    , fLogFile(0)
-    , bzLogFile(0)
-    , useChroot(false)
     , buildMode(buildMode)
 {
-    this->drvPath = drvPath;
-    state = &DerivationGoal::init;
+    state = &DerivationGoal::getDerivation;
     name = (format("building of ‘%1%’") % drvPath).str();
     trace("created");
 }
 
 
+DerivationGoal::DerivationGoal(const Path & drvPath, const BasicDerivation & drv,
+    Worker & worker, BuildMode buildMode)
+    : Goal(worker)
+    , useDerivation(false)
+    , drvPath(drvPath)
+    , buildMode(buildMode)
+{
+    this->drv = std::unique_ptr<BasicDerivation>(new BasicDerivation(drv));
+    state = &DerivationGoal::haveDerivation;
+    name = (format("building of %1%") % showPaths(outputPaths(drv))).str();
+    trace("created");
+}
+
+
 DerivationGoal::~DerivationGoal()
 {
     /* Careful: we should never ever throw an exception from a
@@ -951,28 +970,25 @@ void DerivationGoal::addWantedOutputs(const StringSet & outputs)
 }
 
 
-void DerivationGoal::init()
+void DerivationGoal::getDerivation()
 {
     trace("init");
 
-    if (settings.readOnlyMode)
-        throw Error(format("cannot build derivation ‘%1%’ - no write access to the Nix store") % drvPath);
-
     /* The first thing to do is to make sure that the derivation
        exists.  If it doesn't, it may be created through a
        substitute. */
     if (buildMode == bmNormal && worker.store.isValidPath(drvPath)) {
-        haveDerivation();
+        loadDerivation();
         return;
     }
 
     addWaitee(worker.makeSubstitutionGoal(drvPath));
 
-    state = &DerivationGoal::haveDerivation;
+    state = &DerivationGoal::loadDerivation;
 }
 
 
-void DerivationGoal::haveDerivation()
+void DerivationGoal::loadDerivation()
 {
     trace("loading derivation");
 
@@ -990,10 +1006,18 @@ void DerivationGoal::haveDerivation()
     assert(worker.store.isValidPath(drvPath));
 
     /* Get the derivation. */
-    drv = derivationFromPath(worker.store, drvPath);
+    drv = std::unique_ptr<BasicDerivation>(new Derivation(derivationFromPath(worker.store, drvPath)));
+
+    haveDerivation();
+}
 
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        worker.store.addTempRoot(i->second.path);
+
+void DerivationGoal::haveDerivation()
+{
+    trace("have derivation");
+
+    for (auto & i : drv->outputs)
+        worker.store.addTempRoot(i.second.path);
 
     /* Check what outputs paths are not already valid. */
     PathSet invalidOutputs = checkPathValidity(false, buildMode == bmRepair);
@@ -1006,15 +1030,15 @@ void DerivationGoal::haveDerivation()
 
     /* Check whether any output previously failed to build.  If so,
        don't bother. */
-    foreach (PathSet::iterator, i, invalidOutputs)
-        if (pathFailed(*i)) return;
+    for (auto & i : invalidOutputs)
+        if (pathFailed(i)) return;
 
     /* We are first going to try to create the invalid output paths
        through substitutes.  If that doesn't work, we'll build
        them. */
-    if (settings.useSubstitutes && substitutesAllowed(drv))
-        foreach (PathSet::iterator, i, invalidOutputs)
-            addWaitee(worker.makeSubstitutionGoal(*i, buildMode == bmRepair));
+    if (settings.useSubstitutes && substitutesAllowed(*drv))
+        for (auto & i : invalidOutputs)
+            addWaitee(worker.makeSubstitutionGoal(i, buildMode == bmRepair));
 
     if (waitees.empty()) /* to prevent hang (no wake-up event) */
         outputsSubstituted();
@@ -1063,11 +1087,17 @@ void DerivationGoal::outputsSubstituted()
     wantedOutputs = PathSet();
 
     /* The inputs must be built before we can build this goal. */
-    foreach (DerivationInputs::iterator, i, drv.inputDrvs)
-        addWaitee(worker.makeDerivationGoal(i->first, i->second, buildMode == bmRepair ? bmRepair : bmNormal));
+    if (useDerivation)
+        for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs)
+            addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
 
-    foreach (PathSet::iterator, i, drv.inputSrcs)
-        addWaitee(worker.makeSubstitutionGoal(*i));
+    for (auto & i : drv->inputSrcs) {
+        if (worker.store.isValidPath(i)) continue;
+        if (!settings.useSubstitutes)
+            throw Error(format("dependency of ‘%1%’ of ‘%2%’ does not exist, and substitution is disabled")
+                % i % drvPath);
+        addWaitee(worker.makeSubstitutionGoal(i));
+    }
 
     if (waitees.empty()) /* to prevent hang (no wake-up event) */
         inputsRealised();
@@ -1085,34 +1115,34 @@ void DerivationGoal::repairClosure()
 
     /* Get the output closure. */
     PathSet outputClosure;
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        computeFSClosure(worker.store, i->second.path, outputClosure);
+    for (auto & i : drv->outputs)
+        computeFSClosure(worker.store, i.second.path, outputClosure);
 
     /* Filter out our own outputs (which we have already checked). */
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        outputClosure.erase(i->second.path);
+    for (auto & i : drv->outputs)
+        outputClosure.erase(i.second.path);
 
     /* Get all dependencies of this derivation so that we know which
        derivation is responsible for which path in the output
        closure. */
     PathSet inputClosure;
-    computeFSClosure(worker.store, drvPath, inputClosure);
+    if (useDerivation) computeFSClosure(worker.store, drvPath, inputClosure);
     std::map<Path, Path> outputsToDrv;
-    foreach (PathSet::iterator, i, inputClosure)
-        if (isDerivation(*i)) {
-            Derivation drv = derivationFromPath(worker.store, *i);
-            foreach (DerivationOutputs::iterator, j, drv.outputs)
-                outputsToDrv[j->second.path] = *i;
+    for (auto & i : inputClosure)
+        if (isDerivation(i)) {
+            Derivation drv = derivationFromPath(worker.store, i);
+            for (auto & j : drv.outputs)
+                outputsToDrv[j.second.path] = i;
         }
 
     /* Check each path (slow!). */
     PathSet broken;
-    foreach (PathSet::iterator, i, outputClosure) {
-        if (worker.store.pathContentsGood(*i)) continue;
-        printMsg(lvlError, format("found corrupted or missing path ‘%1%’ in the output closure of ‘%2%’") % *i % drvPath);
-        Path drvPath2 = outputsToDrv[*i];
+    for (auto & i : outputClosure) {
+        if (worker.store.pathContentsGood(i)) continue;
+        printMsg(lvlError, format("found corrupted or missing path ‘%1%’ in the output closure of ‘%2%’") % i % drvPath);
+        Path drvPath2 = outputsToDrv[i];
         if (drvPath2 == "")
-            addWaitee(worker.makeSubstitutionGoal(*i, true));
+            addWaitee(worker.makeSubstitutionGoal(i, true));
         else
             addWaitee(worker.makeDerivationGoal(drvPath2, PathSet(), bmRepair));
     }
@@ -1140,6 +1170,8 @@ void DerivationGoal::inputsRealised()
     trace("all inputs realised");
 
     if (nrFailed != 0) {
+        if (!useDerivation)
+            throw Error(format("some dependencies of ‘%1%’ are missing") % drvPath);
         printMsg(lvlError,
             format("cannot build derivation ‘%1%’: %2% dependencies couldn't be built")
             % drvPath % nrFailed);
@@ -1156,32 +1188,33 @@ void DerivationGoal::inputsRealised()
        running the build hook. */
 
     /* The outputs are referenceable paths. */
-    foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        debug(format("building path ‘%1%’") % i->second.path);
-        allPaths.insert(i->second.path);
+    for (auto & i : drv->outputs) {
+        debug(format("building path ‘%1%’") % i.second.path);
+        allPaths.insert(i.second.path);
     }
 
     /* Determine the full set of input paths. */
 
     /* First, the input derivations. */
-    foreach (DerivationInputs::iterator, i, drv.inputDrvs) {
-        /* Add the relevant output closures of the input derivation
-           `*i' as input paths.  Only add the closures of output paths
-           that are specified as inputs. */
-        assert(worker.store.isValidPath(i->first));
-        Derivation inDrv = derivationFromPath(worker.store, i->first);
-        foreach (StringSet::iterator, j, i->second)
-            if (inDrv.outputs.find(*j) != inDrv.outputs.end())
-                computeFSClosure(worker.store, inDrv.outputs[*j].path, inputPaths);
-            else
-                throw Error(
-                    format("derivation ‘%1%’ requires non-existent output ‘%2%’ from input derivation ‘%3%’")
-                    % drvPath % *j % i->first);
-    }
+    if (useDerivation)
+        for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
+            /* Add the relevant output closures of the input derivation
+               `i' as input paths.  Only add the closures of output paths
+               that are specified as inputs. */
+            assert(worker.store.isValidPath(i.first));
+            Derivation inDrv = derivationFromPath(worker.store, i.first);
+            for (auto & j : i.second)
+                if (inDrv.outputs.find(j) != inDrv.outputs.end())
+                    computeFSClosure(worker.store, inDrv.outputs[j].path, inputPaths);
+                else
+                    throw Error(
+                        format("derivation ‘%1%’ requires non-existent output ‘%2%’ from input derivation ‘%3%’")
+                        % drvPath % j % i.first);
+        }
 
     /* Second, the input sources. */
-    foreach (PathSet::iterator, i, drv.inputSrcs)
-        computeFSClosure(worker.store, *i, inputPaths);
+    for (auto & i : drv->inputSrcs)
+        computeFSClosure(worker.store, i, inputPaths);
 
     debug(format("added input paths %1%") % showPaths(inputPaths));
 
@@ -1189,8 +1222,8 @@ void DerivationGoal::inputsRealised()
 
     /* Is this a fixed-output derivation? */
     fixedOutput = true;
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        if (i->second.hash == "") fixedOutput = false;
+    for (auto & i : drv->outputs)
+        if (i.second.hash == "") fixedOutput = false;
 
     /* 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
@@ -1217,13 +1250,13 @@ static string get(const StringPairs & map, const string & key, const string & de
 }
 
 
-bool willBuildLocally(const Derivation & drv)
+bool willBuildLocally(const BasicDerivation & drv)
 {
     return get(drv.env, "preferLocalBuild") == "1" && canBuildLocally(drv.platform);
 }
 
 
-bool substitutesAllowed(const Derivation & drv)
+bool substitutesAllowed(const BasicDerivation & drv)
 {
     return get(drv.env, "allowSubstitutes", "1") == "1";
 }
@@ -1238,10 +1271,10 @@ void DerivationGoal::tryToBuild()
        (It can't happen between here and the lockPaths() call below
        because we're not allowing multi-threading.)  If so, put this
        goal to sleep until another goal finishes, then try again. */
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        if (pathIsLockedByMe(i->second.path)) {
+    for (auto & i : drv->outputs)
+        if (pathIsLockedByMe(i.second.path)) {
             debug(format("putting derivation ‘%1%’ to sleep because ‘%2%’ is locked by another goal")
-                % drvPath % i->second.path);
+                % drvPath % i.second.path);
             worker.waitForAnyGoal(shared_from_this());
             return;
         }
@@ -1251,7 +1284,7 @@ void DerivationGoal::tryToBuild()
        can't acquire the lock, then continue; hopefully some other
        goal can start a build, and if not, the main loop will sleep a
        few seconds and then retry this goal. */
-    if (!outputLocks.lockPaths(outputPaths(drv), "", false)) {
+    if (!outputLocks.lockPaths(outputPaths(*drv), "", false)) {
         worker.waitForAWhile(shared_from_this());
         return;
     }
@@ -1264,22 +1297,22 @@ void DerivationGoal::tryToBuild()
        now hold the locks on the output paths, no other process can
        build this derivation, so no further checks are necessary. */
     validPaths = checkPathValidity(true, buildMode == bmRepair);
-    assert(buildMode != bmCheck || validPaths.size() == drv.outputs.size());
-    if (buildMode != bmCheck && validPaths.size() == drv.outputs.size()) {
+    assert(buildMode != bmCheck || validPaths.size() == drv->outputs.size());
+    if (buildMode != bmCheck && validPaths.size() == drv->outputs.size()) {
         debug(format("skipping build of derivation ‘%1%’, someone beat us to it") % drvPath);
         outputLocks.setDeletion(true);
         amDone(ecSuccess);
         return;
     }
 
-    missingPaths = outputPaths(drv);
+    missingPaths = outputPaths(*drv);
     if (buildMode != bmCheck)
         foreach (PathSet::iterator, i, validPaths) missingPaths.erase(*i);
 
     /* If any of the outputs already exist but are not valid, delete
        them. */
-    foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        Path path = i->second.path;
+    for (auto & i : drv->outputs) {
+        Path path = i.second.path;
         if (worker.store.isValidPath(path)) continue;
         if (!pathExists(path)) continue;
         debug(format("removing invalid path ‘%1%’") % path);
@@ -1289,13 +1322,13 @@ void DerivationGoal::tryToBuild()
     /* Check again whether any output previously failed to build,
        because some other process may have tried and failed before we
        acquired the lock. */
-    foreach (DerivationOutputs::iterator, i, drv.outputs)
-        if (pathFailed(i->second.path)) return;
+    for (auto & i : drv->outputs)
+        if (pathFailed(i.second.path)) return;
 
     /* Don't do a remote build if the derivation has the attribute
        `preferLocalBuild' set.  Also, check and repair modes are only
        supported for local builds. */
-    bool buildLocally = buildMode != bmNormal || willBuildLocally(drv);
+    bool buildLocally = buildMode != bmNormal || willBuildLocally(*drv);
 
     /* Is the build hook willing to accept this job? */
     if (!buildLocally) {
@@ -1504,8 +1537,8 @@ void DerivationGoal::buildDone()
                Hook errors (like communication problems with the
                remote machine) shouldn't be cached either. */
             if (settings.cacheFailure && !fixedOutput && !diskFull)
-                foreach (DerivationOutputs::iterator, i, drv.outputs)
-                    worker.store.registerFailedPath(i->second.path);
+                for (auto & i : drv->outputs)
+                    worker.store.registerFailedPath(i.second.path);
         }
 
         amDone(ecFailed);
@@ -1524,21 +1557,21 @@ void DerivationGoal::buildDone()
 
 HookReply DerivationGoal::tryBuildHook()
 {
-    if (!settings.useBuildHook || getEnv("NIX_BUILD_HOOK") == "") return rpDecline;
+    if (!settings.useBuildHook || getEnv("NIX_BUILD_HOOK") == "" || !useDerivation) return rpDecline;
 
     if (!worker.hook)
-        worker.hook = std::shared_ptr<HookInstance>(new HookInstance);
+        worker.hook = std::make_shared<HookInstance>();
 
     /* 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.) */
-    Strings features = tokenizeString<Strings>(get(drv.env, "requiredSystemFeatures"));
+    Strings features = tokenizeString<Strings>(get(drv->env, "requiredSystemFeatures"));
     foreach (Strings::iterator, i, features) checkStoreName(*i); /* !!! abuse */
 
     /* Send the request to the hook. */
     writeLine(worker.hook->toHook.writeSide, (format("%1% %2% %3% %4%")
         % (worker.getNrLocalBuilds() < settings.maxBuildJobs ? "1" : "0")
-        % drv.platform % drvPath % concatStringsSep(",", features)).str());
+        % drv->platform % drvPath % concatStringsSep(",", features)).str());
 
     /* Read the first line of input, which should be a word indicating
        whether the hook wishes to perform the build. */
@@ -1596,7 +1629,7 @@ HookReply DerivationGoal::tryBuildHook()
 
     if (settings.printBuildTrace)
         printMsg(lvlError, format("@ build-started %1% - %2% %3%")
-            % drvPath % drv.platform % logFile);
+            % drvPath % drv->platform % logFile);
 
     return rpAccept;
 }
@@ -1624,12 +1657,12 @@ void DerivationGoal::startBuilder()
             "building path(s) %1%") % showPaths(missingPaths));
 
     /* Right platform? */
-    if (!canBuildLocally(drv.platform)) {
+    if (!canBuildLocally(drv->platform)) {
         if (settings.printBuildTrace)
-            printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv.platform);
+            printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv->platform);
         throw Error(
             format("a ‘%1%’ is required to build ‘%3%’, but I am a ‘%2%’")
-            % drv.platform % settings.thisSystem % drvPath);
+            % drv->platform % settings.thisSystem % drvPath);
     }
 
     /* Construct the environment passed to the builder. */
@@ -1666,9 +1699,9 @@ void DerivationGoal::startBuilder()
        attribute. Those are passed as file names pointing to
        temporary files containing the contents. */
     PathSet filesToChown;
-    StringSet passAsFile = tokenizeString<StringSet>(get(drv.env, "passAsFile"));
+    StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile"));
     int fileNr = 0;
-    for (auto & i : drv.env) {
+    for (auto & i : drv->env) {
         if (passAsFile.find(i.first) == passAsFile.end()) {
             env[i.first] = i.second;
         } else {
@@ -1708,7 +1741,7 @@ void DerivationGoal::startBuilder()
        fixed-output derivations is by definition pure (since we
        already know the cryptographic hash of the output). */
     if (fixedOutput) {
-        Strings varNames = tokenizeString<Strings>(get(drv.env, "impureEnvVars"));
+        Strings varNames = tokenizeString<Strings>(get(drv->env, "impureEnvVars"));
         foreach (Strings::iterator, i, varNames) env[*i] = getEnv(*i);
     }
 
@@ -1719,7 +1752,7 @@ void DerivationGoal::startBuilder()
        temporary build directory.  The text files have the format used
        by `nix-store --register-validity'.  However, the deriver
        fields are left empty. */
-    string s = get(drv.env, "exportReferencesGraph");
+    string s = get(drv->env, "exportReferencesGraph");
     Strings ss = tokenizeString<Strings>(s);
     if (ss.size() % 2 != 0)
         throw BuildError(format("odd number of tokens in ‘exportReferencesGraph’: ‘%1%’") % s);
@@ -1748,8 +1781,8 @@ void DerivationGoal::startBuilder()
         foreach (PathSet::iterator, j, paths2) {
             if (isDerivation(*j)) {
                 Derivation drv = derivationFromPath(worker.store, *j);
-                foreach (DerivationOutputs::iterator, k, drv.outputs)
-                    computeFSClosure(worker.store, k->second.path, paths);
+                for (auto & k : drv.outputs)
+                    computeFSClosure(worker.store, k.second.path, paths);
             }
         }
 
@@ -1789,14 +1822,14 @@ void DerivationGoal::startBuilder()
         if (x != "true" && x != "false" && x != "relaxed")
             throw Error("option ‘build-use-chroot’ must be set to one of ‘true’, ‘false’ or ‘relaxed’");
         if (x == "true") {
-            if (get(drv.env, "__noChroot") == "1")
+            if (get(drv->env, "__noChroot") == "1")
                 throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, but that's not allowed when ‘build-use-chroot’ is ‘true’") % drvPath);
             useChroot = true;
         }
         else if (x == "false")
             useChroot = false;
         else if (x == "relaxed")
-            useChroot = !fixedOutput && get(drv.env, "__noChroot") != "1";
+            useChroot = !fixedOutput && get(drv->env, "__noChroot") != "1";
     }
 
     if (useChroot) {
@@ -1834,7 +1867,7 @@ void DerivationGoal::startBuilder()
         PathSet allowedPaths = tokenizeString<StringSet>(allowed);
 
         /* This works like the above, except on a per-derivation level */
-        Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps"));
+        Strings impurePaths = tokenizeString<Strings>(get(drv->env, "__impureHostDeps"));
 
         for (auto & i : impurePaths) {
             bool found = false;
@@ -1865,7 +1898,7 @@ void DerivationGoal::startBuilder()
         if (pathExists(chrootRootDir)) deletePath(chrootRootDir);
 
         /* Clean up the chroot directory automatically. */
-        autoDelChroot = std::shared_ptr<AutoDelete>(new AutoDelete(chrootRootDir));
+        autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
 
         printMsg(lvlChatty, format("setting up chroot environment in ‘%1%’") % chrootRootDir);
 
@@ -1948,8 +1981,8 @@ void DerivationGoal::startBuilder()
            (typically the dependencies of /bin/sh).  Throw them
            out. */
         if (buildMode != bmNormal)
-            foreach (DerivationOutputs::iterator, i, drv.outputs)
-                dirsInChroot.erase(i->second.path);
+            for (auto & i : drv->outputs)
+                dirsInChroot.erase(i.second.path);
 
 #elif SANDBOX_ENABLED
         /* We don't really have any parent prep work to do (yet?)
@@ -2025,7 +2058,7 @@ void DerivationGoal::startBuilder()
     }
 
     /* Run the builder. */
-    printMsg(lvlChatty, format("executing builder ‘%1%’") % drv.builder);
+    printMsg(lvlChatty, format("executing builder ‘%1%’") % drv->builder);
 
     /* Create the log file. */
     Path logFile = openLogFile();
@@ -2111,7 +2144,7 @@ void DerivationGoal::startBuilder()
 
     if (settings.printBuildTrace) {
         printMsg(lvlError, format("@ build-started %1% - %2% %3%")
-            % drvPath % drv.platform % logFile);
+            % drvPath % drv->platform % logFile);
     }
 }
 
@@ -2285,7 +2318,7 @@ void DerivationGoal::runChild()
            i686-linux build on an x86_64-linux machine. */
         struct utsname utsbuf;
         uname(&utsbuf);
-        if (drv.platform == "i686-linux" &&
+        if (drv->platform == "i686-linux" &&
             (settings.thisSystem == "x86_64-linux" ||
              (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) {
             if (personality(PER_LINUX32) == -1)
@@ -2294,7 +2327,7 @@ void DerivationGoal::runChild()
 
         /* Impersonate a Linux 2.6 machine to get some determinism in
            builds that depend on the kernel version. */
-        if ((drv.platform == "i686-linux" || drv.platform == "x86_64-linux") && settings.impersonateLinux26) {
+        if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
             int cur = personality(0xffffffff);
             if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
         }
@@ -2449,15 +2482,15 @@ void DerivationGoal::runChild()
             args.push_back("sandbox-exec");
             args.push_back("-p");
             args.push_back(sandboxProfile);
-            args.push_back(drv.builder);
+            args.push_back(drv->builder);
         } else {
-            builder = drv.builder.c_str();
-            string builderBasename = baseNameOf(drv.builder);
+            builder = drv->builder.c_str();
+            string builderBasename = baseNameOf(drv->builder);
             args.push_back(builderBasename);
         }
 
-        foreach (Strings::iterator, i, drv.args)
-            args.push_back(rewriteHashes(*i, rewritesToTmp));
+        for (auto & i : drv->args)
+            args.push_back(rewriteHashes(i, rewritesToTmp));
 
         restoreSIGPIPE();
 
@@ -2473,7 +2506,7 @@ void DerivationGoal::runChild()
         /* Execute the program.  This should not return. */
         execve(builder, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
 
-        throw SysError(format("executing ‘%1%’") % drv.builder);
+        throw SysError(format("executing ‘%1%’") % drv->builder);
 
     } catch (std::exception & e) {
         writeFull(STDERR_FILENO, "while setting up the build environment: " + string(e.what()) + "\n");
@@ -2485,7 +2518,7 @@ void DerivationGoal::runChild()
 /* Parse a list of reference specifiers.  Each element must either be
    a store path, or the symbolic name of the output of the derivation
    (such as `out'). */
-PathSet parseReferenceSpecifiers(const Derivation & drv, string attr)
+PathSet parseReferenceSpecifiers(const BasicDerivation & drv, string attr)
 {
     PathSet result;
     Paths paths = tokenizeString<Paths>(attr);
@@ -2509,8 +2542,8 @@ void DerivationGoal::registerOutputs()
        to do anything here. */
     if (hook) {
         bool allValid = true;
-        foreach (DerivationOutputs::iterator, i, drv.outputs)
-            if (!worker.store.isValidPath(i->second.path)) allValid = false;
+        for (auto & i : drv->outputs)
+            if (!worker.store.isValidPath(i.second.path)) allValid = false;
         if (allValid) return;
     }
 
@@ -2519,8 +2552,8 @@ void DerivationGoal::registerOutputs()
     /* Check whether the output paths were created, and grep each
        output path to determine what other paths it references.  Also make all
        output paths read-only. */
-    foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        Path path = i->second.path;
+    for (auto & i : drv->outputs) {
+        Path path = i.second.path;
         if (missingPaths.find(path) == missingPaths.end()) continue;
 
         Path actualPath = path;
@@ -2591,10 +2624,10 @@ void DerivationGoal::registerOutputs()
         /* Check that fixed-output derivations produced the right
            outputs (i.e., the content hash should match the specified
            hash). */
-        if (i->second.hash != "") {
+        if (i.second.hash != "") {
 
             bool recursive; HashType ht; Hash h;
-            i->second.parseHashInfo(recursive, ht, h);
+            i.second.parseHashInfo(recursive, ht, h);
 
             if (!recursive) {
                 /* The output path should be a regular file without
@@ -2609,7 +2642,7 @@ void DerivationGoal::registerOutputs()
             if (h != h2)
                 throw BuildError(
                     format("output path ‘%1%’ should have %2% hash ‘%3%’, instead has ‘%4%’")
-                    % path % i->second.hashAlgo % printHash16or32(h) % printHash16or32(h2));
+                    % path % i.second.hashAlgo % printHash16or32(h) % printHash16or32(h2));
         }
 
         /* Get rid of all weird permissions.  This also checks that
@@ -2643,9 +2676,9 @@ void DerivationGoal::registerOutputs()
 
         /* Enforce `allowedReferences' and friends. */
         auto checkRefs = [&](const string & attrName, bool allowed, bool recursive) {
-            if (drv.env.find(attrName) == drv.env.end()) return;
+            if (drv->env.find(attrName) == drv->env.end()) return;
 
-            PathSet spec = parseReferenceSpecifiers(drv, get(drv.env, attrName));
+            PathSet spec = parseReferenceSpecifiers(*drv, get(drv->env, attrName));
 
             PathSet used;
             if (recursive) {
@@ -2806,12 +2839,12 @@ void DerivationGoal::handleEOF(int fd)
 PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
 {
     PathSet result;
-    foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        if (!wantOutput(i->first, wantedOutputs)) continue;
+    for (auto & i : drv->outputs) {
+        if (!wantOutput(i.first, wantedOutputs)) continue;
         bool good =
-            worker.store.isValidPath(i->second.path) &&
-            (!checkHash || worker.store.pathContentsGood(i->second.path));
-        if (good == returnValid) result.insert(i->second.path);
+            worker.store.isValidPath(i.second.path) &&
+            (!checkHash || worker.store.pathContentsGood(i.second.path));
+        if (good == returnValid) result.insert(i.second.path);
     }
     return result;
 }
@@ -3066,7 +3099,7 @@ void SubstitutionGoal::tryToRun()
     }
 
     /* Acquire a lock on the output path. */
-    outputLock = std::shared_ptr<PathLocks>(new PathLocks);
+    outputLock = std::make_shared<PathLocks>();
     if (!outputLock->lockPaths(singleton<PathSet>(storePath), "", false)) {
         worker.waitForAWhile(shared_from_this());
         return;
@@ -3270,7 +3303,7 @@ GoalPtr Worker::makeDerivationGoal(const Path & path, const StringSet & wantedOu
 {
     GoalPtr goal = derivationGoals[path].lock();
     if (!goal) {
-        goal = GoalPtr(new DerivationGoal(path, wantedOutputs, *this, buildMode));
+        goal = std::make_shared<DerivationGoal>(path, wantedOutputs, *this, buildMode);
         derivationGoals[path] = goal;
         wakeUp(goal);
     } else
@@ -3279,11 +3312,19 @@ GoalPtr Worker::makeDerivationGoal(const Path & path, const StringSet & wantedOu
 }
 
 
+GoalPtr Worker::makeBasicDerivationGoal(const Path & drvPath, const BasicDerivation & drv, BuildMode buildMode)
+{
+    auto goal = std::make_shared<DerivationGoal>(drvPath, drv, *this, buildMode);
+    wakeUp(goal);
+    return goal;
+}
+
+
 GoalPtr Worker::makeSubstitutionGoal(const Path & path, bool repair)
 {
     GoalPtr goal = substitutionGoals[path].lock();
     if (!goal) {
-        goal = GoalPtr(new SubstitutionGoal(path, *this, repair));
+        goal = std::make_shared<SubstitutionGoal>(path, *this, repair);
         substitutionGoals[path] = goal;
         wakeUp(goal);
     }
@@ -3604,8 +3645,7 @@ unsigned int Worker::exitStatus()
 
 void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
 {
-    startNest(nest, lvlDebug,
-        format("building %1%") % showPaths(drvPaths));
+    startNest(nest, lvlDebug, format("building %1%") % showPaths(drvPaths));
 
     Worker worker(*this);
 
@@ -3633,6 +3673,35 @@ void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
 }
 
 
+BuildResult LocalStore::buildDerivation(const Path & drvPath, const BasicDerivation & drv,
+    BuildMode buildMode)
+{
+    startNest(nest, lvlDebug, format("building %1%") % showPaths({drvPath}));
+
+    Worker worker(*this);
+    auto goal = worker.makeBasicDerivationGoal(drvPath, drv, buildMode);
+
+    BuildResult result;
+
+    try {
+        worker.run(Goals{goal});
+        if (goal->getExitCode() == Goal::ecSuccess)
+            result.status = BuildResult::Success;
+        else if (worker.permanentFailure)
+            result.status = BuildResult::PermanentFailure;
+        else if (worker.timedOut)
+            result.status = BuildResult::TimedOut;
+        else
+            result.status = BuildResult::MiscFailure;
+    } catch (Error & e) {
+        result.status = BuildResult::MiscFailure;
+        result.errorMsg = e.msg();
+    }
+
+    return result;
+}
+
+
 void LocalStore::ensurePath(const Path & path)
 {
     /* If the path is already valid, we're done. */
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index fbc1d99f3d6f..db35af6cbccc 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -3,6 +3,7 @@
 #include "globals.hh"
 #include "util.hh"
 #include "misc.hh"
+#include "worker-protocol.hh"
 
 
 namespace nix {
@@ -285,7 +286,7 @@ bool wantOutput(const string & output, const std::set<string> & wanted)
 }
 
 
-PathSet outputPaths(const Derivation & drv)
+PathSet outputPaths(const BasicDerivation & drv)
 {
     PathSet paths;
     for (auto & i : drv.outputs)
@@ -294,4 +295,44 @@ PathSet outputPaths(const Derivation & drv)
 }
 
 
+Source & operator >> (Source & in, BasicDerivation & drv)
+{
+    drv.outputs.clear();
+    auto nr = readInt(in);
+    for (unsigned int n = 0; n < nr; n++) {
+        auto name = readString(in);
+        DerivationOutput o;
+        in >> o.path >> o.hashAlgo >> o.hash;
+        assertStorePath(o.path);
+        drv.outputs[name] = o;
+    }
+
+    drv.inputSrcs = readStorePaths<PathSet>(in);
+    in >> drv.platform >> drv.builder;
+    drv.args = readStrings<Strings>(in);
+
+    nr = readInt(in);
+    for (unsigned int n = 0; n < nr; n++) {
+        auto key = readString(in);
+        auto value = readString(in);
+        drv.env[key] = value;
+    }
+
+    return in;
+}
+
+
+Sink & operator << (Sink & out, const BasicDerivation & drv)
+{
+    out << drv.outputs.size();
+    for (auto & i : drv.outputs)
+        out << i.first << i.second.path << i.second.hashAlgo << i.second.hash;
+    out << drv.inputSrcs << drv.platform << drv.builder << drv.args;
+    out << drv.env.size();
+    for (auto & i : drv.env)
+        out << i.first << i.second;
+    return out;
+}
+
+
 }
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 8d5e4d05d469..f0842045f86b 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -40,15 +40,21 @@ typedef std::map<Path, StringSet> DerivationInputs;
 
 typedef std::map<string, string> StringPairs;
 
-struct Derivation
+struct BasicDerivation
 {
     DerivationOutputs outputs; /* keyed on symbolic IDs */
-    DerivationInputs inputDrvs; /* inputs that are sub-derivations */
     PathSet inputSrcs; /* inputs that are sources */
     string platform;
     Path builder;
     Strings args;
     StringPairs env;
+
+    virtual ~BasicDerivation() { };
+};
+
+struct Derivation : BasicDerivation
+{
+    DerivationInputs inputDrvs; /* inputs that are sub-derivations */
 };
 
 
@@ -89,6 +95,12 @@ Path makeDrvPathWithOutputs(const Path & drvPath, const std::set<string> & outpu
 
 bool wantOutput(const string & output, const std::set<string> & wanted);
 
-PathSet outputPaths(const Derivation & drv);
+PathSet outputPaths(const BasicDerivation & drv);
+
+struct Source;
+struct Sink;
+
+Source & operator >> (Source & in, BasicDerivation & drv);
+Sink & operator << (Sink & out, const BasicDerivation & drv);
 
 }
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 819f59327a23..105f3592c685 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -151,6 +151,9 @@ public:
 
     void buildPaths(const PathSet & paths, BuildMode buildMode);
 
+    BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
+        BuildMode buildMode) override;
+
     void ensurePath(const Path & path);
 
     void addTempRoot(const Path & path);
diff --git a/src/libstore/misc.hh b/src/libstore/misc.hh
index d3e31d51f72c..495c528750fb 100644
--- a/src/libstore/misc.hh
+++ b/src/libstore/misc.hh
@@ -32,9 +32,9 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
     PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown,
     unsigned long long & downloadSize, unsigned long long & narSize);
 
-bool willBuildLocally(const Derivation & drv);
+bool willBuildLocally(const BasicDerivation & drv);
 
-bool substitutesAllowed(const Derivation & drv);
+bool substitutesAllowed(const BasicDerivation & drv);
 
 
 }
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index ab87d9d8b16f..acabd6cf0048 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -482,6 +482,13 @@ void RemoteStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
 }
 
 
+BuildResult RemoteStore::buildDerivation(const Path & drvPath, const BasicDerivation & drv,
+    BuildMode buildMode)
+{
+    throw Error("not implemented");
+}
+
+
 void RemoteStore::ensurePath(const Path & path)
 {
     openConnection();
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 030120db4067..09e250386c5c 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -67,6 +67,9 @@ public:
 
     void buildPaths(const PathSet & paths, BuildMode buildMode);
 
+    BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
+        BuildMode buildMode) override;
+
     void ensurePath(const Path & path);
 
     void addTempRoot(const Path & path);
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 3764f3e54242..afc7376fe9ff 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -100,6 +100,22 @@ typedef list<ValidPathInfo> ValidPathInfos;
 enum BuildMode { bmNormal, bmRepair, bmCheck };
 
 
+struct BuildResult
+{
+    enum Status {
+        Success = 0,
+        PermanentFailure = 1,
+        TimedOut = 2,
+        MiscFailure = 3
+    } status = MiscFailure;
+    std::string errorMsg;
+    //time_t startTime = 0, stopTime = 0;
+};
+
+
+struct BasicDerivation;
+
+
 class StoreAPI
 {
 public:
@@ -194,6 +210,12 @@ public:
        not derivations, substitute them. */
     virtual void buildPaths(const PathSet & paths, BuildMode buildMode = bmNormal) = 0;
 
+    /* Build a single non-materialized derivation (i.e. not from an
+       on-disk .drv file). Note that ‘drvPath’ is only used for
+       informational purposes. */
+    virtual BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
+        BuildMode buildMode = bmNormal) = 0;
+
     /* Ensure that a path is valid.  If it is not currently valid, it
        may be made valid by running a substitute (if defined for the
        path). */
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 92417507508a..aa16a20e6d35 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -155,6 +155,12 @@ void writeInt(unsigned int n, Sink & sink)
     sink(buf, sizeof(buf));
 }
 
+Sink & operator << (Sink & out, unsigned int n)
+{
+    writeInt(n, out);
+    return out;
+}
+
 
 void writeLongLong(unsigned long long n, Sink & sink)
 {
@@ -184,6 +190,12 @@ void writeString(const string & s, Sink & sink)
     writeString((const unsigned char *) s.data(), s.size(), sink);
 }
 
+Sink & operator << (Sink & out, const string & s)
+{
+    writeString(s, out);
+    return out;
+}
+
 
 template<class T> void writeStrings(const T & ss, Sink & sink)
 {
@@ -195,6 +207,18 @@ template<class T> void writeStrings(const T & ss, Sink & sink)
 template void writeStrings(const Paths & ss, Sink & sink);
 template void writeStrings(const PathSet & ss, Sink & sink);
 
+Sink & operator << (Sink & out, const Strings & s)
+{
+    writeStrings(s, out);
+    return out;
+}
+
+Sink & operator << (Sink & out, const StringSet & s)
+{
+    writeStrings(s, out);
+    return out;
+}
+
 
 void readPadding(size_t len, Source & source)
 {
@@ -258,7 +282,13 @@ string readString(Source & source)
     return string((char *) buf, len);
 }
 
- 
+Source & operator >> (Source & in, string & s)
+{
+    s = readString(in);
+    return in;
+}
+
+
 template<class T> T readStrings(Source & source)
 {
     unsigned int count = readInt(source);
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index 6a6f028aa652..3a20ad032254 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -118,6 +118,12 @@ void writeString(const unsigned char * buf, size_t len, Sink & sink);
 void writeString(const string & s, Sink & sink);
 template<class T> void writeStrings(const T & ss, Sink & sink);
 
+Sink & operator << (Sink & out, unsigned int n);
+Sink & operator << (Sink & out, const string & s);
+Sink & operator << (Sink & out, const Strings & s);
+Sink & operator << (Sink & out, const StringSet & s);
+
+
 void readPadding(size_t len, Source & source);
 unsigned int readInt(Source & source);
 unsigned long long readLongLong(Source & source);
@@ -125,6 +131,8 @@ size_t readString(unsigned char * buf, size_t max, Source & source);
 string readString(Source & source);
 template<class T> T readStrings(Source & source);
 
+Source & operator >> (Source & in, string & s);
+
 
 MakeError(SerialisationError, Error)
 
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 23b97ca9e5aa..434aefba488f 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -862,6 +862,16 @@ static void opServe(Strings opFlags, Strings opArgs)
     out.flush();
     readInt(in); // Client version, unused for now
 
+    auto getBuildSettings = [&]() {
+        // FIXME: changing options here doesn't work if we're
+        // building through the daemon.
+        verbosity = lvlError;
+        settings.keepLog = false;
+        settings.useSubstitutes = false;
+        settings.maxSilentTime = readInt(in);
+        settings.buildTimeout = readInt(in);
+    };
+
     while (true) {
         ServeCommand cmd;
         try {
@@ -943,19 +953,12 @@ static void opServe(Strings opFlags, Strings opArgs)
                 break;
             }
 
-            case cmdBuildPaths: {
+            case cmdBuildPaths: { /* Used by build-remote.pl. */
 
-                /* Used by build-remote.pl. */
                 if (!writeAllowed) throw Error("building paths is not allowed");
                 PathSet paths = readStorePaths<PathSet>(in);
 
-                // FIXME: changing options here doesn't work if we're
-                // building through the daemon.
-                verbosity = lvlError;
-                settings.keepLog = false;
-                settings.useSubstitutes = false;
-                settings.maxSilentTime = readInt(in);
-                settings.buildTimeout = readInt(in);
+                getBuildSettings();
 
                 try {
                     MonitorFdHup monitor(in.fd);
@@ -969,6 +972,25 @@ static void opServe(Strings opFlags, Strings opArgs)
                 break;
             }
 
+            case cmdBuildDerivation: { /* Used by hydra-queue-runner. */
+
+                if (!writeAllowed) throw Error("building paths is not allowed");
+
+                Path drvPath = readStorePath(in); // informational only
+                BasicDerivation drv;
+                in >> drv;
+
+                getBuildSettings();
+
+                MonitorFdHup monitor(in.fd);
+                auto status = store->buildDerivation(drvPath, drv);
+
+                writeInt(status.status, out);
+                writeString(status.errorMsg, out);
+
+                break;
+            }
+
             case cmdQueryClosure: {
                 bool includeOutputs = readInt(in);
                 PathSet paths = readStorePaths<PathSet>(in);
diff --git a/src/nix-store/serve-protocol.hh b/src/nix-store/serve-protocol.hh
index 741b622beb17..f7f151d4603f 100644
--- a/src/nix-store/serve-protocol.hh
+++ b/src/nix-store/serve-protocol.hh
@@ -5,7 +5,7 @@ namespace nix {
 #define SERVE_MAGIC_1 0x390c9deb
 #define SERVE_MAGIC_2 0x5452eecb
 
-#define SERVE_PROTOCOL_VERSION 0x200
+#define SERVE_PROTOCOL_VERSION 0x201
 #define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
 #define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
 
@@ -17,6 +17,7 @@ typedef enum {
     cmdExportPaths = 5,
     cmdBuildPaths = 6,
     cmdQueryClosure = 7,
+    cmdBuildDerivation = 8,
 } ServeCommand;
 
 }