diff --git a/.clang-tidy b/.clang-tidy index fc0f2d65..df2270c0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,23 @@ Checks: - - -* - bugprone-* - performance-* - modernize-* - - cppcoreguidelines-pro-type-cstyle-cast + - readability-* + - misc-* + - portability-* + - concurrency-* + - google-* + - -google-readability-todo + + # don't find them too problematic + - -readability-identifier-length + - -readability-magic-numbers + + # maybe address this in the future + - -readability-function-cognitive-complexity + + - cppcoreguidelines-* + - -cppcoreguidelines-avoid-magic-numbers UseColor: true +CheckOptions: + misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: True diff --git a/flake.lock b/flake.lock index 2befde43..2aec5641 100644 --- a/flake.lock +++ b/flake.lock @@ -42,11 +42,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1723221148, - "narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=", + "lastModified": 1730958623, + "narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "154bcb95ad51bc257c2ce4043a725de6ca700ef6", + "rev": "85f7e662eda4fa3a995556527c87b2524b691933", "type": "github" }, "original": { diff --git a/shell.nix b/shell.nix index 8607d3f2..935f77bc 100644 --- a/shell.nix +++ b/shell.nix @@ -24,20 +24,10 @@ let in (pkgs.mkShell.override { inherit stdenv; }) { inherit (nix-eval-jobs) buildInputs; - nativeBuildInputs = - nix-eval-jobs.nativeBuildInputs - ++ [ (pkgs.python3.withPackages (ps: [ ps.pytest ])) ] - ++ - lib.optional stdenv.isLinux # broken on darwin - ( - pkgs.writeShellScriptBin "update-include-what-you-use" '' - #!${pkgs.stdenv.shell} - export PATH=${pkgs.include-what-you-use}/bin:$PATH - find src -type f -name '*.cpp' -o -name '*.hh' -print0 | \ - xargs -n1 --null include-what-you-use -std=c++20 -isystem ${lib.getDev nix}/include/nix 2>&1 | \ - fix_includes.py - '' - ); + nativeBuildInputs = nix-eval-jobs.nativeBuildInputs ++ [ + (pkgs.python3.withPackages (ps: [ ps.pytest ])) + (lib.hiPrio pkgs.clang-tools) + ]; shellHook = lib.optionalString stdenv.isLinux '' export NIX_DEBUG_INFO_DIRS="${pkgs.curl.debug}/lib/debug:${nix.debug}/lib/debug''${NIX_DEBUG_INFO_DIRS:+:$NIX_DEBUG_INFO_DIRS}" diff --git a/src/buffered-io.cc b/src/buffered-io.cc index a6b289e3..aa458b91 100644 --- a/src/buffered-io.cc +++ b/src/buffered-io.cc @@ -2,19 +2,27 @@ #include #include #include -#include +// NOLINTBEGIN(modernize-deprecated-headers) +// misc-include-cleaner wants these headers rather than the C++ version +#include +#include +// NOLINTEND(modernize-deprecated-headers) +#include #include #include #include +#include +#include #include "buffered-io.hh" +#include "strings-portable.hh" [[nodiscard]] auto tryWriteLine(int fd, std::string s) -> int { s += "\n"; std::string_view sv{s}; while (!sv.empty()) { nix::checkInterrupt(); - ssize_t res = write(fd, sv.data(), sv.size()); + const ssize_t res = write(fd, sv.data(), sv.size()); if (res == -1 && errno != EINTR) { return -errno; } @@ -25,29 +33,23 @@ return 0; } -LineReader::LineReader(int fd) { - stream = fdopen(fd, "r"); - if (!stream) { - throw nix::Error("fdopen(%d) failed: %s", fd, strerror(errno)); +LineReader::LineReader(int fd) : stream(fdopen(fd, "r")) { + if (stream == nullptr) { + throw nix::Error("fdopen(%d) failed: %s", fd, get_error_name(errno)); } } -LineReader::~LineReader() { - fclose(stream); - free(buffer); -} - -LineReader::LineReader(LineReader &&other) noexcept { - stream = other.stream; +LineReader::LineReader(LineReader &&other) noexcept + : stream(other.stream.release()), buffer(other.buffer.release()), + len(other.len) { other.stream = nullptr; - buffer = other.buffer; - other.buffer = nullptr; - len = other.len; other.len = 0; } [[nodiscard]] auto LineReader::readLine() -> std::string_view { - ssize_t read = getline(&buffer, &len, stream); + char *buf = buffer.release(); + const ssize_t read = getline(&buf, &len, stream.get()); + buffer.reset(buf); if (read == -1) { return {}; // Return an empty string_view in case of error @@ -56,5 +58,6 @@ LineReader::LineReader(LineReader &&other) noexcept { nix::checkInterrupt(); // Remove trailing newline - return {buffer, static_cast(read) - 1}; + char *line = buffer.get(); + return {line, static_cast(read) - 1}; } diff --git a/src/buffered-io.hh b/src/buffered-io.hh index 202a3c84..ec5107bb 100644 --- a/src/buffered-io.hh +++ b/src/buffered-io.hh @@ -2,19 +2,40 @@ #include #include #include +#include +#include [[nodiscard]] auto tryWriteLine(int fd, std::string s) -> int; +struct FileDeleter { + void operator()(FILE *file) const { + if (file != nullptr) { + std::fclose(file); // NOLINT(cppcoreguidelines-owning-memory) + } + } +}; + +struct MemoryDeleter { + void operator()(void *ptr) const { + // NOLINTBEGIN(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + std::free(ptr); + // NOLINTEND(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + } +}; + class LineReader { public: - LineReader(int fd); - ~LineReader(); + LineReader(const LineReader &) = delete; + explicit LineReader(int fd); + auto operator=(const LineReader &) -> LineReader & = delete; + auto operator=(LineReader &&) -> LineReader & = delete; + ~LineReader() = default; LineReader(LineReader &&other) noexcept; [[nodiscard]] auto readLine() -> std::string_view; private: - FILE *stream = nullptr; - char *buffer = nullptr; + std::unique_ptr stream = nullptr; + std::unique_ptr buffer = nullptr; size_t len = 0; }; diff --git a/src/drv.cc b/src/drv.cc index c4e5c455..8f33dac1 100644 --- a/src/drv.cc +++ b/src/drv.cc @@ -3,11 +3,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -17,20 +17,28 @@ #include #include #include +#include +#include #include #include #include -#include +#include +#include +#include #include "drv.hh" #include "eval-args.hh" -static auto -queryCacheStatus(nix::Store &store, - std::map> &outputs) - -> Drv::CacheStatus { - uint64_t downloadSize, narSize; - nix::StorePathSet willBuild, willSubstitute, unknown; +namespace { + +auto queryCacheStatus(nix::Store &store, + std::map> + &outputs) -> Drv::CacheStatus { + uint64_t downloadSize = 0; + uint64_t narSize = 0; + nix::StorePathSet willBuild; + nix::StorePathSet willSubstitute; + nix::StorePathSet unknown; std::vector paths; for (auto const &[key, val] : outputs) { @@ -47,16 +55,15 @@ queryCacheStatus(nix::Store &store, // - there's nothing to build // - there's nothing to substitute return Drv::CacheStatus::Local; - } else { - // cacheStatus is Cached if: - // - there's nothing to build - // - there are paths to substitute - return Drv::CacheStatus::Cached; } - } else { - return Drv::CacheStatus::NotBuilt; + // cacheStatus is Cached if: + // - there's nothing to build + // - there are paths to substitute + return Drv::CacheStatus::Cached; } -} + return Drv::CacheStatus::NotBuilt; +}; +} // namespace /* The fields of a derivation that are printed in json form */ Drv::Drv(std::string &attrPath, nix::EvalState &state, @@ -104,11 +111,11 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, if (args.meta) { nlohmann::json meta_; - for (auto &metaName : packageInfo.queryMetaNames()) { + for (const auto &metaName : packageInfo.queryMetaNames()) { nix::NixStringContext context; std::stringstream ss; - auto metaValue = packageInfo.queryMeta(metaName); + auto *metaValue = packageInfo.queryMeta(metaName); // Skip non-serialisable types // TODO: Fix serialisation of derivations to store paths if (metaValue == nullptr) { @@ -133,7 +140,7 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, auto drv = localStore->readDerivation(packageInfo.requireDrvPath()); for (const auto &[inputDrvPath, inputNode] : drv.inputDrvs.map) { std::set inputDrvOutputs; - for (auto &outputName : inputNode.value) { + for (const auto &outputName : inputNode.value) { inputDrvOutputs.insert(outputName); } inputDrvs[localStore->printStorePath(inputDrvPath)] = inputDrvOutputs; @@ -144,7 +151,7 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, void to_json(nlohmann::json &json, const Drv &drv) { std::map outputsJson; - for (auto &[name, optPath] : drv.outputs) { + for (const auto &[name, optPath] : drv.outputs) { outputsJson[name] = optPath ? nlohmann::json(*optPath) : nlohmann::json(nullptr); } @@ -164,9 +171,16 @@ void to_json(nlohmann::json &json, const Drv &drv) { json["isCached"] = drv.cacheStatus == Drv::CacheStatus::Cached || drv.cacheStatus == Drv::CacheStatus::Local; - json["cacheStatus"] = - drv.cacheStatus == Drv::CacheStatus::Cached ? "cached" - : drv.cacheStatus == Drv::CacheStatus::Local ? "local" - : "notBuilt"; + switch (drv.cacheStatus) { + case Drv::CacheStatus::Cached: + json["cacheStatus"] = "cached"; + break; + case Drv::CacheStatus::Local: + json["cacheStatus"] = "local"; + break; + default: + json["cacheStatus"] = "notBuilt"; + break; + } } } diff --git a/src/drv.hh b/src/drv.hh index 15b65ef3..626b8ed4 100644 --- a/src/drv.hh +++ b/src/drv.hh @@ -1,18 +1,14 @@ #include #include -#include #include #include #include #include #include -#include #include #include "eval-args.hh" -class MyArgs; - namespace nix { class EvalState; struct PackageInfo; @@ -20,6 +16,8 @@ struct PackageInfo; /* The fields of a derivation that are printed in json form */ struct Drv { + Drv(std::string &attrPath, nix::EvalState &state, + nix::PackageInfo &packageInfo, MyArgs &args); std::string name; std::string system; std::string drvPath; @@ -33,8 +31,5 @@ struct Drv { std::map> outputs; std::map> inputDrvs; std::optional meta; - - Drv(std::string &attrPath, nix::EvalState &state, - nix::PackageInfo &packageInfo, MyArgs &args); }; void to_json(nlohmann::json &json, const Drv &drv); diff --git a/src/eval-args.cc b/src/eval-args.cc index 4caf27a0..b83f5d49 100644 --- a/src/eval-args.cc +++ b/src/eval-args.cc @@ -5,11 +5,15 @@ #include #include #include +#include +#include #include -#include +#include #include -#include #include +#include +#include +#include #include "eval-args.hh" @@ -18,15 +22,16 @@ MyArgs::MyArgs() : MixCommonArgs("nix-eval-jobs") { .longName = "help", .description = "show usage information", .handler = {[&]() { - printf("USAGE: nix-eval-jobs [options] expr\n\n"); + std::cout << "USAGE: nix-eval-jobs [options] expr\n\n"; for (const auto &[name, flag] : longFlags) { - if (hiddenCategories.count(flag->category)) { + if (hiddenCategories.contains(flag->category)) { continue; } - printf(" --%-20s %s\n", name.c_str(), - flag->description.c_str()); + std::cout << " --" << std::left << std::setw(20) << name << " " + << flag->description << "\n"; } - ::exit(0); + + ::exit(0); // NOLINT(concurrency-mt-unsafe) }}, }); @@ -46,15 +51,14 @@ MyArgs::MyArgs() : MixCommonArgs("nix-eval-jobs") { addFlag({.longName = "workers", .description = "number of evaluate workers", .labels = {"workers"}, - .handler = {[=, this](const std::string &s) { - nrWorkers = std::stoi(s); - }}}); + .handler = { + [this](const std::string &s) { nrWorkers = std::stoi(s); }}}); addFlag({.longName = "max-memory-size", .description = "maximum evaluation memory size in megabyte " "(4GiB per worker by default)", .labels = {"size"}, - .handler = {[=, this](const std::string &s) { + .handler = {[this](const std::string &s) { maxMemorySize = std::stoi(s); }}}); diff --git a/src/eval-args.hh b/src/eval-args.hh index c1d78e01..d39eee86 100644 --- a/src/eval-args.hh +++ b/src/eval-args.hh @@ -5,18 +5,14 @@ #include #include #include -#include #include -#include #include -#include -#include -#include class MyArgs : virtual public nix::MixEvalArgs, virtual public nix::MixCommonArgs, virtual public nix::RootArgs { public: + virtual ~MyArgs() = default; std::string releaseExpr; nix::Path gcRootsDir; bool flake = false; @@ -35,6 +31,9 @@ class MyArgs : virtual public nix::MixEvalArgs, .useRegistries = false, .allowUnlocked = false}; MyArgs(); + MyArgs(MyArgs &&) = delete; + auto operator=(const MyArgs &) -> MyArgs & = default; + auto operator=(MyArgs &&) -> MyArgs & = delete; MyArgs(const MyArgs &) = delete; void parseArgs(char **argv, int argc); diff --git a/src/meson.build b/src/meson.build index 7db62d8a..5fe077c8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,7 +3,8 @@ src = [ 'eval-args.cc', 'drv.cc', 'buffered-io.cc', - 'worker.cc' + 'worker.cc', + 'strings-portable.cc' ] cc = meson.get_compiler('cpp') diff --git a/src/nix-eval-jobs.cc b/src/nix-eval-jobs.cc index 43a71d93..c4015ad9 100644 --- a/src/nix-eval-jobs.cc +++ b/src/nix-eval-jobs.cc @@ -12,6 +12,12 @@ #include #include #include +// NOLINTBEGIN(modernize-deprecated-headers) +// misc-include-cleaner wants these header rather than the C++ versions +#include +#include +#include +// NOLINTEND(modernize-deprecated-headers) #include #include #include @@ -21,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +36,7 @@ #include #include #include -#include +#include #include #include #include @@ -44,56 +49,51 @@ #include #include #include +#include #include "eval-args.hh" #include "buffered-io.hh" #include "worker.hh" +#include "strings-portable.hh" -using namespace nix; -using namespace nlohmann; - -static MyArgs myArgs; +namespace { +MyArgs myArgs; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +} -using Processor = std::function state, Bindings &autoArgs, - const Channel &channel, MyArgs &args)>; +using Processor = std::function; /* Auto-cleanup of fork's process and fds. */ struct Proc { - AutoCloseFD to, from; - Pid pid; + nix::AutoCloseFD to, from; + nix::Pid pid; - Proc(const Processor &proc) { - Pipe toPipe, fromPipe; + Proc(const Proc &) = delete; + Proc(Proc &&) = delete; + auto operator=(const Proc &) -> Proc & = delete; + auto operator=(Proc &&) -> Proc & = delete; + + explicit Proc(const Processor &proc) { + nix::Pipe toPipe; + nix::Pipe fromPipe; toPipe.create(); fromPipe.create(); - - to = std::move(toPipe.writeSide); - from = std::move(fromPipe.readSide); - auto p = startProcess( [&, - to{std::make_shared(std::move(fromPipe.writeSide))}, - from{ - std::make_shared(std::move(toPipe.readSide))}]() { - debug("created worker process %d", getpid()); + to{std::make_shared( + std::move(fromPipe.writeSide))}, + from{std::make_shared( + std::move(toPipe.readSide))}]() { + nix::logger->log( + nix::lvlDebug, + nix::fmt("created worker process %d", getpid())); try { - auto evalStore = myArgs.evalStoreUrl - ? openStore(*myArgs.evalStoreUrl) - : openStore(); - auto state = std::make_shared( - myArgs.lookupPath, evalStore, fetchSettings, - evalSettings); - Bindings &autoArgs = *myArgs.getAutoArgs(*state); - Channel channel{ - .from = from, - .to = to, - }; - proc(ref(state), autoArgs, channel, myArgs); - } catch (Error &e) { + proc(myArgs, *to, *from); + } catch (nix::Error &e) { nlohmann::json err; - auto &msg = e.msg(); + const auto &msg = e.msg(); err["error"] = nix::filterANSIEscapes(msg, true); - printError(msg); + nix::logger->log(nix::lvlError, msg); if (tryWriteLine(to->get(), err.dump()) < 0) { return; // main process died }; @@ -104,8 +104,10 @@ struct Proc { } } }, - ProcessOptions{.allowVfork = false}); + nix::ProcessOptions{.allowVfork = false}); + to = std::move(toPipe.writeSide); + from = std::move(fromPipe.readSide); pid = p; } @@ -119,41 +121,42 @@ struct Proc { // evaluator under an anemic stack of 0.5MiB has it overflow way too quickly. // Hence, we have our own custom Thread struct. struct Thread { - pthread_t thread; + pthread_t thread = {}; // NOLINT(misc-include-cleaner) Thread(const Thread &) = delete; Thread(Thread &&) noexcept = default; + ~Thread() = default; + auto operator=(const Thread &) -> Thread & = delete; + auto operator=(Thread &&) -> Thread & = delete; - Thread(std::function f) { - int s; - pthread_attr_t attr; + explicit Thread(std::function f) { + pthread_attr_t attr = {}; // NOLINT(misc-include-cleaner) auto func = std::make_unique>(std::move(f)); - s = pthread_attr_init(&attr); + int s = pthread_attr_init(&attr); if (s != 0) { - throw SysError(s, "calling pthread_attr_init"); + throw nix::SysError(s, "calling pthread_attr_init"); } s = pthread_attr_setstacksize(&attr, static_cast(64) * 1024 * 1024); if (s != 0) { - throw SysError(s, "calling pthread_attr_setstacksize"); + throw nix::SysError(s, "calling pthread_attr_setstacksize"); } s = pthread_create(&thread, &attr, Thread::init, func.release()); if (s != 0) { - throw SysError(s, "calling pthread_launch"); + throw nix::SysError(s, "calling pthread_launch"); } s = pthread_attr_destroy(&attr); if (s != 0) { - throw SysError(s, "calling pthread_attr_destroy"); + throw nix::SysError(s, "calling pthread_attr_destroy"); } } - void join() { - int s; - s = pthread_join(thread, nullptr); + void join() const { + const int s = pthread_join(thread, nullptr); if (s != 0) { - throw SysError(s, "calling pthread_join"); + throw nix::SysError(s, "calling pthread_join"); } } @@ -168,73 +171,78 @@ struct Thread { }; struct State { - std::set todo = json::array({json::array()}); - std::set active; + std::set todo = + nlohmann::json::array({nlohmann::json::array()}); + std::set active; std::exception_ptr exc; }; void handleBrokenWorkerPipe(Proc &proc, std::string_view msg) { // we already took the process status from Proc, no // need to wait for it again to avoid error messages - pid_t pid = proc.pid.release(); + const pid_t pid = proc.pid.release(); while (true) { - int status; - int rc = waitpid(pid, &status, WNOHANG); + int status = 0; + const int rc = waitpid(pid, &status, WNOHANG); if (rc == 0) { kill(pid, SIGKILL); - throw Error("BUG: while %s, worker pipe got closed but evaluation " - "worker still running?", - msg); - } else if (rc == -1) { + throw nix::Error( + "BUG: while %s, worker pipe got closed but evaluation " + "worker still running?", + msg); + } + + if (rc == -1) { kill(pid, SIGKILL); - throw Error( + throw nix::Error( "BUG: while %s, waitpid for evaluation worker failed: %s", msg, - strerror(errno)); - } else { - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 1) { - throw Error( - "while %s, evaluation worker exited with exit code 1, " - "(possible infinite recursion)", - msg); - } - throw Error("while %s, evaluation worker exited with %d", msg, - WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - switch (WTERMSIG(status)) { - case SIGKILL: - throw Error( - "while %s, evaluation worker got killed by SIGKILL, " - "maybe " - "memory limit reached?", - msg); - break; + get_error_name(errno)); + } + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 1) { + throw nix::Error( + "while %s, evaluation worker exited with exit code 1, " + "(possible infinite recursion)", + msg); + } + throw nix::Error("while %s, evaluation worker exited with %d", msg, + WEXITSTATUS(status)); + } + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGKILL: + throw nix::Error( + "while %s, evaluation worker got killed by SIGKILL, " + "maybe " + "memory limit reached?", + msg); + break; #ifdef __APPLE__ - case SIGBUS: - throw Error( - "while %s, evaluation worker got killed by SIGBUS, " - "(possible infinite recursion)", - msg); - break; + case SIGBUS: + throw nix::Error( + "while %s, evaluation worker got killed by SIGBUS, " + "(possible infinite recursion)", + msg); + break; #else - case SIGSEGV: - throw Error( - "while %s, evaluation worker got killed by SIGSEGV, " - "(possible infinite recursion)", - msg); + case SIGSEGV: + throw nix::Error( + "while %s, evaluation worker got killed by SIGSEGV, " + "(possible infinite recursion)", + msg); #endif - default: - throw Error("while %s, evaluation worker got killed by " - "signal %d (%s)", - msg, WTERMSIG(status), - strsignal(WTERMSIG(status))); - } - } // else ignore WIFSTOPPED and WIFCONTINUED - } + default: + throw nix::Error("while %s, evaluation worker got killed by " + "signal %d (%s)", + msg, WTERMSIG(status), + get_signal_name(WTERMSIG(status))); + } + } // else ignore WIFSTOPPED and WIFCONTINUED } } -auto joinAttrPath(json &attrPath) -> std::string { +auto joinAttrPath(nlohmann::json &attrPath) -> std::string { std::string joined; for (auto &element : attrPath) { if (!joined.empty()) { @@ -245,7 +253,7 @@ auto joinAttrPath(json &attrPath) -> std::string { return joined; } -void collector(Sync &state_, std::condition_variable &wakeup) { +void collector(nix::Sync &state_, std::condition_variable &wakeup) { try { std::optional> proc_; std::optional> fromReader_; @@ -271,20 +279,21 @@ void collector(Sync &state_, std::condition_variable &wakeup) { continue; } else if (s != "next") { try { - auto json = json::parse(s); - throw Error("worker error: %s", (std::string)json["error"]); - } catch (const json::exception &e) { - throw Error( + auto json = nlohmann::json::parse(s); + throw nix::Error("worker error: %s", + std::string(json["error"])); + } catch (const nlohmann::json::exception &e) { + throw nix::Error( "Received invalid JSON from worker: %s\n json: '%s'", e.what(), s); } } /* Wait for a job name to become available. */ - json attrPath; + nlohmann::json attrPath; while (true) { - checkInterrupt(); + nix::checkInterrupt(); auto state(state_.lock()); if ((state->todo.empty() && state->active.empty()) || state->exc) { @@ -298,8 +307,8 @@ void collector(Sync &state_, std::condition_variable &wakeup) { state->todo.erase(state->todo.begin()); state->active.insert(attrPath); break; - } else - state.wait(wakeup); + } + state.wait(wakeup); } /* Tell the worker to evaluate it. */ @@ -315,20 +324,21 @@ void collector(Sync &state_, std::condition_variable &wakeup) { joinAttrPath(attrPath) + "'"; handleBrokenWorkerPipe(*proc.get(), msg); } - json response; + nlohmann::json response; try { - response = json::parse(respString); - } catch (const json::exception &e) { - throw Error( + response = nlohmann::json::parse(respString); + } catch (const nlohmann::json::exception &e) { + throw nix::Error( "Received invalid JSON from worker: %s\n json: '%s'", e.what(), respString); } /* Handle the response. */ - std::vector newAttrs; + std::vector newAttrs; if (response.find("attrs") != response.end()) { for (auto &i : response["attrs"]) { - json newAttr = json(response["attrPath"]); + nlohmann::json newAttr = + nlohmann::json(response["attrPath"]); newAttr.emplace_back(i); newAttrs.push_back(newAttr); } @@ -361,10 +371,10 @@ auto main(int argc, char **argv) -> int { /* Prevent undeclared dependencies in the evaluation via $NIX_PATH. */ - unsetenv("NIX_PATH"); + unsetenv("NIX_PATH"); // NOLINT(concurrency-mt-unsafe) /* We are doing the garbage collection by killing forks */ - setenv("GC_DONT_GC", "1", 1); + setenv("GC_DONT_GC", "1", 1); // NOLINT(concurrency-mt-unsafe) /* Because of an objc quirk[1], calling curl_global_init for the first time after fork() will always result in a crash. @@ -379,43 +389,47 @@ auto main(int argc, char **argv) -> int { */ curl_global_init(CURL_GLOBAL_ALL); - return handleExceptions(argv[0], [&]() { - initNix(); - initGC(); - flake::initLib(flakeSettings); + auto args = std::span(argv, argc); + + return nix::handleExceptions(args[0], [&]() { + nix::initNix(); + nix::initGC(); + nix::flake::initLib(nix::flakeSettings); myArgs.parseArgs(argv, argc); /* FIXME: The build hook in conjunction with import-from-derivation is * causing "unexpected EOF" during eval */ - settings.builders = ""; + nix::settings.builders = ""; /* Prevent access to paths outside of the Nix search path and to the environment. */ - evalSettings.restrictEval = false; + nix::evalSettings.restrictEval = false; /* When building a flake, use pure evaluation (no access to 'getEnv', 'currentSystem' etc. */ if (myArgs.impure) { - evalSettings.pureEval = false; + nix::evalSettings.pureEval = false; } else if (myArgs.flake) { - evalSettings.pureEval = true; + nix::evalSettings.pureEval = true; } - if (myArgs.releaseExpr == "") - throw UsageError("no expression specified"); + if (myArgs.releaseExpr.empty()) { + throw nix::UsageError("no expression specified"); + } - if (myArgs.gcRootsDir == "") { - printMsg(lvlError, "warning: `--gc-roots-dir' not specified"); + if (myArgs.gcRootsDir.empty()) { + nix::logger->log(nix::lvlError, + "warning: `--gc-roots-dir' not specified"); } else { myArgs.gcRootsDir = std::filesystem::absolute(myArgs.gcRootsDir); } if (myArgs.showTrace) { - loggerSettings.showTrace.assign(true); + nix::loggerSettings.showTrace.assign(true); } - Sync state_; + nix::Sync state_; /* Start a collector thread per worker process. */ std::vector threads; @@ -425,12 +439,14 @@ auto main(int argc, char **argv) -> int { [&state_, &wakeup] { collector(state_, wakeup); }); } - for (auto &thread : threads) + for (auto &thread : threads) { thread.join(); + } auto state(state_.lock()); - if (state->exc) + if (state->exc) { std::rethrow_exception(state->exc); + } }); } diff --git a/src/strings-portable.cc b/src/strings-portable.cc new file mode 100644 index 00000000..4dc65e19 --- /dev/null +++ b/src/strings-portable.cc @@ -0,0 +1,44 @@ +#include + +#ifdef __APPLE__ +// for sys_siglist and sys_errlist +#include +#include +#elif defined(__FreeBSD__) +#include +#endif + +#if defined(__GLIBC__) +// Linux with glibc specific: sigabbrev_np +auto get_signal_name(int sig) -> const char * { + const char *name = sigabbrev_np(sig); + if (name != nullptr) { + return name; + } + return "Unknown signal"; +} +auto get_error_name(int err) -> const char * { + const char *name = strerrorname_np(err); + if (name != nullptr) { + return name; + } + return "Unknown error"; +} +#elif defined(__APPLE__) || defined(__FreeBSD__) +// macOS and FreeBSD have sys_siglist +auto get_signal_name(int sig) -> const char * { + if (sig >= 0 && sig < NSIG) { + return sys_siglist[sig]; + } + return "Unknown signal"; +} +auto get_error_name(int err) -> const char * { + if (err >= 0 && err < sys_nerr) { + return sys_errlist[err]; + } + return "Unknown error"; +} +#else +auto get_signal_name(int sig) -> const char * { return strsignal(sig); } +auto get_error_name(int err) -> const char * { return strerror(err); } +#endif diff --git a/src/strings-portable.hh b/src/strings-portable.hh new file mode 100644 index 00000000..c6ce15b0 --- /dev/null +++ b/src/strings-portable.hh @@ -0,0 +1,4 @@ +#pragma once + +auto get_signal_name(int sig) -> const char *; +auto get_error_name(int err) -> const char *; diff --git a/src/worker.cc b/src/worker.cc index 6ee2d05c..fd724ef8 100644 --- a/src/worker.cc +++ b/src/worker.cc @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -13,36 +12,35 @@ #include #include #include -#include +#include + +// NOLINTBEGIN(modernize-deprecated-headers) +// misc-include-cleaner wants this header rather than the C++ version +#include +// NOLINTEND(modernize-deprecated-headers) + #include #include #include -#include #include -#include #include #include #include #include #include -#include #include -#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include -#include #include "worker.hh" #include "drv.hh" @@ -53,9 +51,9 @@ namespace nix { struct Expr; } // namespace nix -static auto releaseExprTopLevelValue(nix::EvalState &state, - nix::Bindings &autoArgs, - MyArgs &args) -> nix::Value * { +namespace { +auto releaseExprTopLevelValue(nix::EvalState &state, nix::Bindings &autoArgs, + MyArgs &args) -> nix::Value * { nix::Value vTop; if (args.fromArgs) { @@ -66,14 +64,14 @@ static auto releaseExprTopLevelValue(nix::EvalState &state, state.evalFile(lookupFileArg(state, args.releaseExpr), vTop); } - auto vRoot = state.allocValue(); + auto *vRoot = state.allocValue(); state.autoCallFunction(autoArgs, vTop, *vRoot); return vRoot; } -static auto attrPathJoin(nlohmann::json input) -> std::string { +auto attrPathJoin(nlohmann::json input) -> std::string { return std::accumulate(input.begin(), input.end(), std::string(), [](const std::string &ss, std::string s) { // Escape token if containing dots @@ -83,9 +81,18 @@ static auto attrPathJoin(nlohmann::json input) -> std::string { return ss.empty() ? s : ss + "." + s; }); } +} // namespace + +void worker( + MyArgs &args, + nix::AutoCloseFD &to, // NOLINT(bugprone-easily-swappable-parameters) + nix::AutoCloseFD &from) { -void worker(nix::ref state, nix::Bindings &autoArgs, - const Channel &channel, MyArgs &args) { + auto evalStore = args.evalStoreUrl ? nix::openStore(*args.evalStoreUrl) + : nix::openStore(); + auto state = nix::make_ref( + args.lookupPath, evalStore, nix::fetchSettings, nix::evalSettings); + nix::Bindings &autoArgs = *args.getAutoArgs(*state); nix::Value *vRoot = [&]() { if (args.flake) { @@ -97,16 +104,16 @@ void worker(nix::ref state, nix::Bindings &autoArgs, {}, {}, args.lockFlags}; return flake.toValue(*state).first; - } else { - return releaseExprTopLevelValue(*state, autoArgs, args); } + + return releaseExprTopLevelValue(*state, autoArgs, args); }(); - LineReader fromReader(channel.from->release()); + LineReader fromReader(from.release()); while (true) { /* Wait for the collector to send us a job name. */ - if (tryWriteLine(channel.to->get(), "next") < 0) { + if (tryWriteLine(to.get(), "next") < 0) { return; // main process died } @@ -115,8 +122,8 @@ void worker(nix::ref state, nix::Bindings &autoArgs, break; } if (!nix::hasPrefix(s, "do ")) { - fprintf(stderr, "worker error: received invalid command '%s'\n", - s.data()); + std::cerr << "worker error: received invalid command '" << s + << "'\n"; abort(); } auto path = nlohmann::json::parse(s.substr(3)); @@ -126,11 +133,11 @@ void worker(nix::ref state, nix::Bindings &autoArgs, nlohmann::json reply = nlohmann::json{{"attr", attrPathS}, {"attrPath", path}}; try { - auto vTmp = + auto *vTmp = nix::findAlongAttrPath(*state, attrPathS, autoArgs, *vRoot) .first; - auto v = state->allocValue(); + auto *v = state->allocValue(); state->autoCallFunction(autoArgs, *vTmp, *v); if (v->type() == nix::nAttrs) { @@ -142,8 +149,8 @@ void worker(nix::ref state, nix::Bindings &autoArgs, /* Register the derivation as a GC root. !!! This registers roots for jobs that we may have already done. */ - if (args.gcRootsDir != "") { - nix::Path root = + if (args.gcRootsDir.empty()) { + const nix::Path root = args.gcRootsDir + "/" + std::string(nix::baseNameOf(drv.drvPath)); if (!nix::pathExists(root)) { @@ -159,8 +166,8 @@ void worker(nix::ref state, nix::Bindings &autoArgs, auto attrs = nlohmann::json::array(); bool recurse = args.forceRecurse || - path.size() == 0; // Dont require `recurseForDerivations - // = true;` for top-level attrset + path.empty(); // Dont require `recurseForDerivations + // = true;` for top-level attrset for (auto &i : v->attrs()->lexicographicOrder(state->symbols)) { @@ -169,17 +176,18 @@ void worker(nix::ref state, nix::Bindings &autoArgs, if (name == "recurseForDerivations" && !args.forceRecurse) { - auto attrv = + const auto *attrv = v->attrs()->get(state->sRecurseForDerivations); recurse = state->forceBool( *attrv->value, attrv->pos, "while evaluating recurseForDerivations"); } } - if (recurse) + if (recurse) { reply["attrs"] = std::move(attrs); - else + } else { reply["attrs"] = nlohmann::json::array(); + } } } else { // We ignore everything that cannot be build @@ -196,28 +204,31 @@ void worker(nix::ref state, nix::Bindings &autoArgs, reply["error"] = nix::filterANSIEscapes(msg, true); // Don't forget to print it into the STDERR log, this is // what's shown in the Hydra UI. - fprintf(stderr, "%s\n", msg.c_str()); + std::cerr << msg << "\n"; } catch ( const std::exception &e) { // FIXME: for some reason the catch block // above, doesn't trigger on macOS (?) - auto msg = e.what(); + const auto *msg = e.what(); reply["error"] = nix::filterANSIEscapes(msg, true); - fprintf(stderr, "%s\n", msg); + std::cerr << msg << '\n'; } - if (tryWriteLine(channel.to->get(), reply.dump()) < 0) { + if (tryWriteLine(to.get(), reply.dump()) < 0) { return; // main process died } /* If our RSS exceeds the maximum, exit. The collector will start a new process. */ - struct rusage r; + struct rusage r = {}; // NOLINT(misc-include-cleaner) getrusage(RUSAGE_SELF, &r); - if ((size_t)r.ru_maxrss > args.maxMemorySize * 1024) + const size_t maxrss = + r.ru_maxrss; // NOLINT(cppcoreguidelines-pro-type-union-access) + if (maxrss > args.maxMemorySize * 1024) { break; + } } - if (tryWriteLine(channel.to->get(), "restart") < 0) { + if (tryWriteLine(to.get(), "restart") < 0) { return; // main process died }; } diff --git a/src/worker.hh b/src/worker.hh index cc2c2992..5ad99a3c 100644 --- a/src/worker.hh +++ b/src/worker.hh @@ -1,7 +1,5 @@ #pragma once -#include #include -#include #include "eval-args.hh" @@ -14,9 +12,4 @@ class EvalState; template class ref; } // namespace nix -struct Channel { - std::shared_ptr from, to; -}; - -void worker(nix::ref state, nix::Bindings &autoArgs, - const Channel &channel, MyArgs &args); +void worker(MyArgs &args, nix::AutoCloseFD &to, nix::AutoCloseFD &from);