forked from lix-project/hydra
hydra-queue-runner: Write directly to a binary cache
This commit is contained in:
parent
e46acbf05b
commit
2d0dd7fb49
|
@ -2,7 +2,8 @@ bin_PROGRAMS = hydra-queue-runner
|
||||||
|
|
||||||
hydra_queue_runner_SOURCES = hydra-queue-runner.cc queue-monitor.cc dispatcher.cc \
|
hydra_queue_runner_SOURCES = hydra-queue-runner.cc queue-monitor.cc dispatcher.cc \
|
||||||
builder.cc build-result.cc build-remote.cc \
|
builder.cc build-result.cc build-remote.cc \
|
||||||
build-result.hh counter.hh pool.hh sync.hh token-server.hh state.hh db.hh
|
build-result.hh counter.hh pool.hh sync.hh token-server.hh state.hh db.hh \
|
||||||
|
local-binary-cache.hh local-binary-cache.cc
|
||||||
hydra_queue_runner_LDADD = $(NIX_LIBS) -lpqxx
|
hydra_queue_runner_LDADD = $(NIX_LIBS) -lpqxx
|
||||||
|
|
||||||
AM_CXXFLAGS = $(NIX_CFLAGS) -Wall
|
AM_CXXFLAGS = $(NIX_CFLAGS) -Wall
|
||||||
|
|
|
@ -73,14 +73,14 @@ static void openConnection(Machine::ptr machine, Path tmpDir, int stderrFD, Chil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void copyClosureTo(ref<Store> store,
|
static void copyClosureTo(ref<Store> destStore,
|
||||||
FdSource & from, FdSink & to, const PathSet & paths,
|
FdSource & from, FdSink & to, const PathSet & paths,
|
||||||
counter & bytesSent,
|
counter & bytesSent,
|
||||||
bool useSubstitutes = false)
|
bool useSubstitutes = false)
|
||||||
{
|
{
|
||||||
PathSet closure;
|
PathSet closure;
|
||||||
for (auto & path : paths)
|
for (auto & path : paths)
|
||||||
store->computeFSClosure(path, closure);
|
destStore->computeFSClosure(path, closure);
|
||||||
|
|
||||||
/* Send the "query valid paths" command with the "lock" option
|
/* Send the "query valid paths" command with the "lock" option
|
||||||
enabled. This prevents a race where the remote host
|
enabled. This prevents a race where the remote host
|
||||||
|
@ -95,7 +95,7 @@ static void copyClosureTo(ref<Store> store,
|
||||||
|
|
||||||
if (present.size() == closure.size()) return;
|
if (present.size() == closure.size()) return;
|
||||||
|
|
||||||
Paths sorted = store->topoSortPaths(closure);
|
Paths sorted = destStore->topoSortPaths(closure);
|
||||||
|
|
||||||
Paths missing;
|
Paths missing;
|
||||||
for (auto i = sorted.rbegin(); i != sorted.rend(); ++i)
|
for (auto i = sorted.rbegin(); i != sorted.rend(); ++i)
|
||||||
|
@ -104,10 +104,10 @@ static void copyClosureTo(ref<Store> store,
|
||||||
printMsg(lvlDebug, format("sending %1% missing paths") % missing.size());
|
printMsg(lvlDebug, format("sending %1% missing paths") % missing.size());
|
||||||
|
|
||||||
for (auto & p : missing)
|
for (auto & p : missing)
|
||||||
bytesSent += store->queryPathInfo(p).narSize;
|
bytesSent += destStore->queryPathInfo(p).narSize;
|
||||||
|
|
||||||
to << cmdImportPaths;
|
to << cmdImportPaths;
|
||||||
store->exportPaths(missing, false, to);
|
destStore->exportPaths(missing, false, to);
|
||||||
to.flush();
|
to.flush();
|
||||||
|
|
||||||
if (readInt(from) != 1)
|
if (readInt(from) != 1)
|
||||||
|
@ -115,19 +115,19 @@ static void copyClosureTo(ref<Store> store,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void copyClosureFrom(ref<Store> store,
|
static void copyClosureFrom(ref<Store> destStore,
|
||||||
FdSource & from, FdSink & to, const PathSet & paths, counter & bytesReceived)
|
FdSource & from, FdSink & to, const PathSet & paths, counter & bytesReceived)
|
||||||
{
|
{
|
||||||
to << cmdExportPaths << 0 << paths;
|
to << cmdExportPaths << 0 << paths;
|
||||||
to.flush();
|
to.flush();
|
||||||
store->importPaths(false, from);
|
destStore->importPaths(false, from);
|
||||||
|
|
||||||
for (auto & p : paths)
|
for (auto & p : paths)
|
||||||
bytesReceived += store->queryPathInfo(p).narSize;
|
bytesReceived += destStore->queryPathInfo(p).narSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void State::buildRemote(ref<Store> store,
|
void State::buildRemote(ref<Store> destStore,
|
||||||
Machine::ptr machine, Step::ptr step,
|
Machine::ptr machine, Step::ptr step,
|
||||||
unsigned int maxSilentTime, unsigned int buildTimeout,
|
unsigned int maxSilentTime, unsigned int buildTimeout,
|
||||||
RemoteResult & result)
|
RemoteResult & result)
|
||||||
|
@ -222,14 +222,20 @@ void State::buildRemote(ref<Store> store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure that the inputs exist in the destination store. This is
|
||||||
|
a no-op for regular stores, but for the binary cache store,
|
||||||
|
this will copy the inputs to the binary cache from the local
|
||||||
|
store. */
|
||||||
|
destStore->buildPaths(basicDrv.inputSrcs);
|
||||||
|
|
||||||
/* Copy the input closure. */
|
/* Copy the input closure. */
|
||||||
if (machine->sshName != "localhost") {
|
if (/* machine->sshName != "localhost" */ true) {
|
||||||
auto mc1 = std::make_shared<MaintainCount>(nrStepsWaiting);
|
auto mc1 = std::make_shared<MaintainCount>(nrStepsWaiting);
|
||||||
std::lock_guard<std::mutex> sendLock(machine->state->sendLock);
|
std::lock_guard<std::mutex> sendLock(machine->state->sendLock);
|
||||||
mc1.reset();
|
mc1.reset();
|
||||||
MaintainCount mc2(nrStepsCopyingTo);
|
MaintainCount mc2(nrStepsCopyingTo);
|
||||||
printMsg(lvlDebug, format("sending closure of ‘%1%’ to ‘%2%’") % step->drvPath % machine->sshName);
|
printMsg(lvlDebug, format("sending closure of ‘%1%’ to ‘%2%’") % step->drvPath % machine->sshName);
|
||||||
copyClosureTo(store, from, to, inputs, bytesSent);
|
copyClosureTo(destStore, from, to, inputs, bytesSent);
|
||||||
}
|
}
|
||||||
|
|
||||||
autoDelete.cancel();
|
autoDelete.cancel();
|
||||||
|
@ -277,13 +283,13 @@ void State::buildRemote(ref<Store> store,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy the output paths. */
|
/* Copy the output paths. */
|
||||||
if (machine->sshName != "localhost") {
|
if (/* machine->sshName != "localhost" */ true) {
|
||||||
printMsg(lvlDebug, format("copying outputs of ‘%1%’ from ‘%2%’") % step->drvPath % machine->sshName);
|
printMsg(lvlDebug, format("copying outputs of ‘%1%’ from ‘%2%’") % step->drvPath % machine->sshName);
|
||||||
PathSet outputs;
|
PathSet outputs;
|
||||||
for (auto & output : step->drv.outputs)
|
for (auto & output : step->drv.outputs)
|
||||||
outputs.insert(output.second.path);
|
outputs.insert(output.second.path);
|
||||||
MaintainCount mc(nrStepsCopyingFrom);
|
MaintainCount mc(nrStepsCopyingFrom);
|
||||||
copyClosureFrom(store, from, to, outputs, bytesReceived);
|
copyClosureFrom(destStore, from, to, outputs, bytesReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shut down the connection. */
|
/* Shut down the connection. */
|
||||||
|
|
|
@ -41,6 +41,7 @@ BuildOutput getBuildOutput(nix::ref<Store> store, const Derivation & drv)
|
||||||
/* Get build products. */
|
/* Get build products. */
|
||||||
bool explicitProducts = false;
|
bool explicitProducts = false;
|
||||||
|
|
||||||
|
#if 0
|
||||||
Regex regex(
|
Regex regex(
|
||||||
"(([a-zA-Z0-9_-]+)" // type (e.g. "doc")
|
"(([a-zA-Z0-9_-]+)" // type (e.g. "doc")
|
||||||
"[[:space:]]+"
|
"[[:space:]]+"
|
||||||
|
@ -97,6 +98,7 @@ BuildOutput getBuildOutput(nix::ref<Store> store, const Derivation & drv)
|
||||||
res.products.push_back(product);
|
res.products.push_back(product);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* If no build products were explicitly declared, then add all
|
/* If no build products were explicitly declared, then add all
|
||||||
outputs as a product of type "nix-build". */
|
outputs as a product of type "nix-build". */
|
||||||
|
@ -108,14 +110,17 @@ BuildOutput getBuildOutput(nix::ref<Store> store, const Derivation & drv)
|
||||||
product.subtype = output.first == "out" ? "" : output.first;
|
product.subtype = output.first == "out" ? "" : output.first;
|
||||||
product.name = storePathToName(product.path);
|
product.name = storePathToName(product.path);
|
||||||
|
|
||||||
|
#if 0
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(product.path.c_str(), &st))
|
if (stat(product.path.c_str(), &st))
|
||||||
throw SysError(format("getting status of ‘%1%’") % product.path);
|
throw SysError(format("getting status of ‘%1%’") % product.path);
|
||||||
if (S_ISDIR(st.st_mode))
|
if (S_ISDIR(st.st_mode))
|
||||||
|
#endif
|
||||||
res.products.push_back(product);
|
res.products.push_back(product);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
/* Get the release name from $output/nix-support/hydra-release-name. */
|
/* Get the release name from $output/nix-support/hydra-release-name. */
|
||||||
for (auto & output : outputs) {
|
for (auto & output : outputs) {
|
||||||
Path p = output + "/nix-support/hydra-release-name";
|
Path p = output + "/nix-support/hydra-release-name";
|
||||||
|
@ -139,6 +144,7 @@ BuildOutput getBuildOutput(nix::ref<Store> store, const Derivation & drv)
|
||||||
res.metrics[metric.name] = metric;
|
res.metrics[metric.name] = metric;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ void State::builder(MachineReservation::ptr reservation)
|
||||||
auto step = reservation->step;
|
auto step = reservation->step;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto store = openStore(); // FIXME: pool
|
auto destStore = getDestStore();
|
||||||
retry = doBuildStep(store, step, reservation->machine);
|
retry = doBuildStep(destStore, step, reservation->machine);
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
printMsg(lvlError, format("uncaught exception building ‘%1%’ on ‘%2%’: %3%")
|
printMsg(lvlError, format("uncaught exception building ‘%1%’ on ‘%2%’: %3%")
|
||||||
% step->drvPath % reservation->machine->sshName % e.what());
|
% step->drvPath % reservation->machine->sshName % e.what());
|
||||||
|
@ -45,7 +45,7 @@ void State::builder(MachineReservation::ptr reservation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool State::doBuildStep(nix::ref<Store> store, Step::ptr step,
|
bool State::doBuildStep(nix::ref<Store> destStore, Step::ptr step,
|
||||||
Machine::ptr machine)
|
Machine::ptr machine)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -120,13 +120,13 @@ bool State::doBuildStep(nix::ref<Store> store, Step::ptr step,
|
||||||
/* Do the build. */
|
/* Do the build. */
|
||||||
try {
|
try {
|
||||||
/* FIXME: referring builds may have conflicting timeouts. */
|
/* FIXME: referring builds may have conflicting timeouts. */
|
||||||
buildRemote(store, machine, step, build->maxSilentTime, build->buildTimeout, result);
|
buildRemote(destStore, machine, step, build->maxSilentTime, build->buildTimeout, result);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
result.status = BuildResult::MiscFailure;
|
result.status = BuildResult::MiscFailure;
|
||||||
result.errorMsg = e.msg();
|
result.errorMsg = e.msg();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success()) res = getBuildOutput(store, step->drv);
|
if (result.success()) res = getBuildOutput(destStore, step->drv);
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t stepStopTime = time(0);
|
time_t stepStopTime = time(0);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "state.hh"
|
#include "state.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
|
#include "local-binary-cache.hh"
|
||||||
|
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
@ -24,6 +25,18 @@ State::State()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ref<Store> State::getLocalStore()
|
||||||
|
{
|
||||||
|
return openStore(); // FIXME: pool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ref<Store> State::getDestStore()
|
||||||
|
{
|
||||||
|
return make_ref<LocalBinaryCache>(getLocalStore(), "/tmp/binary-cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void State::parseMachines(const std::string & contents)
|
void State::parseMachines(const std::string & contents)
|
||||||
{
|
{
|
||||||
Machines newMachines, oldMachines;
|
Machines newMachines, oldMachines;
|
||||||
|
@ -94,7 +107,8 @@ void State::monitorMachinesFile()
|
||||||
getEnv("NIX_REMOTE_SYSTEMS", pathExists(defaultMachinesFile) ? defaultMachinesFile : ""), ":");
|
getEnv("NIX_REMOTE_SYSTEMS", pathExists(defaultMachinesFile) ? defaultMachinesFile : ""), ":");
|
||||||
|
|
||||||
if (machinesFiles.empty()) {
|
if (machinesFiles.empty()) {
|
||||||
parseMachines("localhost " + settings.thisSystem
|
parseMachines("localhost " +
|
||||||
|
(settings.thisSystem == "x86_64-linux" ? "x86_64-linux,i686-linux" : settings.thisSystem)
|
||||||
+ " - " + std::to_string(settings.maxBuildJobs) + " 1");
|
+ " - " + std::to_string(settings.maxBuildJobs) + " 1");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
267
src/hydra-queue-runner/local-binary-cache.cc
Normal file
267
src/hydra-queue-runner/local-binary-cache.cc
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
#include "local-binary-cache.hh"
|
||||||
|
|
||||||
|
#include "archive.hh"
|
||||||
|
#include "derivations.hh"
|
||||||
|
#include "globals.hh"
|
||||||
|
#include "worker-protocol.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
LocalBinaryCache::LocalBinaryCache(ref<Store> localStore, const Path & binaryCacheDir)
|
||||||
|
: localStore(localStore), binaryCacheDir(binaryCacheDir)
|
||||||
|
{
|
||||||
|
createDirs(binaryCacheDir + "/nar");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path LocalBinaryCache::narInfoFileFor(const Path & storePath)
|
||||||
|
{
|
||||||
|
assertStorePath(storePath);
|
||||||
|
return binaryCacheDir + "/" + storePathToHash(storePath) + ".narinfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
void atomicWrite(const Path & path, const std::string & s)
|
||||||
|
{
|
||||||
|
Path tmp = path + ".tmp." + std::to_string(getpid());
|
||||||
|
AutoDelete del(tmp, false);
|
||||||
|
writeFile(tmp, s);
|
||||||
|
if (rename(tmp.c_str(), path.c_str()))
|
||||||
|
throw SysError(format("renaming ‘%1%’ to ‘%2%’") % tmp % path);
|
||||||
|
del.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalBinaryCache::addToCache(const ValidPathInfo & info,
|
||||||
|
const string & nar)
|
||||||
|
{
|
||||||
|
size_t narSize = nar.size();
|
||||||
|
Hash narHash = hashString(htSHA256, nar);
|
||||||
|
|
||||||
|
if (info.hash.type != htUnknown && info.hash != narHash)
|
||||||
|
throw Error(format("refusing to copy corrupted path ‘%1%’ to binary cache") % info.path);
|
||||||
|
|
||||||
|
printMsg(lvlTalkative, format("copying path ‘%1%’ (%2% bytes) to binary cache")
|
||||||
|
% info.path % narSize);
|
||||||
|
|
||||||
|
/* Atomically write the NAR file. */
|
||||||
|
string narFileRel = "nar/" + printHash(narHash) + ".nar";
|
||||||
|
Path narFile = binaryCacheDir + "/" + narFileRel;
|
||||||
|
if (!pathExists(narFile)) atomicWrite(narFile, nar);
|
||||||
|
|
||||||
|
/* Atomically write the NAR info file.*/
|
||||||
|
Path narInfoFile = narInfoFileFor(info.path);
|
||||||
|
|
||||||
|
if (!pathExists(narInfoFile)) {
|
||||||
|
|
||||||
|
Strings refs;
|
||||||
|
for (auto & r : info.references)
|
||||||
|
refs.push_back(baseNameOf(r));
|
||||||
|
|
||||||
|
std::string narInfo;
|
||||||
|
narInfo += "StorePath: " + info.path + "\n";
|
||||||
|
narInfo += "URL: " + narFileRel + "\n";
|
||||||
|
narInfo += "Compression: none\n";
|
||||||
|
narInfo += "FileHash: sha256:" + printHash(narHash) + "\n";
|
||||||
|
narInfo += "FileSize: " + std::to_string(narSize) + "\n";
|
||||||
|
narInfo += "NarHash: sha256:" + printHash(narHash) + "\n";
|
||||||
|
narInfo += "NarSize: " + std::to_string(narSize) + "\n";
|
||||||
|
narInfo += "References: " + concatStringsSep(" ", refs) + "\n";
|
||||||
|
|
||||||
|
// FIXME: add signature
|
||||||
|
|
||||||
|
atomicWrite(narInfoFile, narInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalBinaryCache::NarInfo LocalBinaryCache::readNarInfo(const Path & storePath)
|
||||||
|
{
|
||||||
|
NarInfo res;
|
||||||
|
|
||||||
|
Path narInfoFile = narInfoFileFor(storePath);
|
||||||
|
if (!pathExists(narInfoFile))
|
||||||
|
abort();
|
||||||
|
std::string narInfo = readFile(narInfoFile);
|
||||||
|
|
||||||
|
auto corrupt = [&]() {
|
||||||
|
throw Error(format("corrupt NAR info file ‘%1%’") % narInfoFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < narInfo.size()) {
|
||||||
|
|
||||||
|
size_t colon = narInfo.find(':', pos);
|
||||||
|
if (colon == std::string::npos) corrupt();
|
||||||
|
|
||||||
|
std::string name(narInfo, pos, colon - pos);
|
||||||
|
|
||||||
|
size_t eol = narInfo.find('\n', colon + 2);
|
||||||
|
if (eol == std::string::npos) corrupt();
|
||||||
|
|
||||||
|
std::string value(narInfo, colon + 2, eol - colon - 2);
|
||||||
|
|
||||||
|
if (name == "StorePath") {
|
||||||
|
res.info.path = value;
|
||||||
|
if (value != storePath) corrupt();
|
||||||
|
res.info.path = value;
|
||||||
|
}
|
||||||
|
else if (name == "References") {
|
||||||
|
auto refs = tokenizeString<Strings>(value, " ");
|
||||||
|
if (!res.info.references.empty()) corrupt();
|
||||||
|
for (auto & r : refs)
|
||||||
|
res.info.references.insert(settings.nixStore + "/" + r);
|
||||||
|
}
|
||||||
|
else if (name == "URL") {
|
||||||
|
res.narUrl = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = eol + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.info.path.empty() || res.narUrl.empty()) corrupt();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalBinaryCache::isValidPath(const Path & storePath)
|
||||||
|
{
|
||||||
|
Path narInfoFile = narInfoFileFor(storePath);
|
||||||
|
|
||||||
|
printMsg(lvlDebug, format("checking %1% -> %2%") % storePath % narInfoFile);
|
||||||
|
|
||||||
|
return pathExists(narInfoFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalBinaryCache::exportPath(const Path & storePath, bool sign, Sink & sink)
|
||||||
|
{
|
||||||
|
assert(!sign);
|
||||||
|
|
||||||
|
auto res = readNarInfo(storePath);
|
||||||
|
|
||||||
|
auto nar = readFile(binaryCacheDir + "/" + res.narUrl);
|
||||||
|
|
||||||
|
printMsg(lvlTalkative, format("exporting path ‘%1%’ (%2% bytes)") % storePath % nar.size());
|
||||||
|
|
||||||
|
assert(nar.size() % 8 == 0);
|
||||||
|
|
||||||
|
sink((unsigned char *) nar.c_str(), nar.size());
|
||||||
|
|
||||||
|
// FIXME: check integrity of NAR.
|
||||||
|
|
||||||
|
sink << exportMagic << storePath << res.info.references << res.info.deriver << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Paths LocalBinaryCache::importPaths(bool requireSignature, Source & source)
|
||||||
|
{
|
||||||
|
assert(!requireSignature);
|
||||||
|
Paths res;
|
||||||
|
while (true) {
|
||||||
|
unsigned long long n = readLongLong(source);
|
||||||
|
if (n == 0) break;
|
||||||
|
if (n != 1) throw Error("input doesn't look like something created by ‘nix-store --export’");
|
||||||
|
res.push_back(importPath(source));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TeeSource : Source
|
||||||
|
{
|
||||||
|
Source & readSource;
|
||||||
|
std::string data;
|
||||||
|
TeeSource(Source & readSource) : readSource(readSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
size_t read(unsigned char * data, size_t len)
|
||||||
|
{
|
||||||
|
size_t n = readSource.read(data, len);
|
||||||
|
this->data.append((char *) data, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NopSink : ParseSink
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
Path LocalBinaryCache::importPath(Source & source)
|
||||||
|
{
|
||||||
|
/* FIXME: some cut&paste of LocalStore::importPath(). */
|
||||||
|
|
||||||
|
/* Extract the NAR from the source. */
|
||||||
|
TeeSource tee(source);
|
||||||
|
NopSink sink;
|
||||||
|
parseDump(sink, tee);
|
||||||
|
|
||||||
|
uint32_t magic = readInt(source);
|
||||||
|
if (magic != exportMagic)
|
||||||
|
throw Error("Nix archive cannot be imported; wrong format");
|
||||||
|
|
||||||
|
ValidPathInfo info;
|
||||||
|
info.path = readStorePath(source);
|
||||||
|
|
||||||
|
info.references = readStorePaths<PathSet>(source);
|
||||||
|
|
||||||
|
readString(source); // deriver, don't care
|
||||||
|
|
||||||
|
bool haveSignature = readInt(source) == 1;
|
||||||
|
assert(!haveSignature);
|
||||||
|
|
||||||
|
addToCache(info, tee.data);
|
||||||
|
|
||||||
|
return info.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidPathInfo LocalBinaryCache::queryPathInfo(const Path & storePath)
|
||||||
|
{
|
||||||
|
return readNarInfo(storePath).info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalBinaryCache::querySubstitutablePathInfos(const PathSet & paths,
|
||||||
|
SubstitutablePathInfos & infos)
|
||||||
|
{
|
||||||
|
PathSet left;
|
||||||
|
|
||||||
|
for (auto & storePath : paths) {
|
||||||
|
if (!localStore->isValidPath(storePath)) {
|
||||||
|
left.insert(storePath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ValidPathInfo info = localStore->queryPathInfo(storePath);
|
||||||
|
SubstitutablePathInfo sub;
|
||||||
|
sub.references = info.references;
|
||||||
|
sub.downloadSize = 0;
|
||||||
|
sub.narSize = info.narSize;
|
||||||
|
infos.emplace(storePath, sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStore->querySubstitutablePathInfos(left, infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalBinaryCache::buildPaths(const PathSet & paths, BuildMode buildMode)
|
||||||
|
{
|
||||||
|
for (auto & storePath : paths) {
|
||||||
|
assert(!isDerivation(storePath));
|
||||||
|
|
||||||
|
if (isValidPath(storePath)) continue;
|
||||||
|
|
||||||
|
localStore->addTempRoot(storePath);
|
||||||
|
|
||||||
|
if (!localStore->isValidPath(storePath))
|
||||||
|
localStore->ensurePath(storePath);
|
||||||
|
|
||||||
|
ValidPathInfo info = localStore->queryPathInfo(storePath);
|
||||||
|
|
||||||
|
for (auto & ref : info.references)
|
||||||
|
if (ref != storePath)
|
||||||
|
ensurePath(ref);
|
||||||
|
|
||||||
|
StringSink sink;
|
||||||
|
dumpPath(storePath, sink);
|
||||||
|
|
||||||
|
addToCache(info, sink.s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalBinaryCache::ensurePath(const Path & path)
|
||||||
|
{
|
||||||
|
buildPaths({path});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
124
src/hydra-queue-runner/local-binary-cache.hh
Normal file
124
src/hydra-queue-runner/local-binary-cache.hh
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class LocalBinaryCache : public nix::Store
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ref<Store> localStore;
|
||||||
|
Path binaryCacheDir;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
LocalBinaryCache(ref<Store> localStore, const Path & binaryCacheDir);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Path narInfoFileFor(const Path & storePath);
|
||||||
|
|
||||||
|
void addToCache(const ValidPathInfo & info, const string & nar);
|
||||||
|
|
||||||
|
struct NarInfo
|
||||||
|
{
|
||||||
|
ValidPathInfo info;
|
||||||
|
std::string narUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
NarInfo readNarInfo(const Path & storePath);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool isValidPath(const Path & path) override;
|
||||||
|
|
||||||
|
PathSet queryValidPaths(const PathSet & paths) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
PathSet queryAllValidPaths() override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
ValidPathInfo queryPathInfo(const Path & path) override;
|
||||||
|
|
||||||
|
Hash queryPathHash(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void queryReferrers(const Path & path,
|
||||||
|
PathSet & referrers) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
Path queryDeriver(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
PathSet queryValidDerivers(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
PathSet queryDerivationOutputs(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
StringSet queryDerivationOutputNames(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
Path queryPathFromHashPart(const string & hashPart) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
PathSet querySubstitutablePaths(const PathSet & paths) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void querySubstitutablePathInfos(const PathSet & paths,
|
||||||
|
SubstitutablePathInfos & infos) override;
|
||||||
|
|
||||||
|
Path addToStore(const string & name, const Path & srcPath,
|
||||||
|
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||||
|
PathFilter & filter = defaultPathFilter, bool repair = false) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
Path addTextToStore(const string & name, const string & s,
|
||||||
|
const PathSet & references, bool repair = false) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void exportPath(const Path & path, bool sign,
|
||||||
|
Sink & sink) override;
|
||||||
|
|
||||||
|
Paths importPaths(bool requireSignature, Source & source) override;
|
||||||
|
|
||||||
|
Path importPath(Source & source);
|
||||||
|
|
||||||
|
void buildPaths(const PathSet & paths, BuildMode buildMode = bmNormal) override;
|
||||||
|
|
||||||
|
BuildResult buildDerivation(const Path & drvPath, const BasicDerivation & drv,
|
||||||
|
BuildMode buildMode = bmNormal) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void ensurePath(const Path & path) override;
|
||||||
|
|
||||||
|
void addTempRoot(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void addIndirectRoot(const Path & path) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void syncWithGC() override
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Roots findRoots() override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
void collectGarbage(const GCOptions & options, GCResults & results) override
|
||||||
|
{ abort(); }
|
||||||
|
|
||||||
|
PathSet queryFailedPaths() override
|
||||||
|
{ return PathSet(); }
|
||||||
|
|
||||||
|
void clearFailedPaths(const PathSet & paths) override
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void optimiseStore() override
|
||||||
|
{ }
|
||||||
|
|
||||||
|
bool verifyStore(bool checkContents, bool repair) override
|
||||||
|
{ return true; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -30,12 +30,13 @@ void State::queueMonitorLoop()
|
||||||
receiver buildsBumped(*conn, "builds_bumped");
|
receiver buildsBumped(*conn, "builds_bumped");
|
||||||
receiver jobsetSharesChanged(*conn, "jobset_shares_changed");
|
receiver jobsetSharesChanged(*conn, "jobset_shares_changed");
|
||||||
|
|
||||||
auto store = openStore(); // FIXME: pool
|
auto localStore = getLocalStore();
|
||||||
|
auto destStore = getDestStore();
|
||||||
|
|
||||||
unsigned int lastBuildId = 0;
|
unsigned int lastBuildId = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
bool done = getQueuedBuilds(*conn, store, lastBuildId);
|
bool done = getQueuedBuilds(*conn, localStore, destStore, lastBuildId);
|
||||||
|
|
||||||
/* Sleep until we get notification from the database about an
|
/* Sleep until we get notification from the database about an
|
||||||
event. */
|
event. */
|
||||||
|
@ -63,7 +64,8 @@ void State::queueMonitorLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool State::getQueuedBuilds(Connection & conn, ref<Store> store, unsigned int & lastBuildId)
|
bool State::getQueuedBuilds(Connection & conn, ref<Store> localStore,
|
||||||
|
ref<Store> destStore, unsigned int & lastBuildId)
|
||||||
{
|
{
|
||||||
printMsg(lvlInfo, format("checking the queue for builds > %1%...") % lastBuildId);
|
printMsg(lvlInfo, format("checking the queue for builds > %1%...") % lastBuildId);
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ bool State::getQueuedBuilds(Connection & conn, ref<Store> store, unsigned int &
|
||||||
nrAdded++;
|
nrAdded++;
|
||||||
newBuildsByID.erase(build->id);
|
newBuildsByID.erase(build->id);
|
||||||
|
|
||||||
if (!store->isValidPath(build->drvPath)) {
|
if (!localStore->isValidPath(build->drvPath)) {
|
||||||
/* Derivation has been GC'ed prematurely. */
|
/* Derivation has been GC'ed prematurely. */
|
||||||
printMsg(lvlError, format("aborting GC'ed build %1%") % build->id);
|
printMsg(lvlError, format("aborting GC'ed build %1%") % build->id);
|
||||||
if (!build->finishedInDB) {
|
if (!build->finishedInDB) {
|
||||||
|
@ -138,7 +140,8 @@ bool State::getQueuedBuilds(Connection & conn, ref<Store> store, unsigned int &
|
||||||
|
|
||||||
std::set<Step::ptr> newSteps;
|
std::set<Step::ptr> newSteps;
|
||||||
std::set<Path> finishedDrvs; // FIXME: re-use?
|
std::set<Path> finishedDrvs; // FIXME: re-use?
|
||||||
Step::ptr step = createStep(store, conn, build, build->drvPath, build, 0, finishedDrvs, newSteps, newRunnable);
|
Step::ptr step = createStep(destStore, conn, build, build->drvPath,
|
||||||
|
build, 0, finishedDrvs, newSteps, newRunnable);
|
||||||
|
|
||||||
/* Some of the new steps may be the top level of builds that
|
/* Some of the new steps may be the top level of builds that
|
||||||
we haven't processed yet. So do them now. This ensures that
|
we haven't processed yet. So do them now. This ensures that
|
||||||
|
@ -156,7 +159,7 @@ bool State::getQueuedBuilds(Connection & conn, ref<Store> store, unsigned int &
|
||||||
all valid. So we mark this as a finished, cached build. */
|
all valid. So we mark this as a finished, cached build. */
|
||||||
if (!step) {
|
if (!step) {
|
||||||
Derivation drv = readDerivation(build->drvPath);
|
Derivation drv = readDerivation(build->drvPath);
|
||||||
BuildOutput res = getBuildOutput(store, drv);
|
BuildOutput res = getBuildOutput(destStore, drv);
|
||||||
|
|
||||||
pqxx::work txn(conn);
|
pqxx::work txn(conn);
|
||||||
time_t now = time(0);
|
time_t now = time(0);
|
||||||
|
@ -314,7 +317,7 @@ void State::processQueueChange(Connection & conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Step::ptr State::createStep(ref<Store> store,
|
Step::ptr State::createStep(ref<Store> destStore,
|
||||||
Connection & conn, Build::ptr build, const Path & drvPath,
|
Connection & conn, Build::ptr build, const Path & drvPath,
|
||||||
Build::ptr referringBuild, Step::ptr referringStep, std::set<Path> & finishedDrvs,
|
Build::ptr referringBuild, Step::ptr referringStep, std::set<Path> & finishedDrvs,
|
||||||
std::set<Step::ptr> & newSteps, std::set<Step::ptr> & newRunnable)
|
std::set<Step::ptr> & newSteps, std::set<Step::ptr> & newRunnable)
|
||||||
|
@ -394,7 +397,7 @@ Step::ptr State::createStep(ref<Store> store,
|
||||||
DerivationOutputs missing;
|
DerivationOutputs missing;
|
||||||
PathSet missingPaths;
|
PathSet missingPaths;
|
||||||
for (auto & i : step->drv.outputs)
|
for (auto & i : step->drv.outputs)
|
||||||
if (!store->isValidPath(i.second.path)) {
|
if (!destStore->isValidPath(i.second.path)) {
|
||||||
valid = false;
|
valid = false;
|
||||||
missing[i.first] = i.second;
|
missing[i.first] = i.second;
|
||||||
missingPaths.insert(i.second.path);
|
missingPaths.insert(i.second.path);
|
||||||
|
@ -406,7 +409,7 @@ Step::ptr State::createStep(ref<Store> store,
|
||||||
assert(missing.size() == missingPaths.size());
|
assert(missing.size() == missingPaths.size());
|
||||||
if (!missing.empty() && settings.useSubstitutes) {
|
if (!missing.empty() && settings.useSubstitutes) {
|
||||||
SubstitutablePathInfos infos;
|
SubstitutablePathInfos infos;
|
||||||
store->querySubstitutablePathInfos(missingPaths, infos);
|
destStore->querySubstitutablePathInfos(missingPaths, infos); // FIXME
|
||||||
if (infos.size() == missingPaths.size()) {
|
if (infos.size() == missingPaths.size()) {
|
||||||
valid = true;
|
valid = true;
|
||||||
for (auto & i : missing) {
|
for (auto & i : missing) {
|
||||||
|
@ -414,7 +417,7 @@ Step::ptr State::createStep(ref<Store> store,
|
||||||
printMsg(lvlInfo, format("substituting output ‘%1%’ of ‘%2%’") % i.second.path % drvPath);
|
printMsg(lvlInfo, format("substituting output ‘%1%’ of ‘%2%’") % i.second.path % drvPath);
|
||||||
|
|
||||||
time_t startTime = time(0);
|
time_t startTime = time(0);
|
||||||
store->ensurePath(i.second.path);
|
destStore->ensurePath(i.second.path);
|
||||||
time_t stopTime = time(0);
|
time_t stopTime = time(0);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -443,7 +446,7 @@ Step::ptr State::createStep(ref<Store> store,
|
||||||
|
|
||||||
/* Create steps for the dependencies. */
|
/* Create steps for the dependencies. */
|
||||||
for (auto & i : step->drv.inputDrvs) {
|
for (auto & i : step->drv.inputDrvs) {
|
||||||
auto dep = createStep(store, conn, build, i.first, 0, step, finishedDrvs, newSteps, newRunnable);
|
auto dep = createStep(destStore, conn, build, i.first, 0, step, finishedDrvs, newSteps, newRunnable);
|
||||||
if (dep) {
|
if (dep) {
|
||||||
auto step_(step->state.lock());
|
auto step_(step->state.lock());
|
||||||
step_->deps.insert(dep);
|
step_->deps.insert(dep);
|
||||||
|
|
|
@ -350,6 +350,13 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/* Return a store object that can access derivations produced by
|
||||||
|
hydra-evaluator. */
|
||||||
|
nix::ref<nix::Store> getLocalStore();
|
||||||
|
|
||||||
|
/* Return a store object to store build results. */
|
||||||
|
nix::ref<nix::Store> getDestStore();
|
||||||
|
|
||||||
void clearBusy(Connection & conn, time_t stopTime);
|
void clearBusy(Connection & conn, time_t stopTime);
|
||||||
|
|
||||||
void parseMachines(const std::string & contents);
|
void parseMachines(const std::string & contents);
|
||||||
|
@ -377,7 +384,8 @@ private:
|
||||||
void queueMonitorLoop();
|
void queueMonitorLoop();
|
||||||
|
|
||||||
/* Check the queue for new builds. */
|
/* Check the queue for new builds. */
|
||||||
bool getQueuedBuilds(Connection & conn, nix::ref<nix::Store> store, unsigned int & lastBuildId);
|
bool getQueuedBuilds(Connection & conn, nix::ref<nix::Store> localStore,
|
||||||
|
nix::ref<nix::Store> destStore, unsigned int & lastBuildId);
|
||||||
|
|
||||||
/* Handle cancellation, deletion and priority bumps. */
|
/* Handle cancellation, deletion and priority bumps. */
|
||||||
void processQueueChange(Connection & conn);
|
void processQueueChange(Connection & conn);
|
||||||
|
@ -405,10 +413,10 @@ private:
|
||||||
|
|
||||||
/* Perform the given build step. Return true if the step is to be
|
/* Perform the given build step. Return true if the step is to be
|
||||||
retried. */
|
retried. */
|
||||||
bool doBuildStep(nix::ref<nix::Store> store, Step::ptr step,
|
bool doBuildStep(nix::ref<nix::Store> destStore, Step::ptr step,
|
||||||
Machine::ptr machine);
|
Machine::ptr machine);
|
||||||
|
|
||||||
void buildRemote(nix::ref<nix::Store> store,
|
void buildRemote(nix::ref<nix::Store> destStore,
|
||||||
Machine::ptr machine, Step::ptr step,
|
Machine::ptr machine, Step::ptr step,
|
||||||
unsigned int maxSilentTime, unsigned int buildTimeout,
|
unsigned int maxSilentTime, unsigned int buildTimeout,
|
||||||
RemoteResult & result);
|
RemoteResult & result);
|
||||||
|
|
Loading…
Reference in a new issue