Eliminate the substituter mechanism
Substitution is now simply a Store -> Store copy operation, most typically from BinaryCacheStore to LocalStore.
This commit is contained in:
parent
21e9d183cc
commit
aa3bc3d5dc
16 changed files with 166 additions and 597 deletions
|
@ -7,18 +7,13 @@ nix_bin_scripts := \
|
||||||
|
|
||||||
bin-scripts += $(nix_bin_scripts)
|
bin-scripts += $(nix_bin_scripts)
|
||||||
|
|
||||||
nix_substituters := \
|
|
||||||
$(d)/copy-from-other-stores.pl \
|
|
||||||
$(d)/download-from-binary-cache.pl
|
|
||||||
|
|
||||||
nix_noinst_scripts := \
|
nix_noinst_scripts := \
|
||||||
$(d)/build-remote.pl \
|
$(d)/build-remote.pl \
|
||||||
$(d)/find-runtime-roots.pl \
|
$(d)/find-runtime-roots.pl \
|
||||||
$(d)/resolve-system-dependencies.pl \
|
$(d)/resolve-system-dependencies.pl \
|
||||||
$(d)/nix-http-export.cgi \
|
$(d)/nix-http-export.cgi \
|
||||||
$(d)/nix-profile.sh \
|
$(d)/nix-profile.sh \
|
||||||
$(d)/nix-reduce-build \
|
$(d)/nix-reduce-build
|
||||||
$(nix_substituters)
|
|
||||||
|
|
||||||
noinst-scripts += $(nix_noinst_scripts)
|
noinst-scripts += $(nix_noinst_scripts)
|
||||||
|
|
||||||
|
@ -28,7 +23,6 @@ $(eval $(call install-file-as, $(d)/nix-profile.sh, $(profiledir)/nix.sh, 0644))
|
||||||
$(eval $(call install-program-in, $(d)/find-runtime-roots.pl, $(libexecdir)/nix))
|
$(eval $(call install-program-in, $(d)/find-runtime-roots.pl, $(libexecdir)/nix))
|
||||||
$(eval $(call install-program-in, $(d)/build-remote.pl, $(libexecdir)/nix))
|
$(eval $(call install-program-in, $(d)/build-remote.pl, $(libexecdir)/nix))
|
||||||
$(eval $(call install-program-in, $(d)/resolve-system-dependencies.pl, $(libexecdir)/nix))
|
$(eval $(call install-program-in, $(d)/resolve-system-dependencies.pl, $(libexecdir)/nix))
|
||||||
$(foreach prog, $(nix_substituters), $(eval $(call install-program-in, $(prog), $(libexecdir)/nix/substituters)))
|
|
||||||
$(eval $(call install-symlink, nix-build, $(bindir)/nix-shell))
|
$(eval $(call install-symlink, nix-build, $(bindir)/nix-shell))
|
||||||
|
|
||||||
clean-files += $(nix_bin_scripts) $(nix_noinst_scripts)
|
clean-files += $(nix_bin_scripts) $(nix_noinst_scripts)
|
||||||
|
|
|
@ -8,11 +8,14 @@
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "affinity.hh"
|
#include "affinity.hh"
|
||||||
#include "builtins.hh"
|
#include "builtins.hh"
|
||||||
|
#include "finally.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
@ -199,8 +202,6 @@ struct Child
|
||||||
time_t timeStarted;
|
time_t timeStarted;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef map<pid_t, Child> Children;
|
|
||||||
|
|
||||||
|
|
||||||
/* The worker class. */
|
/* The worker class. */
|
||||||
class Worker
|
class Worker
|
||||||
|
@ -220,7 +221,7 @@ private:
|
||||||
WeakGoals wantingToBuild;
|
WeakGoals wantingToBuild;
|
||||||
|
|
||||||
/* Child processes currently running. */
|
/* Child processes currently running. */
|
||||||
Children children;
|
std::list<Child> children;
|
||||||
|
|
||||||
/* Number of build slots occupied. This includes local builds and
|
/* Number of build slots occupied. This includes local builds and
|
||||||
substitutions but not remote builds via the build hook. */
|
substitutions but not remote builds via the build hook. */
|
||||||
|
@ -278,14 +279,14 @@ public:
|
||||||
|
|
||||||
/* Registers a running child process. `inBuildSlot' means that
|
/* Registers a running child process. `inBuildSlot' means that
|
||||||
the process counts towards the jobs limit. */
|
the process counts towards the jobs limit. */
|
||||||
void childStarted(GoalPtr goal, pid_t pid,
|
void childStarted(GoalPtr goal, const set<int> & fds,
|
||||||
const set<int> & fds, bool inBuildSlot, bool respectTimeouts);
|
bool inBuildSlot, bool respectTimeouts);
|
||||||
|
|
||||||
/* Unregisters a running child process. `wakeSleepers' should be
|
/* Unregisters a running child process. `wakeSleepers' should be
|
||||||
false if there is no sense in waking up goals that are sleeping
|
false if there is no sense in waking up goals that are sleeping
|
||||||
because they can't run yet (e.g., there is no free build slot,
|
because they can't run yet (e.g., there is no free build slot,
|
||||||
or the hook would still say `postpone'). */
|
or the hook would still say `postpone'). */
|
||||||
void childTerminated(pid_t pid, bool wakeSleepers = true);
|
void childTerminated(GoalPtr goal, bool wakeSleepers = true);
|
||||||
|
|
||||||
/* Put `goal' to sleep until a build slot becomes available (which
|
/* Put `goal' to sleep until a build slot becomes available (which
|
||||||
might be right away). */
|
might be right away). */
|
||||||
|
@ -942,7 +943,7 @@ DerivationGoal::~DerivationGoal()
|
||||||
void DerivationGoal::killChild()
|
void DerivationGoal::killChild()
|
||||||
{
|
{
|
||||||
if (pid != -1) {
|
if (pid != -1) {
|
||||||
worker.childTerminated(pid);
|
worker.childTerminated(shared_from_this());
|
||||||
|
|
||||||
if (buildUser.enabled()) {
|
if (buildUser.enabled()) {
|
||||||
/* If we're using a build user, then there is a tricky
|
/* If we're using a build user, then there is a tricky
|
||||||
|
@ -1403,22 +1404,14 @@ void DerivationGoal::buildDone()
|
||||||
to have terminated. In fact, the builder could also have
|
to have terminated. In fact, the builder could also have
|
||||||
simply have closed its end of the pipe --- just don't do that
|
simply have closed its end of the pipe --- just don't do that
|
||||||
:-) */
|
:-) */
|
||||||
int status;
|
/* !!! this could block! security problem! solution: kill the
|
||||||
pid_t savedPid;
|
child */
|
||||||
if (hook) {
|
int status = hook ? hook->pid.wait(true) : pid.wait(true);
|
||||||
savedPid = hook->pid;
|
|
||||||
status = hook->pid.wait(true);
|
|
||||||
} else {
|
|
||||||
/* !!! this could block! security problem! solution: kill the
|
|
||||||
child */
|
|
||||||
savedPid = pid;
|
|
||||||
status = pid.wait(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(format("builder process for ‘%1%’ finished") % drvPath);
|
debug(format("builder process for ‘%1%’ finished") % drvPath);
|
||||||
|
|
||||||
/* So the child is gone now. */
|
/* So the child is gone now. */
|
||||||
worker.childTerminated(savedPid);
|
worker.childTerminated(shared_from_this());
|
||||||
|
|
||||||
/* Close the read side of the logger pipe. */
|
/* Close the read side of the logger pipe. */
|
||||||
if (hook) {
|
if (hook) {
|
||||||
|
@ -1621,7 +1614,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
set<int> fds;
|
set<int> fds;
|
||||||
fds.insert(hook->fromHook.readSide);
|
fds.insert(hook->fromHook.readSide);
|
||||||
fds.insert(hook->builderOut.readSide);
|
fds.insert(hook->builderOut.readSide);
|
||||||
worker.childStarted(shared_from_this(), hook->pid, fds, false, false);
|
worker.childStarted(shared_from_this(), fds, false, false);
|
||||||
|
|
||||||
return rpAccept;
|
return rpAccept;
|
||||||
}
|
}
|
||||||
|
@ -2155,7 +2148,7 @@ void DerivationGoal::startBuilder()
|
||||||
/* parent */
|
/* parent */
|
||||||
pid.setSeparatePG(true);
|
pid.setSeparatePG(true);
|
||||||
builderOut.writeSide.close();
|
builderOut.writeSide.close();
|
||||||
worker.childStarted(shared_from_this(), pid,
|
worker.childStarted(shared_from_this(),
|
||||||
singleton<set<int> >(builderOut.readSide), true, true);
|
singleton<set<int> >(builderOut.readSide), true, true);
|
||||||
|
|
||||||
/* Check if setting up the build environment failed. */
|
/* Check if setting up the build environment failed. */
|
||||||
|
@ -3032,28 +3025,24 @@ private:
|
||||||
Path storePath;
|
Path storePath;
|
||||||
|
|
||||||
/* The remaining substituters. */
|
/* The remaining substituters. */
|
||||||
Paths subs;
|
std::list<ref<Store>> subs;
|
||||||
|
|
||||||
/* The current substituter. */
|
/* The current substituter. */
|
||||||
Path sub;
|
std::shared_ptr<Store> sub;
|
||||||
|
|
||||||
/* Whether any substituter can realise this path */
|
/* Whether any substituter can realise this path. */
|
||||||
bool hasSubstitute;
|
bool hasSubstitute;
|
||||||
|
|
||||||
/* Path info returned by the substituter's query info operation. */
|
/* Path info returned by the substituter's query info operation. */
|
||||||
SubstitutablePathInfo info;
|
std::shared_ptr<const ValidPathInfo> info;
|
||||||
|
|
||||||
/* Pipe for the substituter's standard output. */
|
/* Pipe for the substituter's standard output. */
|
||||||
Pipe outPipe;
|
Pipe outPipe;
|
||||||
|
|
||||||
/* Pipe for the substituter's standard error. */
|
/* The substituter thread. */
|
||||||
Pipe logPipe;
|
std::thread thr;
|
||||||
|
|
||||||
/* The process ID of the builder. */
|
std::promise<void> promise;
|
||||||
Pid pid;
|
|
||||||
|
|
||||||
/* Lock on the store path. */
|
|
||||||
std::shared_ptr<PathLocks> outputLock;
|
|
||||||
|
|
||||||
/* Whether to try to repair a valid path. */
|
/* Whether to try to repair a valid path. */
|
||||||
bool repair;
|
bool repair;
|
||||||
|
@ -3069,7 +3058,7 @@ public:
|
||||||
SubstitutionGoal(const Path & storePath, Worker & worker, bool repair = false);
|
SubstitutionGoal(const Path & storePath, Worker & worker, bool repair = false);
|
||||||
~SubstitutionGoal();
|
~SubstitutionGoal();
|
||||||
|
|
||||||
void timedOut();
|
void timedOut() { abort(); };
|
||||||
|
|
||||||
string key()
|
string key()
|
||||||
{
|
{
|
||||||
|
@ -3110,18 +3099,14 @@ SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool
|
||||||
|
|
||||||
SubstitutionGoal::~SubstitutionGoal()
|
SubstitutionGoal::~SubstitutionGoal()
|
||||||
{
|
{
|
||||||
if (pid != -1) worker.childTerminated(pid);
|
try {
|
||||||
}
|
if (thr.joinable()) {
|
||||||
|
thr.join();
|
||||||
|
worker.childTerminated(shared_from_this());
|
||||||
void SubstitutionGoal::timedOut()
|
}
|
||||||
{
|
} catch (...) {
|
||||||
if (pid != -1) {
|
ignoreException();
|
||||||
pid_t savedPid = pid;
|
|
||||||
pid.kill();
|
|
||||||
worker.childTerminated(savedPid);
|
|
||||||
}
|
}
|
||||||
amDone(ecFailed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3146,7 +3131,7 @@ void SubstitutionGoal::init()
|
||||||
if (settings.readOnlyMode)
|
if (settings.readOnlyMode)
|
||||||
throw Error(format("cannot substitute path ‘%1%’ - no write access to the Nix store") % storePath);
|
throw Error(format("cannot substitute path ‘%1%’ - no write access to the Nix store") % storePath);
|
||||||
|
|
||||||
subs = settings.substituters;
|
subs = getDefaultSubstituters();
|
||||||
|
|
||||||
tryNext();
|
tryNext();
|
||||||
}
|
}
|
||||||
|
@ -3171,17 +3156,19 @@ void SubstitutionGoal::tryNext()
|
||||||
sub = subs.front();
|
sub = subs.front();
|
||||||
subs.pop_front();
|
subs.pop_front();
|
||||||
|
|
||||||
SubstitutablePathInfos infos;
|
try {
|
||||||
PathSet dummy(singleton<PathSet>(storePath));
|
// FIXME: make async
|
||||||
worker.store.querySubstitutablePathInfos(sub, dummy, infos);
|
info = sub->queryPathInfo(storePath);
|
||||||
SubstitutablePathInfos::iterator k = infos.find(storePath);
|
} catch (InvalidPath &) {
|
||||||
if (k == infos.end()) { tryNext(); return; }
|
tryNext();
|
||||||
info = k->second;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hasSubstitute = true;
|
hasSubstitute = true;
|
||||||
|
|
||||||
/* To maintain the closure invariant, we first have to realise the
|
/* To maintain the closure invariant, we first have to realise the
|
||||||
paths referenced by this one. */
|
paths referenced by this one. */
|
||||||
for (auto & i : info.references)
|
for (auto & i : info->references)
|
||||||
if (i != storePath) /* ignore self-references */
|
if (i != storePath) /* ignore self-references */
|
||||||
addWaitee(worker.makeSubstitutionGoal(i));
|
addWaitee(worker.makeSubstitutionGoal(i));
|
||||||
|
|
||||||
|
@ -3202,7 +3189,7 @@ void SubstitutionGoal::referencesValid()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : info.references)
|
for (auto & i : info->references)
|
||||||
if (i != storePath) /* ignore self-references */
|
if (i != storePath) /* ignore self-references */
|
||||||
assert(worker.store.isValidPath(i));
|
assert(worker.store.isValidPath(i));
|
||||||
|
|
||||||
|
@ -3224,70 +3211,30 @@ void SubstitutionGoal::tryToRun()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Maybe a derivation goal has already locked this path
|
|
||||||
(exceedingly unlikely, since it should have used a substitute
|
|
||||||
first, but let's be defensive). */
|
|
||||||
outputLock.reset(); // make sure this goal's lock is gone
|
|
||||||
if (pathIsLockedByMe(storePath)) {
|
|
||||||
debug(format("restarting substitution of ‘%1%’ because it's locked by another goal")
|
|
||||||
% storePath);
|
|
||||||
worker.waitForAnyGoal(shared_from_this());
|
|
||||||
return; /* restart in the tryToRun() state when another goal finishes */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Acquire a lock on the output path. */
|
|
||||||
outputLock = std::make_shared<PathLocks>();
|
|
||||||
if (!outputLock->lockPaths(singleton<PathSet>(storePath), "", false)) {
|
|
||||||
worker.waitForAWhile(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check again whether the path is invalid. */
|
|
||||||
if (!repair && worker.store.isValidPath(storePath)) {
|
|
||||||
debug(format("store path ‘%1%’ has become valid") % storePath);
|
|
||||||
outputLock->setDeletion(true);
|
|
||||||
amDone(ecSuccess);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printMsg(lvlInfo, format("fetching path ‘%1%’...") % storePath);
|
printMsg(lvlInfo, format("fetching path ‘%1%’...") % storePath);
|
||||||
|
|
||||||
outPipe.create();
|
outPipe.create();
|
||||||
logPipe.create();
|
|
||||||
|
|
||||||
destPath = repair ? storePath + ".tmp" : storePath;
|
promise = std::promise<void>();
|
||||||
|
|
||||||
/* Remove the (stale) output path if it exists. */
|
thr = std::thread([this]() {
|
||||||
deletePath(destPath);
|
try {
|
||||||
|
/* Wake up the worker loop when we're done. */
|
||||||
|
Finally updateStats([this]() { outPipe.writeSide.close(); });
|
||||||
|
|
||||||
worker.store.setSubstituterEnv();
|
StringSink sink;
|
||||||
|
sub->exportPaths({storePath}, false, sink);
|
||||||
|
|
||||||
/* Fill in the arguments. */
|
StringSource source(*sink.s);
|
||||||
Strings args;
|
worker.store.importPaths(false, source, 0);
|
||||||
args.push_back(baseNameOf(sub));
|
|
||||||
args.push_back("--substitute");
|
|
||||||
args.push_back(storePath);
|
|
||||||
args.push_back(destPath);
|
|
||||||
|
|
||||||
/* Fork the substitute program. */
|
promise.set_value();
|
||||||
pid = startProcess([&]() {
|
} catch (...) {
|
||||||
|
promise.set_exception(std::current_exception());
|
||||||
commonChildInit(logPipe);
|
}
|
||||||
|
|
||||||
if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1)
|
|
||||||
throw SysError("cannot dup output pipe into stdout");
|
|
||||||
|
|
||||||
execv(sub.c_str(), stringsToCharPtrs(args).data());
|
|
||||||
|
|
||||||
throw SysError(format("executing ‘%1%’") % sub);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pid.setSeparatePG(true);
|
worker.childStarted(shared_from_this(), {outPipe.readSide}, true, false);
|
||||||
pid.setKillSignal(SIGTERM);
|
|
||||||
outPipe.writeSide.close();
|
|
||||||
logPipe.writeSide.close();
|
|
||||||
worker.childStarted(shared_from_this(),
|
|
||||||
pid, singleton<set<int> >(logPipe.readSide), true, true);
|
|
||||||
|
|
||||||
state = &SubstitutionGoal::finished;
|
state = &SubstitutionGoal::finished;
|
||||||
}
|
}
|
||||||
|
@ -3297,52 +3244,12 @@ void SubstitutionGoal::finished()
|
||||||
{
|
{
|
||||||
trace("substitute finished");
|
trace("substitute finished");
|
||||||
|
|
||||||
/* Since we got an EOF on the logger pipe, the substitute is
|
thr.join();
|
||||||
presumed to have terminated. */
|
worker.childTerminated(shared_from_this());
|
||||||
pid_t savedPid = pid;
|
|
||||||
int status = pid.wait(true);
|
|
||||||
|
|
||||||
/* So the child is gone now. */
|
|
||||||
worker.childTerminated(savedPid);
|
|
||||||
|
|
||||||
/* Close the read side of the logger pipe. */
|
|
||||||
logPipe.readSide.close();
|
|
||||||
|
|
||||||
/* Get the hash info from stdout. */
|
|
||||||
string dummy = readLine(outPipe.readSide);
|
|
||||||
string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : "";
|
|
||||||
outPipe.readSide.close();
|
|
||||||
|
|
||||||
/* Check the exit status and the build result. */
|
|
||||||
HashResult hash;
|
|
||||||
try {
|
try {
|
||||||
|
promise.get_future().get();
|
||||||
if (!statusOk(status))
|
} catch (Error & e) {
|
||||||
throw SubstError(format("fetching path ‘%1%’ %2%")
|
|
||||||
% storePath % statusToString(status));
|
|
||||||
|
|
||||||
if (!pathExists(destPath))
|
|
||||||
throw SubstError(format("substitute did not produce path ‘%1%’") % destPath);
|
|
||||||
|
|
||||||
hash = hashPath(htSHA256, destPath);
|
|
||||||
|
|
||||||
/* Verify the expected hash we got from the substituer. */
|
|
||||||
if (expectedHashStr != "") {
|
|
||||||
size_t n = expectedHashStr.find(':');
|
|
||||||
if (n == string::npos)
|
|
||||||
throw Error(format("bad hash from substituter: %1%") % expectedHashStr);
|
|
||||||
HashType hashType = parseHashType(string(expectedHashStr, 0, n));
|
|
||||||
if (hashType == htUnknown)
|
|
||||||
throw Error(format("unknown hash algorithm in ‘%1%’") % expectedHashStr);
|
|
||||||
Hash expectedHash = parseHash16or32(hashType, string(expectedHashStr, n + 1));
|
|
||||||
Hash actualHash = hashType == htSHA256 ? hash.first : hashPath(hashType, destPath).first;
|
|
||||||
if (expectedHash != actualHash)
|
|
||||||
throw SubstError(format("hash mismatch in downloaded path ‘%1%’: expected %2%, got %3%")
|
|
||||||
% storePath % printHash(expectedHash) % printHash(actualHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SubstError & e) {
|
|
||||||
|
|
||||||
printMsg(lvlInfo, e.msg());
|
printMsg(lvlInfo, e.msg());
|
||||||
|
|
||||||
/* Try the next substitute. */
|
/* Try the next substitute. */
|
||||||
|
@ -3351,23 +3258,6 @@ void SubstitutionGoal::finished()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repair) replaceValidPath(storePath, destPath);
|
|
||||||
|
|
||||||
canonicalisePathMetaData(storePath, -1);
|
|
||||||
|
|
||||||
worker.store.optimisePath(storePath); // FIXME: combine with hashPath()
|
|
||||||
|
|
||||||
ValidPathInfo info2;
|
|
||||||
info2.path = storePath;
|
|
||||||
info2.narHash = hash.first;
|
|
||||||
info2.narSize = hash.second;
|
|
||||||
info2.references = info.references;
|
|
||||||
info2.deriver = info.deriver;
|
|
||||||
worker.store.registerValidPath(info2);
|
|
||||||
|
|
||||||
outputLock->setDeletion(true);
|
|
||||||
outputLock.reset();
|
|
||||||
|
|
||||||
worker.markContentsGood(storePath);
|
worker.markContentsGood(storePath);
|
||||||
|
|
||||||
printMsg(lvlChatty,
|
printMsg(lvlChatty,
|
||||||
|
@ -3379,18 +3269,15 @@ void SubstitutionGoal::finished()
|
||||||
|
|
||||||
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
|
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
|
||||||
{
|
{
|
||||||
assert(fd == logPipe.readSide);
|
|
||||||
printMsg(lvlError, data); // FIXME
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SubstitutionGoal::handleEOF(int fd)
|
void SubstitutionGoal::handleEOF(int fd)
|
||||||
{
|
{
|
||||||
if (fd == logPipe.readSide) worker.wakeUp(shared_from_this());
|
if (fd == outPipe.readSide) worker.wakeUp(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
@ -3506,9 +3393,8 @@ unsigned Worker::getNrLocalBuilds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Worker::childStarted(GoalPtr goal,
|
void Worker::childStarted(GoalPtr goal, const set<int> & fds,
|
||||||
pid_t pid, const set<int> & fds, bool inBuildSlot,
|
bool inBuildSlot, bool respectTimeouts)
|
||||||
bool respectTimeouts)
|
|
||||||
{
|
{
|
||||||
Child child;
|
Child child;
|
||||||
child.goal = goal;
|
child.goal = goal;
|
||||||
|
@ -3516,30 +3402,29 @@ void Worker::childStarted(GoalPtr goal,
|
||||||
child.timeStarted = child.lastOutput = time(0);
|
child.timeStarted = child.lastOutput = time(0);
|
||||||
child.inBuildSlot = inBuildSlot;
|
child.inBuildSlot = inBuildSlot;
|
||||||
child.respectTimeouts = respectTimeouts;
|
child.respectTimeouts = respectTimeouts;
|
||||||
children[pid] = child;
|
children.emplace_back(child);
|
||||||
if (inBuildSlot) nrLocalBuilds++;
|
if (inBuildSlot) nrLocalBuilds++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Worker::childTerminated(pid_t pid, bool wakeSleepers)
|
void Worker::childTerminated(GoalPtr goal, bool wakeSleepers)
|
||||||
{
|
{
|
||||||
assert(pid != -1); /* common mistake */
|
auto i = std::find_if(children.begin(), children.end(),
|
||||||
|
[&](const Child & child) { return child.goal.lock() == goal; });
|
||||||
Children::iterator i = children.find(pid);
|
|
||||||
assert(i != children.end());
|
assert(i != children.end());
|
||||||
|
|
||||||
if (i->second.inBuildSlot) {
|
if (i->inBuildSlot) {
|
||||||
assert(nrLocalBuilds > 0);
|
assert(nrLocalBuilds > 0);
|
||||||
nrLocalBuilds--;
|
nrLocalBuilds--;
|
||||||
}
|
}
|
||||||
|
|
||||||
children.erase(pid);
|
children.erase(i);
|
||||||
|
|
||||||
if (wakeSleepers) {
|
if (wakeSleepers) {
|
||||||
|
|
||||||
/* Wake up goals waiting for a build slot. */
|
/* Wake up goals waiting for a build slot. */
|
||||||
for (auto & i : wantingToBuild) {
|
for (auto & j : wantingToBuild) {
|
||||||
GoalPtr goal = i.lock();
|
GoalPtr goal = j.lock();
|
||||||
if (goal) wakeUp(goal);
|
if (goal) wakeUp(goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3641,11 +3526,11 @@ void Worker::waitForInput()
|
||||||
assert(sizeof(time_t) >= sizeof(long));
|
assert(sizeof(time_t) >= sizeof(long));
|
||||||
time_t nearest = LONG_MAX; // nearest deadline
|
time_t nearest = LONG_MAX; // nearest deadline
|
||||||
for (auto & i : children) {
|
for (auto & i : children) {
|
||||||
if (!i.second.respectTimeouts) continue;
|
if (!i.respectTimeouts) continue;
|
||||||
if (settings.maxSilentTime != 0)
|
if (settings.maxSilentTime != 0)
|
||||||
nearest = std::min(nearest, i.second.lastOutput + settings.maxSilentTime);
|
nearest = std::min(nearest, i.lastOutput + settings.maxSilentTime);
|
||||||
if (settings.buildTimeout != 0)
|
if (settings.buildTimeout != 0)
|
||||||
nearest = std::min(nearest, i.second.timeStarted + settings.buildTimeout);
|
nearest = std::min(nearest, i.timeStarted + settings.buildTimeout);
|
||||||
}
|
}
|
||||||
if (nearest != LONG_MAX) {
|
if (nearest != LONG_MAX) {
|
||||||
timeout.tv_sec = std::max((time_t) 1, nearest - before);
|
timeout.tv_sec = std::max((time_t) 1, nearest - before);
|
||||||
|
@ -3663,7 +3548,6 @@ void Worker::waitForInput()
|
||||||
timeout.tv_sec = std::max((time_t) 1, (time_t) (lastWokenUp + settings.pollInterval - before));
|
timeout.tv_sec = std::max((time_t) 1, (time_t) (lastWokenUp + settings.pollInterval - before));
|
||||||
} else lastWokenUp = 0;
|
} else lastWokenUp = 0;
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
/* Use select() to wait for the input side of any logger pipe to
|
/* Use select() to wait for the input side of any logger pipe to
|
||||||
become `available'. Note that `available' (i.e., non-blocking)
|
become `available'. Note that `available' (i.e., non-blocking)
|
||||||
includes EOF. */
|
includes EOF. */
|
||||||
|
@ -3671,7 +3555,7 @@ void Worker::waitForInput()
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
int fdMax = 0;
|
int fdMax = 0;
|
||||||
for (auto & i : children) {
|
for (auto & i : children) {
|
||||||
for (auto & j : i.second.fds) {
|
for (auto & j : i.fds) {
|
||||||
FD_SET(j, &fds);
|
FD_SET(j, &fds);
|
||||||
if (j >= fdMax) fdMax = j + 1;
|
if (j >= fdMax) fdMax = j + 1;
|
||||||
}
|
}
|
||||||
|
@ -3685,22 +3569,16 @@ void Worker::waitForInput()
|
||||||
time_t after = time(0);
|
time_t after = time(0);
|
||||||
|
|
||||||
/* Process all available file descriptors. */
|
/* Process all available file descriptors. */
|
||||||
|
decltype(children)::iterator i;
|
||||||
|
for (auto j = children.begin(); j != children.end(); j = i) {
|
||||||
|
i = std::next(j);
|
||||||
|
|
||||||
/* Since goals may be canceled from inside the loop below (causing
|
|
||||||
them go be erased from the `children' map), we have to be
|
|
||||||
careful that we don't keep iterators alive across calls to
|
|
||||||
timedOut(). */
|
|
||||||
set<pid_t> pids;
|
|
||||||
for (auto & i : children) pids.insert(i.first);
|
|
||||||
|
|
||||||
for (auto & i : pids) {
|
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
Children::iterator j = children.find(i);
|
|
||||||
if (j == children.end()) continue; // child destroyed
|
GoalPtr goal = j->goal.lock();
|
||||||
GoalPtr goal = j->second.goal.lock();
|
|
||||||
assert(goal);
|
assert(goal);
|
||||||
|
|
||||||
set<int> fds2(j->second.fds);
|
set<int> fds2(j->fds);
|
||||||
for (auto & k : fds2) {
|
for (auto & k : fds2) {
|
||||||
if (FD_ISSET(k, &fds)) {
|
if (FD_ISSET(k, &fds)) {
|
||||||
unsigned char buffer[4096];
|
unsigned char buffer[4096];
|
||||||
|
@ -3712,12 +3590,12 @@ void Worker::waitForInput()
|
||||||
} else if (rd == 0) {
|
} else if (rd == 0) {
|
||||||
debug(format("%1%: got EOF") % goal->getName());
|
debug(format("%1%: got EOF") % goal->getName());
|
||||||
goal->handleEOF(k);
|
goal->handleEOF(k);
|
||||||
j->second.fds.erase(k);
|
j->fds.erase(k);
|
||||||
} else {
|
} else {
|
||||||
printMsg(lvlVomit, format("%1%: read %2% bytes")
|
printMsg(lvlVomit, format("%1%: read %2% bytes")
|
||||||
% goal->getName() % rd);
|
% goal->getName() % rd);
|
||||||
string data((char *) buffer, rd);
|
string data((char *) buffer, rd);
|
||||||
j->second.lastOutput = after;
|
j->lastOutput = after;
|
||||||
goal->handleChildOutput(k, data);
|
goal->handleChildOutput(k, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3725,8 +3603,8 @@ void Worker::waitForInput()
|
||||||
|
|
||||||
if (goal->getExitCode() == Goal::ecBusy &&
|
if (goal->getExitCode() == Goal::ecBusy &&
|
||||||
settings.maxSilentTime != 0 &&
|
settings.maxSilentTime != 0 &&
|
||||||
j->second.respectTimeouts &&
|
j->respectTimeouts &&
|
||||||
after - j->second.lastOutput >= (time_t) settings.maxSilentTime)
|
after - j->lastOutput >= (time_t) settings.maxSilentTime)
|
||||||
{
|
{
|
||||||
printMsg(lvlError,
|
printMsg(lvlError,
|
||||||
format("%1% timed out after %2% seconds of silence")
|
format("%1% timed out after %2% seconds of silence")
|
||||||
|
@ -3736,8 +3614,8 @@ void Worker::waitForInput()
|
||||||
|
|
||||||
else if (goal->getExitCode() == Goal::ecBusy &&
|
else if (goal->getExitCode() == Goal::ecBusy &&
|
||||||
settings.buildTimeout != 0 &&
|
settings.buildTimeout != 0 &&
|
||||||
j->second.respectTimeouts &&
|
j->respectTimeouts &&
|
||||||
after - j->second.timeStarted >= (time_t) settings.buildTimeout)
|
after - j->timeStarted >= (time_t) settings.buildTimeout)
|
||||||
{
|
{
|
||||||
printMsg(lvlError,
|
printMsg(lvlError,
|
||||||
format("%1% timed out after %2% seconds")
|
format("%1% timed out after %2% seconds")
|
||||||
|
|
|
@ -184,19 +184,6 @@ void Settings::update()
|
||||||
_get(enableImportNative, "allow-unsafe-native-code-during-evaluation");
|
_get(enableImportNative, "allow-unsafe-native-code-during-evaluation");
|
||||||
_get(useCaseHack, "use-case-hack");
|
_get(useCaseHack, "use-case-hack");
|
||||||
_get(preBuildHook, "pre-build-hook");
|
_get(preBuildHook, "pre-build-hook");
|
||||||
|
|
||||||
string subs = getEnv("NIX_SUBSTITUTERS", "default");
|
|
||||||
if (subs == "default") {
|
|
||||||
substituters.clear();
|
|
||||||
#if 0
|
|
||||||
if (getEnv("NIX_OTHER_STORES") != "")
|
|
||||||
substituters.push_back(nixLibexecDir + "/nix/substituters/copy-from-other-stores.pl");
|
|
||||||
#endif
|
|
||||||
substituters.push_back(nixLibexecDir + "/nix/substituters/download-from-binary-cache.pl");
|
|
||||||
if (useSshSubstituter && !sshSubstituterHosts.empty())
|
|
||||||
substituters.push_back(nixLibexecDir + "/nix/substituters/download-via-ssh");
|
|
||||||
} else
|
|
||||||
substituters = tokenizeString<Strings>(subs, ":");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -110,11 +110,6 @@ struct Settings {
|
||||||
means infinity. */
|
means infinity. */
|
||||||
time_t buildTimeout;
|
time_t buildTimeout;
|
||||||
|
|
||||||
/* The substituters. There are programs that can somehow realise
|
|
||||||
a store path without building, e.g., by downloading it or
|
|
||||||
copying it from a CD. */
|
|
||||||
Paths substituters;
|
|
||||||
|
|
||||||
/* Whether to use build hooks (for distributed builds). Sometimes
|
/* Whether to use build hooks (for distributed builds). Sometimes
|
||||||
users want to disable this from the command-line. */
|
users want to disable this from the command-line. */
|
||||||
bool useBuildHook;
|
bool useBuildHook;
|
||||||
|
|
|
@ -5,12 +5,11 @@
|
||||||
#include "pathlocks.hh"
|
#include "pathlocks.hh"
|
||||||
#include "worker-protocol.hh"
|
#include "worker-protocol.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "affinity.hh"
|
#include "nar-info.hh"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -219,19 +218,6 @@ LocalStore::~LocalStore()
|
||||||
{
|
{
|
||||||
auto state(_state.lock());
|
auto state(_state.lock());
|
||||||
|
|
||||||
try {
|
|
||||||
for (auto & i : state->runningSubstituters) {
|
|
||||||
if (i.second.disabled) continue;
|
|
||||||
i.second.to.close();
|
|
||||||
i.second.from.close();
|
|
||||||
i.second.error.close();
|
|
||||||
if (i.second.pid != -1)
|
|
||||||
i.second.pid.wait(true);
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
ignoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (state->fdTempRoots != -1) {
|
if (state->fdTempRoots != -1) {
|
||||||
state->fdTempRoots.close();
|
state->fdTempRoots.close();
|
||||||
|
@ -792,205 +778,42 @@ Path LocalStore::queryPathFromHashPart(const string & hashPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::setSubstituterEnv()
|
|
||||||
{
|
|
||||||
static std::atomic_flag done;
|
|
||||||
|
|
||||||
if (done.test_and_set()) return;
|
|
||||||
|
|
||||||
/* Pass configuration options (including those overridden with
|
|
||||||
--option) to substituters. */
|
|
||||||
setenv("_NIX_OPTIONS", settings.pack().c_str(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run)
|
|
||||||
{
|
|
||||||
if (run.disabled || run.pid != -1) return;
|
|
||||||
|
|
||||||
debug(format("starting substituter program ‘%1%’") % substituter);
|
|
||||||
|
|
||||||
Pipe toPipe, fromPipe, errorPipe;
|
|
||||||
|
|
||||||
toPipe.create();
|
|
||||||
fromPipe.create();
|
|
||||||
errorPipe.create();
|
|
||||||
|
|
||||||
setSubstituterEnv();
|
|
||||||
|
|
||||||
run.pid = startProcess([&]() {
|
|
||||||
if (dup2(toPipe.readSide, STDIN_FILENO) == -1)
|
|
||||||
throw SysError("dupping stdin");
|
|
||||||
if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1)
|
|
||||||
throw SysError("dupping stdout");
|
|
||||||
if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1)
|
|
||||||
throw SysError("dupping stderr");
|
|
||||||
execl(substituter.c_str(), substituter.c_str(), "--query", NULL);
|
|
||||||
throw SysError(format("executing ‘%1%’") % substituter);
|
|
||||||
});
|
|
||||||
|
|
||||||
run.program = baseNameOf(substituter);
|
|
||||||
run.to = toPipe.writeSide.borrow();
|
|
||||||
run.from = run.fromBuf.fd = fromPipe.readSide.borrow();
|
|
||||||
run.error = errorPipe.readSide.borrow();
|
|
||||||
|
|
||||||
toPipe.readSide.close();
|
|
||||||
fromPipe.writeSide.close();
|
|
||||||
errorPipe.writeSide.close();
|
|
||||||
|
|
||||||
/* The substituter may exit right away if it's disabled in any way
|
|
||||||
(e.g. copy-from-other-stores.pl will exit if no other stores
|
|
||||||
are configured). */
|
|
||||||
try {
|
|
||||||
getLineFromSubstituter(run);
|
|
||||||
} catch (EndOfFile & e) {
|
|
||||||
run.to.close();
|
|
||||||
run.from.close();
|
|
||||||
run.error.close();
|
|
||||||
run.disabled = true;
|
|
||||||
if (run.pid.wait(true) != 0) throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Read a line from the substituter's stdout, while also processing
|
|
||||||
its stderr. */
|
|
||||||
string LocalStore::getLineFromSubstituter(RunningSubstituter & run)
|
|
||||||
{
|
|
||||||
string res, err;
|
|
||||||
|
|
||||||
/* We might have stdout data left over from the last time. */
|
|
||||||
if (run.fromBuf.hasData()) goto haveData;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
checkInterrupt();
|
|
||||||
|
|
||||||
fd_set fds;
|
|
||||||
FD_ZERO(&fds);
|
|
||||||
FD_SET(run.from, &fds);
|
|
||||||
FD_SET(run.error, &fds);
|
|
||||||
|
|
||||||
/* Wait for data to appear on the substituter's stdout or
|
|
||||||
stderr. */
|
|
||||||
if (select(run.from > run.error ? run.from + 1 : run.error + 1, &fds, 0, 0, 0) == -1) {
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
throw SysError("waiting for input from the substituter");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Completely drain stderr before dealing with stdout. */
|
|
||||||
if (FD_ISSET(run.error, &fds)) {
|
|
||||||
char buf[4096];
|
|
||||||
ssize_t n = read(run.error, (unsigned char *) buf, sizeof(buf));
|
|
||||||
if (n == -1) {
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
throw SysError("reading from substituter's stderr");
|
|
||||||
}
|
|
||||||
if (n == 0) throw EndOfFile(format("substituter ‘%1%’ died unexpectedly") % run.program);
|
|
||||||
err.append(buf, n);
|
|
||||||
string::size_type p;
|
|
||||||
while ((p = err.find('\n')) != string::npos) {
|
|
||||||
printMsg(lvlError, run.program + ": " + string(err, 0, p));
|
|
||||||
err = string(err, p + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read from stdout until we get a newline or the buffer is empty. */
|
|
||||||
else if (run.fromBuf.hasData() || FD_ISSET(run.from, &fds)) {
|
|
||||||
haveData:
|
|
||||||
do {
|
|
||||||
unsigned char c;
|
|
||||||
run.fromBuf(&c, 1);
|
|
||||||
if (c == '\n') {
|
|
||||||
if (!err.empty()) printMsg(lvlError, run.program + ": " + err);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
res += c;
|
|
||||||
} while (run.fromBuf.hasData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<class T> T LocalStore::getIntLineFromSubstituter(RunningSubstituter & run)
|
|
||||||
{
|
|
||||||
string s = getLineFromSubstituter(run);
|
|
||||||
T res;
|
|
||||||
if (!string2Int(s, res)) throw Error("integer expected from stream");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PathSet LocalStore::querySubstitutablePaths(const PathSet & paths)
|
PathSet LocalStore::querySubstitutablePaths(const PathSet & paths)
|
||||||
{
|
{
|
||||||
auto state(_state.lock());
|
|
||||||
|
|
||||||
PathSet res;
|
PathSet res;
|
||||||
for (auto & i : settings.substituters) {
|
for (auto & sub : getDefaultSubstituters()) {
|
||||||
if (res.size() == paths.size()) break;
|
for (auto & path : paths) {
|
||||||
RunningSubstituter & run(state->runningSubstituters[i]);
|
if (res.count(path)) continue;
|
||||||
startSubstituter(i, run);
|
debug(format("checking substituter ‘%s’ for path ‘%s’")
|
||||||
if (run.disabled) continue;
|
% sub->getUri() % path);
|
||||||
string s = "have ";
|
if (sub->isValidPath(path))
|
||||||
for (auto & j : paths)
|
res.insert(path);
|
||||||
if (res.find(j) == res.end()) { s += j; s += " "; }
|
|
||||||
writeLine(run.to, s);
|
|
||||||
while (true) {
|
|
||||||
/* FIXME: we only read stderr when an error occurs, so
|
|
||||||
substituters should only write (short) messages to
|
|
||||||
stderr when they fail. I.e. they shouldn't write debug
|
|
||||||
output. */
|
|
||||||
Path path = getLineFromSubstituter(run);
|
|
||||||
if (path == "") break;
|
|
||||||
res.insert(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::querySubstitutablePathInfos(const Path & substituter,
|
|
||||||
PathSet & paths, SubstitutablePathInfos & infos)
|
|
||||||
{
|
|
||||||
auto state(_state.lock());
|
|
||||||
|
|
||||||
RunningSubstituter & run(state->runningSubstituters[substituter]);
|
|
||||||
startSubstituter(substituter, run);
|
|
||||||
if (run.disabled) return;
|
|
||||||
|
|
||||||
string s = "info ";
|
|
||||||
for (auto & i : paths)
|
|
||||||
if (infos.find(i) == infos.end()) { s += i; s += " "; }
|
|
||||||
writeLine(run.to, s);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
Path path = getLineFromSubstituter(run);
|
|
||||||
if (path == "") break;
|
|
||||||
if (paths.find(path) == paths.end())
|
|
||||||
throw Error(format("got unexpected path ‘%1%’ from substituter") % path);
|
|
||||||
paths.erase(path);
|
|
||||||
SubstitutablePathInfo & info(infos[path]);
|
|
||||||
info.deriver = getLineFromSubstituter(run);
|
|
||||||
if (info.deriver != "") assertStorePath(info.deriver);
|
|
||||||
int nrRefs = getIntLineFromSubstituter<int>(run);
|
|
||||||
while (nrRefs--) {
|
|
||||||
Path p = getLineFromSubstituter(run);
|
|
||||||
assertStorePath(p);
|
|
||||||
info.references.insert(p);
|
|
||||||
}
|
|
||||||
info.downloadSize = getIntLineFromSubstituter<long long>(run);
|
|
||||||
info.narSize = getIntLineFromSubstituter<long long>(run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::querySubstitutablePathInfos(const PathSet & paths,
|
void LocalStore::querySubstitutablePathInfos(const PathSet & paths,
|
||||||
SubstitutablePathInfos & infos)
|
SubstitutablePathInfos & infos)
|
||||||
{
|
{
|
||||||
PathSet todo = paths;
|
for (auto & sub : getDefaultSubstituters()) {
|
||||||
for (auto & i : settings.substituters) {
|
for (auto & path : paths) {
|
||||||
if (todo.empty()) break;
|
if (infos.count(path)) continue;
|
||||||
querySubstitutablePathInfos(i, todo, infos);
|
debug(format("checking substituter ‘%s’ for path ‘%s’")
|
||||||
|
% sub->getUri() % path);
|
||||||
|
try {
|
||||||
|
auto info = sub->queryPathInfo(path);
|
||||||
|
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
|
||||||
|
std::shared_ptr<const ValidPathInfo>(info));
|
||||||
|
infos[path] = SubstitutablePathInfo{
|
||||||
|
info->deriver,
|
||||||
|
info->references,
|
||||||
|
narInfo ? narInfo->fileSize : 0,
|
||||||
|
info->narSize};
|
||||||
|
} catch (InvalidPath) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,17 +40,6 @@ struct OptimiseStats
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct RunningSubstituter
|
|
||||||
{
|
|
||||||
Path program;
|
|
||||||
Pid pid;
|
|
||||||
AutoCloseFD to, from, error;
|
|
||||||
FdSource fromBuf;
|
|
||||||
bool disabled;
|
|
||||||
RunningSubstituter() : disabled(false) { };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class LocalStore : public LocalFSStore
|
class LocalStore : public LocalFSStore
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -80,10 +69,6 @@ private:
|
||||||
/* The file to which we write our temporary roots. */
|
/* The file to which we write our temporary roots. */
|
||||||
Path fnTempRoots;
|
Path fnTempRoots;
|
||||||
AutoCloseFD fdTempRoots;
|
AutoCloseFD fdTempRoots;
|
||||||
|
|
||||||
typedef std::map<Path, RunningSubstituter> RunningSubstituters;
|
|
||||||
RunningSubstituters runningSubstituters;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Sync<State, std::recursive_mutex> _state;
|
Sync<State, std::recursive_mutex> _state;
|
||||||
|
@ -122,9 +107,6 @@ public:
|
||||||
|
|
||||||
PathSet querySubstitutablePaths(const PathSet & paths) override;
|
PathSet querySubstitutablePaths(const PathSet & paths) override;
|
||||||
|
|
||||||
void querySubstitutablePathInfos(const Path & substituter,
|
|
||||||
PathSet & paths, SubstitutablePathInfos & infos);
|
|
||||||
|
|
||||||
void querySubstitutablePathInfos(const PathSet & paths,
|
void querySubstitutablePathInfos(const PathSet & paths,
|
||||||
SubstitutablePathInfos & infos) override;
|
SubstitutablePathInfos & infos) override;
|
||||||
|
|
||||||
|
@ -192,8 +174,6 @@ public:
|
||||||
a substituter (if available). */
|
a substituter (if available). */
|
||||||
void repairPath(const Path & path);
|
void repairPath(const Path & path);
|
||||||
|
|
||||||
void setSubstituterEnv();
|
|
||||||
|
|
||||||
void addSignatures(const Path & storePath, const StringSet & sigs) override;
|
void addSignatures(const Path & storePath, const StringSet & sigs) override;
|
||||||
|
|
||||||
static bool haveWriteAccess();
|
static bool haveWriteAccess();
|
||||||
|
@ -246,13 +226,6 @@ private:
|
||||||
|
|
||||||
void removeUnusedLinks(const GCState & state);
|
void removeUnusedLinks(const GCState & state);
|
||||||
|
|
||||||
void startSubstituter(const Path & substituter,
|
|
||||||
RunningSubstituter & runningSubstituter);
|
|
||||||
|
|
||||||
string getLineFromSubstituter(RunningSubstituter & run);
|
|
||||||
|
|
||||||
template<class T> T getIntLineFromSubstituter(RunningSubstituter & run);
|
|
||||||
|
|
||||||
Path createTempDirInStore();
|
Path createTempDirInStore();
|
||||||
|
|
||||||
Path importPath(bool requireSignature, Source & source);
|
Path importPath(bool requireSignature, Source & source);
|
||||||
|
|
|
@ -8,7 +8,7 @@ libstore_SOURCES := $(wildcard $(d)/*.cc)
|
||||||
|
|
||||||
libstore_LIBS = libutil libformat
|
libstore_LIBS = libutil libformat
|
||||||
|
|
||||||
libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) -laws-cpp-sdk-s3 -laws-cpp-sdk-core
|
libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) -laws-cpp-sdk-s3 -laws-cpp-sdk-core -pthread
|
||||||
|
|
||||||
ifeq ($(OS), SunOS)
|
ifeq ($(OS), SunOS)
|
||||||
libstore_LDFLAGS += -lsocket
|
libstore_LDFLAGS += -lsocket
|
||||||
|
|
|
@ -501,4 +501,39 @@ static RegisterStoreImplementation regStore([](const std::string & uri) -> std::
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
std::list<ref<Store>> getDefaultSubstituters()
|
||||||
|
{
|
||||||
|
struct State {
|
||||||
|
bool done = false;
|
||||||
|
std::list<ref<Store>> stores;
|
||||||
|
};
|
||||||
|
static Sync<State> state_;
|
||||||
|
|
||||||
|
auto state(state_.lock());
|
||||||
|
|
||||||
|
if (state->done) return state->stores;
|
||||||
|
|
||||||
|
StringSet done;
|
||||||
|
|
||||||
|
auto addStore = [&](const std::string & uri) {
|
||||||
|
if (done.count(uri)) return;
|
||||||
|
done.insert(uri);
|
||||||
|
state->stores.push_back(openStoreAt(uri));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto uri : settings.get("substituters", Strings()))
|
||||||
|
addStore(uri);
|
||||||
|
|
||||||
|
for (auto uri : settings.get("binary-caches", Strings()))
|
||||||
|
addStore(uri);
|
||||||
|
|
||||||
|
for (auto uri : settings.get("extra-binary-caches", Strings()))
|
||||||
|
addStore(uri);
|
||||||
|
|
||||||
|
state->done = true;
|
||||||
|
|
||||||
|
return state->stores;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -533,6 +533,12 @@ ref<Store> openLocalBinaryCacheStore(std::shared_ptr<Store> localStore,
|
||||||
const Path & secretKeyFile, const Path & binaryCacheDir);
|
const Path & secretKeyFile, const Path & binaryCacheDir);
|
||||||
|
|
||||||
|
|
||||||
|
/* Return the default substituter stores, defined by the
|
||||||
|
‘substituters’ option and various legacy options like
|
||||||
|
‘binary-caches’. */
|
||||||
|
std::list<ref<Store>> getDefaultSubstituters();
|
||||||
|
|
||||||
|
|
||||||
/* Store implementation registration. */
|
/* Store implementation registration. */
|
||||||
typedef std::function<std::shared_ptr<Store>(const std::string & uri)> OpenStore;
|
typedef std::function<std::shared_ptr<Store>(const std::string & uri)> OpenStore;
|
||||||
|
|
||||||
|
|
12
src/libutil/finally.hh
Normal file
12
src/libutil/finally.hh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* A trivial class to run a function at the end of a scope. */
|
||||||
|
class Finally
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::function<void()> fun;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Finally(std::function<void()> fun) : fun(fun) { }
|
||||||
|
~Finally() { fun(); }
|
||||||
|
};
|
|
@ -1,20 +0,0 @@
|
||||||
source common.sh
|
|
||||||
|
|
||||||
clearStore
|
|
||||||
|
|
||||||
drvPath=$(nix-instantiate simple.nix)
|
|
||||||
echo "derivation is $drvPath"
|
|
||||||
|
|
||||||
outPath=$(nix-store -q --fallback "$drvPath")
|
|
||||||
echo "output path is $outPath"
|
|
||||||
|
|
||||||
# Build with a substitute that fails. This should fail.
|
|
||||||
export NIX_SUBSTITUTERS=$(pwd)/substituter2.sh
|
|
||||||
if nix-store -r "$drvPath"; then echo unexpected fallback; exit 1; fi
|
|
||||||
|
|
||||||
# Build with a substitute that fails. This should fall back to a source build.
|
|
||||||
export NIX_SUBSTITUTERS=$(pwd)/substituter2.sh
|
|
||||||
nix-store -r --fallback "$drvPath"
|
|
||||||
|
|
||||||
text=$(cat "$outPath"/hello)
|
|
||||||
if test "$text" != "Hello World!"; then exit 1; fi
|
|
|
@ -3,8 +3,7 @@ check:
|
||||||
|
|
||||||
nix_tests = \
|
nix_tests = \
|
||||||
init.sh hash.sh lang.sh add.sh simple.sh dependencies.sh \
|
init.sh hash.sh lang.sh add.sh simple.sh dependencies.sh \
|
||||||
build-hook.sh substitutes.sh substitutes2.sh \
|
build-hook.sh nix-push.sh gc.sh gc-concurrent.sh \
|
||||||
fallback.sh nix-push.sh gc.sh gc-concurrent.sh \
|
|
||||||
referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \
|
referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \
|
||||||
gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
|
gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
|
||||||
remote-store.sh export.sh export-graph.sh \
|
remote-store.sh export.sh export-graph.sh \
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
#! /bin/sh -e
|
|
||||||
echo
|
|
||||||
echo substituter args: $* >&2
|
|
||||||
|
|
||||||
if test $1 = "--query"; then
|
|
||||||
while read cmd args; do
|
|
||||||
echo "CMD = $cmd, ARGS = $args" >&2
|
|
||||||
if test "$cmd" = "have"; then
|
|
||||||
for path in $args; do
|
|
||||||
read path
|
|
||||||
if grep -q "$path" $TEST_ROOT/sub-paths; then
|
|
||||||
echo $path
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
elif test "$cmd" = "info"; then
|
|
||||||
for path in $args; do
|
|
||||||
echo $path
|
|
||||||
echo "" # deriver
|
|
||||||
echo 0 # nr of refs
|
|
||||||
echo $((1 * 1024 * 1024)) # download size
|
|
||||||
echo $((2 * 1024 * 1024)) # nar size
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
echo "bad command $cmd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
elif test $1 = "--substitute"; then
|
|
||||||
mkdir $2
|
|
||||||
echo "Hallo Wereld" > $2/hello
|
|
||||||
echo # no expected hash
|
|
||||||
else
|
|
||||||
echo "unknown substituter operation"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,33 +0,0 @@
|
||||||
#! /bin/sh -e
|
|
||||||
echo
|
|
||||||
echo substituter2 args: $* >&2
|
|
||||||
|
|
||||||
if test $1 = "--query"; then
|
|
||||||
while read cmd args; do
|
|
||||||
if test "$cmd" = have; then
|
|
||||||
for path in $args; do
|
|
||||||
if grep -q "$path" $TEST_ROOT/sub-paths; then
|
|
||||||
echo $path
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
elif test "$cmd" = info; then
|
|
||||||
for path in $args; do
|
|
||||||
echo $path
|
|
||||||
echo "" # deriver
|
|
||||||
echo 0 # nr of refs
|
|
||||||
echo 0 # download size
|
|
||||||
echo 0 # nar size
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
else
|
|
||||||
echo "bad command $cmd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
elif test $1 = "--substitute"; then
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "unknown substituter operation"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,22 +0,0 @@
|
||||||
source common.sh
|
|
||||||
|
|
||||||
clearStore
|
|
||||||
|
|
||||||
# Instantiate.
|
|
||||||
drvPath=$(nix-instantiate simple.nix)
|
|
||||||
echo "derivation is $drvPath"
|
|
||||||
|
|
||||||
# Find the output path.
|
|
||||||
outPath=$(nix-store -qvv "$drvPath")
|
|
||||||
echo "output path is $outPath"
|
|
||||||
|
|
||||||
echo $outPath > $TEST_ROOT/sub-paths
|
|
||||||
|
|
||||||
export NIX_SUBSTITUTERS=$(pwd)/substituter.sh
|
|
||||||
|
|
||||||
nix-store -r "$drvPath" --dry-run 2>&1 | grep -q "1.00 MiB.*2.00 MiB"
|
|
||||||
|
|
||||||
nix-store -rvv "$drvPath"
|
|
||||||
|
|
||||||
text=$(cat "$outPath"/hello)
|
|
||||||
if test "$text" != "Hallo Wereld"; then echo "wrong substitute output: $text"; exit 1; fi
|
|
|
@ -1,21 +0,0 @@
|
||||||
source common.sh
|
|
||||||
|
|
||||||
clearStore
|
|
||||||
|
|
||||||
# Instantiate.
|
|
||||||
drvPath=$(nix-instantiate simple.nix)
|
|
||||||
echo "derivation is $drvPath"
|
|
||||||
|
|
||||||
# Find the output path.
|
|
||||||
outPath=$(nix-store -qvvvvv "$drvPath")
|
|
||||||
echo "output path is $outPath"
|
|
||||||
|
|
||||||
echo $outPath > $TEST_ROOT/sub-paths
|
|
||||||
|
|
||||||
# First try a substituter that fails, then one that succeeds
|
|
||||||
export NIX_SUBSTITUTERS=$(pwd)/substituter2.sh:$(pwd)/substituter.sh
|
|
||||||
|
|
||||||
nix-store -j0 -rvv "$drvPath"
|
|
||||||
|
|
||||||
text=$(cat "$outPath"/hello)
|
|
||||||
if test "$text" != "Hallo Wereld"; then echo "wrong substitute output: $text"; exit 1; fi
|
|
Loading…
Reference in a new issue