forked from lix-project/lix
8d4268d190
Changes: * The divider lines are gone. These were in practice a bit confusing, in particular with --show-trace or --keep-going, since then there were multiple lines, suggesting a start/end which wasn't the case. * Instead, multi-line error messages are now indented to align with the prefix (e.g. "error: "). * The 'description' field is gone since we weren't really using it. * 'hint' is renamed to 'msg' since it really wasn't a hint. * The error is now printed *before* the location info. * The 'name' field is no longer printed since most of the time it wasn't very useful since it was just the name of the exception (like EvalError). Ideally in the future this would be a unique, easily googleable error ID (like rustc). * "trace:" is now just "…". This assumes error contexts start with something like "while doing X". Example before: error: --- AssertionError ---------------------------------------------------------------------------------------- nix at: (7:7) in file: /home/eelco/Dev/nixpkgs/pkgs/applications/misc/hello/default.nix 6| 7| x = assert false; 1; | ^ 8| assertion 'false' failed ----------------------------------------------------- show-trace ----------------------------------------------------- trace: while evaluating the attribute 'x' of the derivation 'hello-2.10' at: (192:11) in file: /home/eelco/Dev/nixpkgs/pkgs/stdenv/generic/make-derivation.nix 191| // (lib.optionalAttrs (!(attrs ? name) && attrs ? pname && attrs ? version)) { 192| name = "${attrs.pname}-${attrs.version}"; | ^ 193| } // (lib.optionalAttrs (stdenv.hostPlatform != stdenv.buildPlatform && !dontAddHostSuffix && (attrs ? name || (attrs ? pname && attrs ? version)))) { Example after: error: assertion 'false' failed at: (7:7) in file: /home/eelco/Dev/nixpkgs/pkgs/applications/misc/hello/default.nix 6| 7| x = assert false; 1; | ^ 8| … while evaluating the attribute 'x' of the derivation 'hello-2.10' at: (192:11) in file: /home/eelco/Dev/nixpkgs/pkgs/stdenv/generic/make-derivation.nix 191| // (lib.optionalAttrs (!(attrs ? name) && attrs ? pname && attrs ? version)) { 192| name = "${attrs.pname}-${attrs.version}"; | ^ 193| } // (lib.optionalAttrs (stdenv.hostPlatform != stdenv.buildPlatform && !dontAddHostSuffix && (attrs ? name || (attrs ? pname && attrs ? version)))) {
295 lines
10 KiB
C++
295 lines
10 KiB
C++
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <memory>
|
|
#include <tuple>
|
|
#include <iomanip>
|
|
#if __APPLE__
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "machines.hh"
|
|
#include "shared.hh"
|
|
#include "pathlocks.hh"
|
|
#include "globals.hh"
|
|
#include "serialise.hh"
|
|
#include "store-api.hh"
|
|
#include "derivations.hh"
|
|
#include "local-store.hh"
|
|
#include "../nix/legacy.hh"
|
|
|
|
using namespace nix;
|
|
using std::cin;
|
|
|
|
static void handleAlarm(int sig) {
|
|
}
|
|
|
|
std::string escapeUri(std::string uri)
|
|
{
|
|
std::replace(uri.begin(), uri.end(), '/', '_');
|
|
return uri;
|
|
}
|
|
|
|
static string currentLoad;
|
|
|
|
static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
|
|
{
|
|
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
|
|
}
|
|
|
|
static bool allSupportedLocally(Store & store, const std::set<std::string>& requiredFeatures) {
|
|
for (auto & feature : requiredFeatures)
|
|
if (!store.systemFeatures.get().count(feature)) return false;
|
|
return true;
|
|
}
|
|
|
|
static int main_build_remote(int argc, char * * argv)
|
|
{
|
|
{
|
|
logger = makeJSONLogger(*logger);
|
|
|
|
/* Ensure we don't get any SSH passphrase or host key popups. */
|
|
unsetenv("DISPLAY");
|
|
unsetenv("SSH_ASKPASS");
|
|
|
|
if (argc != 2)
|
|
throw UsageError("called without required arguments");
|
|
|
|
verbosity = (Verbosity) std::stoll(argv[1]);
|
|
|
|
FdSource source(STDIN_FILENO);
|
|
|
|
/* Read the parent's settings. */
|
|
while (readInt(source)) {
|
|
auto name = readString(source);
|
|
auto value = readString(source);
|
|
settings.set(name, value);
|
|
}
|
|
|
|
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
|
|
|
|
initPlugins();
|
|
|
|
auto store = openStore().cast<LocalStore>();
|
|
|
|
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
|
|
that gets cleared on reboot, but it wouldn't work on macOS. */
|
|
currentLoad = store->stateDir + "/current-load";
|
|
|
|
std::shared_ptr<Store> sshStore;
|
|
AutoCloseFD bestSlotLock;
|
|
|
|
auto machines = getMachines();
|
|
debug("got %d remote builders", machines.size());
|
|
|
|
if (machines.empty()) {
|
|
std::cerr << "# decline-permanently\n";
|
|
return 0;
|
|
}
|
|
|
|
std::optional<StorePath> drvPath;
|
|
string storeUri;
|
|
|
|
while (true) {
|
|
|
|
try {
|
|
auto s = readString(source);
|
|
if (s != "try") return 0;
|
|
} catch (EndOfFile &) { return 0; }
|
|
|
|
auto amWilling = readInt(source);
|
|
auto neededSystem = readString(source);
|
|
drvPath = store->parseStorePath(readString(source));
|
|
auto requiredFeatures = readStrings<std::set<std::string>>(source);
|
|
|
|
auto canBuildLocally = amWilling
|
|
&& ( neededSystem == settings.thisSystem
|
|
|| settings.extraPlatforms.get().count(neededSystem) > 0)
|
|
&& allSupportedLocally(*store, requiredFeatures);
|
|
|
|
/* Error ignored here, will be caught later */
|
|
mkdir(currentLoad.c_str(), 0777);
|
|
|
|
while (true) {
|
|
bestSlotLock = -1;
|
|
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
|
|
lockFile(lock.get(), ltWrite, true);
|
|
|
|
bool rightType = false;
|
|
|
|
Machine * bestMachine = nullptr;
|
|
uint64_t bestLoad = 0;
|
|
for (auto & m : machines) {
|
|
debug("considering building on remote machine '%s'", m.storeUri);
|
|
|
|
if (m.enabled && std::find(m.systemTypes.begin(),
|
|
m.systemTypes.end(),
|
|
neededSystem) != m.systemTypes.end() &&
|
|
m.allSupported(requiredFeatures) &&
|
|
m.mandatoryMet(requiredFeatures)) {
|
|
rightType = true;
|
|
AutoCloseFD free;
|
|
uint64_t load = 0;
|
|
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
|
|
auto slotLock = openSlotLock(m, slot);
|
|
if (lockFile(slotLock.get(), ltWrite, false)) {
|
|
if (!free) {
|
|
free = std::move(slotLock);
|
|
}
|
|
} else {
|
|
++load;
|
|
}
|
|
}
|
|
if (!free) {
|
|
continue;
|
|
}
|
|
bool best = false;
|
|
if (!bestSlotLock) {
|
|
best = true;
|
|
} else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) {
|
|
best = true;
|
|
} else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) {
|
|
if (m.speedFactor > bestMachine->speedFactor) {
|
|
best = true;
|
|
} else if (m.speedFactor == bestMachine->speedFactor) {
|
|
if (load < bestLoad) {
|
|
best = true;
|
|
}
|
|
}
|
|
}
|
|
if (best) {
|
|
bestLoad = load;
|
|
bestSlotLock = std::move(free);
|
|
bestMachine = &m;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bestSlotLock) {
|
|
if (rightType && !canBuildLocally)
|
|
std::cerr << "# postpone\n";
|
|
else
|
|
{
|
|
// build the hint template.
|
|
string errorText =
|
|
"Failed to find a machine for remote build!\n"
|
|
"derivation: %s\nrequired (system, features): (%s, %s)";
|
|
errorText += "\n%s available machines:";
|
|
errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)";
|
|
|
|
for (unsigned int i = 0; i < machines.size(); ++i)
|
|
errorText += "\n(%s, %s, %s, %s)";
|
|
|
|
// add the template values.
|
|
string drvstr;
|
|
if (drvPath.has_value())
|
|
drvstr = drvPath->to_string();
|
|
else
|
|
drvstr = "<unknown>";
|
|
|
|
auto error = hintformat(errorText);
|
|
error
|
|
% drvstr
|
|
% neededSystem
|
|
% concatStringsSep<StringSet>(", ", requiredFeatures)
|
|
% machines.size();
|
|
|
|
for (auto & m : machines)
|
|
error
|
|
% concatStringsSep<vector<string>>(", ", m.systemTypes)
|
|
% m.maxJobs
|
|
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
|
|
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
|
|
|
|
printMsg(canBuildLocally ? lvlChatty : lvlWarn, error);
|
|
|
|
std::cerr << "# decline\n";
|
|
}
|
|
break;
|
|
}
|
|
|
|
#if __APPLE__
|
|
futimes(bestSlotLock.get(), NULL);
|
|
#else
|
|
futimens(bestSlotLock.get(), NULL);
|
|
#endif
|
|
|
|
lock = -1;
|
|
|
|
try {
|
|
|
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri));
|
|
|
|
sshStore = bestMachine->openStore();
|
|
sshStore->connect();
|
|
storeUri = bestMachine->storeUri;
|
|
|
|
} catch (std::exception & e) {
|
|
auto msg = chomp(drainFD(5, false));
|
|
printError("cannot build on '%s': %s%s",
|
|
bestMachine->storeUri, e.what(),
|
|
msg.empty() ? "" : ": " + msg);
|
|
bestMachine->enabled = false;
|
|
continue;
|
|
}
|
|
|
|
goto connected;
|
|
}
|
|
}
|
|
|
|
connected:
|
|
close(5);
|
|
|
|
std::cerr << "# accept\n" << storeUri << "\n";
|
|
|
|
auto inputs = readStrings<PathSet>(source);
|
|
auto outputs = readStrings<PathSet>(source);
|
|
|
|
AutoCloseFD uploadLock = openLockFile(currentLoad + "/" + escapeUri(storeUri) + ".upload-lock", true);
|
|
|
|
{
|
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
|
|
|
|
auto old = signal(SIGALRM, handleAlarm);
|
|
alarm(15 * 60);
|
|
if (!lockFile(uploadLock.get(), ltWrite, true))
|
|
printError("somebody is hogging the upload lock for '%s', continuing...");
|
|
alarm(0);
|
|
signal(SIGALRM, old);
|
|
}
|
|
|
|
auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute;
|
|
|
|
{
|
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri));
|
|
copyPaths(store, ref<Store>(sshStore), store->parseStorePathSet(inputs), NoRepair, NoCheckSigs, substitute);
|
|
}
|
|
|
|
uploadLock = -1;
|
|
|
|
auto drv = store->readDerivation(*drvPath);
|
|
drv.inputSrcs = store->parseStorePathSet(inputs);
|
|
|
|
auto result = sshStore->buildDerivation(*drvPath, drv);
|
|
|
|
if (!result.success())
|
|
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
|
|
|
|
StorePathSet missing;
|
|
for (auto & path : outputs)
|
|
if (!store->isValidPath(store->parseStorePath(path))) missing.insert(store->parseStorePath(path));
|
|
|
|
if (!missing.empty()) {
|
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri));
|
|
for (auto & i : missing)
|
|
store->locksHeld.insert(store->printStorePath(i)); /* FIXME: ugly */
|
|
copyPaths(ref<Store>(sshStore), store, missing, NoRepair, NoCheckSigs, NoSubstitute);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static RegisterLegacyCommand r_build_remote("build-remote", main_build_remote);
|