diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml index 04a764a6d..1ccadacc4 100644 --- a/doc/manual/change-authors.yml +++ b/doc/manual/change-authors.yml @@ -336,5 +336,8 @@ yorickvp: yshui: github: yshui +ysndr: + github: ysndr + zimbatm: github: zimbatm diff --git a/doc/manual/rl-next/nej-apply-flag.md b/doc/manual/rl-next/nej-apply-flag.md new file mode 100644 index 000000000..1cd998dc0 --- /dev/null +++ b/doc/manual/rl-next/nej-apply-flag.md @@ -0,0 +1,12 @@ +--- +synopsis: "nix-eval-jobs support `--apply` flag" +cls: [5748] +category: "Features" +credits: [isabelroses,mic92,ysndr] +issues: [fj#1214] +--- + +`nix-eval-jobs` now supports the `--apply` flag. With this you can apply the +provided function to the each derivation, the result of this function will then +be serialized as a JSON value and stored inside `"extraValue"` key of the json +line output. diff --git a/subprojects/nix-eval-jobs/README.md b/subprojects/nix-eval-jobs/README.md index a0903a53b..36bdafbbb 100644 --- a/subprojects/nix-eval-jobs/README.md +++ b/subprojects/nix-eval-jobs/README.md @@ -46,6 +46,7 @@ executable. $ nix-eval-jobs --help USAGE: nix-eval-jobs [options] expr + --apply Apply provided Nix function to each derivation. The result of this function will be serialized as a JSON value and stored inside `"extraValue"` key of the json line output. --arg Pass the value *expr* as the argument *name* to Nix functions. --argstr Pass the string *string* as the argument *name* to Nix functions. --check-cache-status Check if the derivations are present locally or in any configured substituters (i.e. binary cache). The information will be exposed in the `isCached` field of the JSON output. diff --git a/subprojects/nix-eval-jobs/src/eval-args.cc b/subprojects/nix-eval-jobs/src/eval-args.cc index 9a90ddff3..7261eb9cd 100644 --- a/subprojects/nix-eval-jobs/src/eval-args.cc +++ b/subprojects/nix-eval-jobs/src/eval-args.cc @@ -12,7 +12,8 @@ #include "eval-args.hh" -MyArgs::MyArgs(nix::AsyncIoRoot & aio) : MixCommonArgs("nix-eval-jobs"), aio_(aio) { +MyArgs::MyArgs(nix::AsyncIoRoot &aio) + : MixCommonArgs("nix-eval-jobs"), aio_(aio) { addFlag({ .longName = "help", .description = "show usage information", @@ -22,7 +23,8 @@ MyArgs::MyArgs(nix::AsyncIoRoot & aio) : MixCommonArgs("nix-eval-jobs"), aio_(ai if (flag->hidden || hiddenCategories.count(flag->category)) { continue; } - std::cout << nix::fmt(" --%-20s %s\n", name, flag->description); + std::cout << nix::fmt(" --%-20s %s\n", name, + flag->description); } ::exit(0); }}, @@ -105,6 +107,14 @@ MyArgs::MyArgs(nix::AsyncIoRoot & aio) : MixCommonArgs("nix-eval-jobs"), aio_(ai .description = "treat the argument as a Nix expression", .handler = {&fromArgs, true}}); + addFlag({.longName = "apply", + .description = + "Apply provided Nix function to each derivation. The result " + "of this function will be serialized as a JSON value and " + "stored inside `\"extraValue\"` key of the json line output.", + .labels = {"expr"}, + .handler = {&applyExpr}}); + // usually in MixFlakeOptions addFlag({ .longName = "override-input", diff --git a/subprojects/nix-eval-jobs/src/eval-args.hh b/subprojects/nix-eval-jobs/src/eval-args.hh index fd197169f..19d3862f0 100644 --- a/subprojects/nix-eval-jobs/src/eval-args.hh +++ b/subprojects/nix-eval-jobs/src/eval-args.hh @@ -22,6 +22,7 @@ struct MyArgs : virtual public nix::MixEvalArgs, bool worker = false; std::string releaseExpr; + std::string applyExpr; nix::Path gcRootsDir; bool flake = false; bool fromArgs = false; diff --git a/subprojects/nix-eval-jobs/src/worker.cc b/subprojects/nix-eval-jobs/src/worker.cc index 6efab21d3..9fe289fd2 100644 --- a/subprojects/nix-eval-jobs/src/worker.cc +++ b/subprojects/nix-eval-jobs/src/worker.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -202,6 +203,21 @@ try { maybeConstituents = readConstituents(&v, state, evaluator); } + if (args.applyExpr != "") { + nix::Expr &applyExpr = state->ctx.parseExprFromString( + args.applyExpr, nix::CanonPath::fromCwd()); + + nix::Value vApply = state->eval(applyExpr); + nix::Value vRes = + state->callFunction(vApply, v, nix::noPos); + state->forceAttrs( + vRes, nix::noPos, + "apply needs to evaluate to an attrset"); + + nix::NixStringContext context; + reply["extraValue"] = nix::printValueAsJSON( + *state, true, vRes, nix::noPos, context); + } auto drv = Drv(attrPathS, *state, *drvInfo, args, maybeConstituents); reply.update(drv); diff --git a/subprojects/nix-eval-jobs/tests/test_eval.py b/subprojects/nix-eval-jobs/tests/test_eval.py index 86d97360c..021cff1ef 100755 --- a/subprojects/nix-eval-jobs/tests/test_eval.py +++ b/subprojects/nix-eval-jobs/tests/test_eval.py @@ -124,6 +124,45 @@ def test_eval_error() -> None: assert "this is an evaluation error" in attr["error"] +def test_apply() -> None: + with TemporaryDirectory() as tempdir: + applyExpr = """drv: { + the-name = drv.name; + version = drv.version or null; + }""" + + results, _ = evaluate( + tempdir, + 0, + ["--workers", "1", "--apply", applyExpr, "--flake", ".#hydraJobs"], + ) + + assert len(results) == 4 # sanity check that we assert against all jobs + + # Check that nix-eval-jobs applied the expression correctly, + # exposing the result under the `extraValue` key, and extracted + # 'version' as 'version' and 'name' as 'the-name' + dotted_job = results[0] + assert dotted_job["attr"] == '"dotted.attr"' + assert dotted_job["extraValue"]["the-name"].startswith("hello-") + assert dotted_job["extraValue"]["version"] is not None + + built_job = results[1] + assert built_job["attr"] == "builtJob" + assert built_job["extraValue"]["the-name"] == "job1" + assert built_job["extraValue"]["version"] is None + + recurse_drv = results[2] + assert recurse_drv["attr"] == "recurse.drvB" + assert recurse_drv["extraValue"]["the-name"] == "drvB" + assert recurse_drv["extraValue"]["version"] is None + + substituted_job = results[3] + assert substituted_job["attr"] == "substitutedJob" + assert substituted_job["extraValue"]["the-name"].startswith("hello-") + assert substituted_job["extraValue"]["version"] is not None + + @pytest.mark.infiniterecursion def test_recursion_error() -> None: with TemporaryDirectory() as tempdir: