Compare commits

..

No commits in common. "main" and "jade/lixexpr" have entirely different histories.

14 changed files with 71 additions and 150 deletions

1
.nix-version Normal file
View file

@ -0,0 +1 @@
unstable

View file

@ -1,8 +1,5 @@
# nix-eval-jobs # nix-eval-jobs
> [!NOTE]
> This is a fork of nix-eval-jobs that works with Lix.
This project evaluates nix attribute sets in parallel with streamable json This project evaluates nix attribute sets in parallel with streamable json
output. This is useful for time and memory intensive evaluations such as NixOS output. This is useful for time and memory intensive evaluations such as NixOS
machines, i.e. in a CI context. The evaluation is done with a controllable machines, i.e. in a CI context. The evaluation is done with a controllable
@ -92,12 +89,11 @@ we collect example ci configuration for various CIs.
## Organisation of this repository ## Organisation of this repository
`main` follows Lix HEAD, and is updated alongside the Lix NixOS module. When we On the `main` branch we target nixUnstable. When a release of nix happens, we
release we will make a `release-2.90` etc branch, which receives backports. fork for a release branch i.e. `release-2.8` and change the nix version in
`.nix-version`. Changes and improvements made in `main` also may be backported
The version of nix-eval-jobs follows the major version of Lix and minor to these release branches. At the time of writing we only intent to support the
versions of nix-eval-jobs are released as necessary when changes are made in latest release branch.
n-e-j itself.
## Projects using nix-eval-jobs ## Projects using nix-eval-jobs

View file

@ -11,7 +11,7 @@ let
in in
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "nix-eval-jobs"; pname = "nix-eval-jobs";
version = "2.90.0-unstable"; version = "2.19.0";
src = if srcDir == null then filterMesonBuild ./. else srcDir; src = if srcDir == null then filterMesonBuild ./. else srcDir;
buildInputs = with pkgs; [ buildInputs = with pkgs; [
nlohmann_json nlohmann_json

View file

@ -23,11 +23,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1730504689, "lastModified": 1701473968,
"narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "506278e768c2a08bec68eb62932193e341f55c90", "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -39,7 +39,6 @@
"lix": { "lix": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"nix2container": "nix2container",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
@ -47,15 +46,18 @@
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": "pre-commit-hooks"
}, },
"locked": { "locked": {
"lastModified": 1732112222, "lastModified": 1714955862,
"narHash": "sha256-H7GN4++a4vE49SUNojZx+FSk4mmpb2ifJUtJMJHProI=", "narHash": "sha256-REWlo2RYHfJkxnmZTEJu3Cd/2VM+wjjpPy7Xi4BdDTQ=",
"rev": "66f6dbda32959dd5cf3a9aaba15af72d037ab7ff", "ref": "refs/tags/2.90-beta.1",
"type": "tarball", "rev": "b6799ab0374a8e1907a48915d3187e07da41d88c",
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/66f6dbda32959dd5cf3a9aaba15af72d037ab7ff.tar.gz?rev=66f6dbda32959dd5cf3a9aaba15af72d037ab7ff" "revCount": 15501,
"type": "git",
"url": "https://git@git.lix.systems/lix-project/lix"
}, },
"original": { "original": {
"type": "tarball", "ref": "refs/tags/2.90-beta.1",
"url": "https://git.lix.systems/lix-project/lix/archive/main.tar.gz" "type": "git",
"url": "https://git@git.lix.systems/lix-project/lix"
} }
}, },
"nix-github-actions": { "nix-github-actions": {
@ -65,11 +67,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1731952509, "lastModified": 1701208414,
"narHash": "sha256-p4gB3Rhw8R6Ak4eMl8pqjCPOLCZRqaehZxdZ/mbFClM=", "narHash": "sha256-xrQ0FyhwTZK6BwKhahIkUVZhMNk21IEI1nUcWSONtpo=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nix-github-actions", "repo": "nix-github-actions",
"rev": "7b5f051df789b6b20d259924d349a9ba3319b226", "rev": "93e39cc1a087d65bcf7a132e75a650c44dd2b734",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -78,34 +80,18 @@
"type": "github" "type": "github"
} }
}, },
"nix2container": {
"flake": false,
"locked": {
"lastModified": 1724996935,
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1732244845, "lastModified": 1703134684,
"narHash": "sha256-aspop5sCDNpDMS23BplGFtQDadwkSb/sOxpuC3lafvo=", "narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "df94f897ffe1af1bcd60cb68697c5d8e6431346e", "rev": "d6863cbcbbb80e71cecfc03356db1cda38919523",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.05-small", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -129,11 +115,11 @@
"pre-commit-hooks": { "pre-commit-hooks": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1726745158, "lastModified": 1712055707,
"narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=", "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74", "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -158,11 +144,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1732292307, "lastModified": 1702979157,
"narHash": "sha256-5WSng844vXt8uytT5djmqBCkopyle6ciFgteuA9bJpw=", "narHash": "sha256-RnFBbLbpqtn4AoJGXKevQMCGhra4h6G2MPcuTSZZQ+g=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "705df92694af7093dfbb27109ce16d828a79155f", "rev": "2961375283668d867e64129c22af532de8e77734",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,7 +1,7 @@
{ {
description = "Hydra's builtin hydra-eval-jobs as a standalone"; description = "Hydra's builtin hydra-eval-jobs as a standalone";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05-small"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.flake-parts.url = "github:hercules-ci/flake-parts"; inputs.flake-parts.url = "github:hercules-ci/flake-parts";
inputs.flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; inputs.flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
inputs.treefmt-nix.url = "github:numtide/treefmt-nix"; inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
@ -9,7 +9,7 @@
inputs.nix-github-actions.url = "github:nix-community/nix-github-actions"; inputs.nix-github-actions.url = "github:nix-community/nix-github-actions";
inputs.nix-github-actions.inputs.nixpkgs.follows = "nixpkgs"; inputs.nix-github-actions.inputs.nixpkgs.follows = "nixpkgs";
inputs.lix = { inputs.lix = {
url = "https://git.lix.systems/lix-project/lix/archive/main.tar.gz"; url = "git+https://git@git.lix.systems/lix-project/lix?ref=refs/tags/2.90-beta.1";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
@ -17,6 +17,7 @@
let let
inherit (inputs.nixpkgs) lib; inherit (inputs.nixpkgs) lib;
inherit (inputs) self; inherit (inputs) self;
nixVersion = lib.fileContents ./.nix-version;
in in
flake-parts.lib.mkFlake { inherit inputs; } flake-parts.lib.mkFlake { inherit inputs; }
{ {

View file

@ -1,11 +1,6 @@
project('nix-eval-jobs', 'cpp', project('nix-eval-jobs', 'cpp',
version : '0.1.6', version : '0.1.6',
license : 'GPL-3.0', license : 'GPL-3.0',
default_options : [
'debug=true',
'optimization=2',
'cpp_std=c++20',
],
) )
nix_main_dep = dependency('lix-main', required: true) nix_main_dep = dependency('lix-main', required: true)

View file

@ -23,7 +23,6 @@ in
pkgs.mkShell { pkgs.mkShell {
inherit (nix-eval-jobs) buildInputs; inherit (nix-eval-jobs) buildInputs;
nativeBuildInputs = nix-eval-jobs.nativeBuildInputs ++ [ nativeBuildInputs = nix-eval-jobs.nativeBuildInputs ++ [
pkgs.clang-tools
(pkgs.python3.withPackages (ps: [ (pkgs.python3.withPackages (ps: [
ps.pytest ps.pytest
])) ]))

View file

@ -23,17 +23,14 @@
#include "drv.hh" #include "drv.hh"
#include "eval-args.hh" #include "eval-args.hh"
static bool static bool queryIsCached(nix::Store &store,
queryIsCached(nix::Store &store, std::map<std::string, std::string> &outputs) {
std::map<std::string, std::optional<std::string>> &outputs) {
uint64_t downloadSize, narSize; uint64_t downloadSize, narSize;
nix::StorePathSet willBuild, willSubstitute, unknown; nix::StorePathSet willBuild, willSubstitute, unknown;
std::vector<nix::StorePathWithOutputs> paths; std::vector<nix::StorePathWithOutputs> paths;
for (auto const &[key, val] : outputs) { for (auto const &[key, val] : outputs) {
if (val) { paths.push_back(followLinksToStorePathWithOutputs(store, val));
paths.push_back(followLinksToStorePathWithOutputs(store, *val));
}
} }
store.queryMissing(toDerivedPaths(paths), willBuild, willSubstitute, store.queryMissing(toDerivedPaths(paths), willBuild, willSubstitute,
@ -48,19 +45,9 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
auto localStore = state.store.dynamic_pointer_cast<nix::LocalFSStore>(); auto localStore = state.store.dynamic_pointer_cast<nix::LocalFSStore>();
try { try {
// CA derivations do not have static output paths, so we have to for (auto out : drvInfo.queryOutputs(true)) {
// defensively not query output paths in case we encounter one. if (out.second)
for (auto &[outputName, optOutputPath] : outputs[out.first] = localStore->printStorePath(*out.second);
drvInfo.queryOutputs(!nix::experimentalFeatureSettings.isEnabled(
nix::Xp::CaDerivations))) {
if (optOutputPath) {
outputs[outputName] =
localStore->printStorePath(*optOutputPath);
} else {
assert(nix::experimentalFeatureSettings.isEnabled(
nix::Xp::CaDerivations));
outputs[outputName] = std::nullopt;
}
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
throw nix::EvalError(state, throw nix::EvalError(state,
@ -111,16 +98,10 @@ Drv::Drv(std::string &attrPath, nix::EvalState &state, nix::DrvInfo &drvInfo,
} }
void to_json(nlohmann::json &json, const Drv &drv) { void to_json(nlohmann::json &json, const Drv &drv) {
std::map<std::string, nlohmann::json> outputsJson;
for (auto &[name, optPath] : drv.outputs) {
outputsJson[name] =
optPath ? nlohmann::json(*optPath) : nlohmann::json(nullptr);
}
json = nlohmann::json{{"name", drv.name}, json = nlohmann::json{{"name", drv.name},
{"system", drv.system}, {"system", drv.system},
{"drvPath", drv.drvPath}, {"drvPath", drv.drvPath},
{"outputs", outputsJson}, {"outputs", drv.outputs},
{"inputDrvs", drv.inputDrvs}}; {"inputDrvs", drv.inputDrvs}};
if (drv.meta.has_value()) { if (drv.meta.has_value()) {

View file

@ -24,7 +24,7 @@ struct Drv {
std::string drvPath; std::string drvPath;
enum class CacheStatus { Cached, Uncached, Unknown } cacheStatus; enum class CacheStatus { Cached, Uncached, Unknown } cacheStatus;
std::map<std::string, std::optional<std::string>> outputs; std::map<std::string, std::string> outputs;
std::map<std::string, std::set<std::string>> inputDrvs; std::map<std::string, std::set<std::string>> inputDrvs;
std::optional<nlohmann::json> meta; std::optional<nlohmann::json> meta;

View file

@ -100,5 +100,5 @@ MyArgs::MyArgs() : MixCommonArgs("nix-eval-jobs") {
} }
void MyArgs::parseArgs(char **argv, int argc) { void MyArgs::parseArgs(char **argv, int argc) {
parseCmdline(nix::Strings(argv + 1, argv + argc)); parseCmdline(nix::argvToStrings(argc, argv));
} }

View file

@ -12,7 +12,7 @@
class MyArgs : virtual public nix::MixEvalArgs, class MyArgs : virtual public nix::MixEvalArgs,
virtual public nix::MixCommonArgs, virtual public nix::MixCommonArgs,
virtual public nix::RootArgs { virtual nix::RootArgs {
public: public:
std::string releaseExpr; std::string releaseExpr;
nix::Path gcRootsDir; nix::Path gcRootsDir;

View file

@ -1,10 +1,10 @@
src = files( src = [
'nix-eval-jobs.cc', 'nix-eval-jobs.cc',
'eval-args.cc', 'eval-args.cc',
'drv.cc', 'drv.cc',
'buffered-io.cc', 'buffered-io.cc',
'worker.cc', 'worker.cc'
) ]
cc = meson.get_compiler('cpp') cc = meson.get_compiler('cpp')
@ -31,4 +31,4 @@ executable('nix-eval-jobs', src,
threads_dep threads_dep
], ],
install: true, install: true,
cpp_args: ['--include', 'autotools-config.h']) cpp_args: ['-std=c++2a', '-fvisibility=hidden', '--include', 'autotools-config.h'])

View file

@ -8,7 +8,6 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <errno.h> #include <errno.h>
#include <pthread.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -18,13 +17,13 @@
#include <lix/libutil/error.hh> #include <lix/libutil/error.hh>
#include <lix/libstore/globals.hh> #include <lix/libstore/globals.hh>
#include <lix/libutil/logging.hh> #include <lix/libutil/logging.hh>
#include <lix/libutil/terminal.hh>
#include <nlohmann/detail/iterators/iter_impl.hpp> #include <nlohmann/detail/iterators/iter_impl.hpp>
#include <nlohmann/detail/json_ref.hpp> #include <nlohmann/detail/json_ref.hpp>
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include <lix/libutil/ref.hh> #include <lix/libutil/ref.hh>
#include <lix/libstore/store-api.hh> #include <lix/libstore/store-api.hh>
#include <map> #include <map>
#include <thread>
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <exception> #include <exception>
@ -93,60 +92,12 @@ struct Proc {
to = std::move(toPipe.writeSide); to = std::move(toPipe.writeSide);
from = std::move(fromPipe.readSide); from = std::move(fromPipe.readSide);
pid = std::move(p); pid = p;
} }
~Proc() {} ~Proc() {}
}; };
// We'd highly prefer using std::thread here; but this won't let us configure the stack
// size. macOS uses 512KiB size stacks for non-main threads, and musl defaults to 128k.
// While Nix configures a 64MiB size for the main thread, this doesn't propagate to the
// threads we launch here. It turns out, running the 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;
Thread(const Thread &) = delete;
Thread(Thread &&) noexcept = default;
Thread(std::function<void(void)> f) {
int s;
pthread_attr_t attr;
auto func = std::make_unique<std::function<void(void)>>(std::move(f));
if ((s = pthread_attr_init(&attr)) != 0) {
throw SysError(s, "calling pthread_attr_init");
}
if ((s = pthread_attr_setstacksize(&attr, 64 * 1024 * 1024)) != 0) {
throw SysError(s, "calling pthread_attr_setstacksize");
}
if ((s = pthread_create(&thread, &attr, Thread::init, func.release())) != 0) {
throw SysError(s, "calling pthread_launch");
}
if ((s = pthread_attr_destroy(&attr)) != 0) {
throw SysError(s, "calling pthread_attr_destroy");
}
}
void join() {
int s;
s = pthread_join(thread, nullptr);
if (s != 0) {
throw SysError(s, "calling pthread_join");
}
}
private:
static void *init(void *ptr) {
std::unique_ptr<std::function<void(void)>> func;
func.reset(static_cast<std::function<void(void)> *>(ptr));
(*func)();
return 0;
}
};
struct State { struct State {
std::set<json> todo = json::array({json::array()}); std::set<json> todo = json::array({json::array()});
std::set<json> active; std::set<json> active;
@ -344,16 +295,24 @@ int main(int argc, char **argv) {
return handleExceptions(argv[0], [&]() { return handleExceptions(argv[0], [&]() {
initNix(); initNix();
initLibExpr(); initGC();
myArgs.parseArgs(argv, argc); myArgs.parseArgs(argv, argc);
/* FIXME: The build hook in conjunction with import-from-derivation is
* causing "unexpected EOF" during eval */
settings.builders = "";
/* Prevent access to paths outside of the Nix search path and
to the environment. */
evalSettings.restrictEval = false;
/* When building a flake, use pure evaluation (no access to /* When building a flake, use pure evaluation (no access to
'getEnv', 'currentSystem' etc. */ 'getEnv', 'currentSystem' etc. */
if (myArgs.impure) { if (myArgs.impure) {
evalSettings.pureEval.override(false); evalSettings.pureEval = false;
} else if (myArgs.flake) { } else if (myArgs.flake) {
evalSettings.pureEval.override(true); evalSettings.pureEval = true;
} }
if (myArgs.releaseExpr == "") if (myArgs.releaseExpr == "")
@ -366,16 +325,16 @@ int main(int argc, char **argv) {
} }
if (myArgs.showTrace) { if (myArgs.showTrace) {
loggerSettings.showTrace.override(true); loggerSettings.showTrace.assign(true);
} }
Sync<State> state_; Sync<State> state_;
/* Start a collector thread per worker process. */ /* Start a collector thread per worker process. */
std::vector<Thread> threads; std::vector<std::thread> threads;
std::condition_variable wakeup; std::condition_variable wakeup;
for (size_t i = 0; i < myArgs.nrWorkers; i++) { for (size_t i = 0; i < myArgs.nrWorkers; i++) {
threads.emplace_back(std::bind(collector, std::ref(state_), std::ref(wakeup))); threads.emplace_back(collector, std::ref(state_), std::ref(wakeup));
} }
for (auto &thread : threads) for (auto &thread : threads)

View file

@ -27,15 +27,18 @@
#include <lix/libstore/store-api.hh> #include <lix/libstore/store-api.hh>
#include <lix/libexpr/symbol-table.hh> #include <lix/libexpr/symbol-table.hh>
#include <lix/libutil/types.hh> #include <lix/libutil/types.hh>
#include <lix/libutil/util.hh>
#include <lix/libexpr/value.hh> #include <lix/libexpr/value.hh>
#include <lix/libutil/terminal.hh>
#include <exception> #include <exception>
#include <map>
#include <memory>
#include <numeric> #include <numeric>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <vector>
#include "worker.hh" #include "worker.hh"
#include "drv.hh" #include "drv.hh"
@ -48,7 +51,7 @@ static nix::Value *releaseExprTopLevelValue(nix::EvalState &state,
nix::Value vTop; nix::Value vTop;
if (args.fromArgs) { if (args.fromArgs) {
nix::Expr &e = state.parseExprFromString( nix::Expr *e = state.parseExprFromString(
args.releaseExpr, state.rootPath(nix::CanonPath::fromCwd())); args.releaseExpr, state.rootPath(nix::CanonPath::fromCwd()));
state.eval(e, vTop); state.eval(e, vTop);
} else { } else {