diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 8c4412f11..5e2e7d89a 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 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 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(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(new Derivation(derivationFromPath(worker.store, drvPath))); - foreach (DerivationOutputs::iterator, i, drv.outputs) - worker.store.addTempRoot(i->second.path); + haveDerivation(); +} + + +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(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 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(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(new HookInstance); + worker.hook = std::make_shared(); /* 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(get(drv.env, "requiredSystemFeatures")); + Strings features = tokenizeString(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(get(drv.env, "passAsFile")); + StringSet passAsFile = tokenizeString(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(get(drv.env, "impureEnvVars")); + Strings varNames = tokenizeString(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(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(allowed); /* This works like the above, except on a per-derivation level */ - Strings impurePaths = tokenizeString(get(drv.env, "__impureHostDeps")); + Strings impurePaths = tokenizeString(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(new AutoDelete(chrootRootDir)); + autoDelChroot = std::make_shared(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(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(new PathLocks); + outputLock = std::make_shared(); if (!outputLock->lockPaths(singleton(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(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(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(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 fbc1d99f3..db35af6cb 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 & 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(in); + in >> drv.platform >> drv.builder; + drv.args = readStrings(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 8d5e4d05d..f0842045f 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -40,15 +40,21 @@ typedef std::map DerivationInputs; typedef std::map 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 & outpu bool wantOutput(const string & output, const std::set & 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 819f59327..105f3592c 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 d3e31d51f..495c52875 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 ab87d9d8b..acabd6cf0 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 030120db4..09e250386 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 3764f3e54..afc7376fe 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -100,6 +100,22 @@ typedef list 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 924175075..aa16a20e6 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 void writeStrings(const T & ss, Sink & sink) { @@ -195,6 +207,18 @@ template 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 T readStrings(Source & source) { unsigned int count = readInt(source); diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index 6a6f028aa..3a20ad032 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 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 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 23b97ca9e..434aefba4 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(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(in); diff --git a/src/nix-store/serve-protocol.hh b/src/nix-store/serve-protocol.hh index 741b622be..f7f151d46 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; }