Skip to content

libexpr-c: expose essential evaluator functions for derivation tree inspection - #15842

Merged
xokdvium merged 5 commits into
NixOS:masterfrom
NotAShelf:master
Jul 2, 2026
Merged

libexpr-c: expose essential evaluator functions for derivation tree inspection#15842
xokdvium merged 5 commits into
NixOS:masterfrom
NotAShelf:master

Conversation

@NotAShelf

@NotAShelf NotAShelf commented May 12, 2026

Copy link
Copy Markdown
Member

Motivation

I maintain a set of Rust bindings for Nix that interact with the evaluator
through FFI. While looking to create a nix-eval-jobs replacement I've noticed
that it depends heavily on C++ APIs that are not a part of stable C bindings.
This effectively blocks efforts to build external tooling (such as but not
limited to a Rust-based nix-eval-jobs replacement) that interacts with the
evaluator through FFI. Furthermore, other external tools also call internal C++
APIs like nix::getDerivation, nix::findAlongAttrPath,
EvalState::autoCallFunction, and nix::printValueAsJSON because the stable C
bindings lack equivalents. This forces consumers to link against unstable
headers that shift between Nix versions, which I find to be making maintenance
brittle. In the light of recent work on the C API, expanding the C API surface
to cover these four operations allows downstream tooling to migrate to stable
bindings and, in turn, enables pure-FFI consumers without any new internal
linkage. This is not all, of course, but I think it's a good start.

Context

nix-eval-jobs (src/worker.cc, src/drv.cc) is the canonical consumer. It
evaluates Nix attribute sets in parallel, inspecting each value to determine
whether it is a derivation (recurse vs. emit JSON) and serializing the result.
Conversely, every other piece of derivation metadata nix-eval-jobs extracts
(name, system, output names, meta) is a plain attribute lookup on the forced
attrset, already covered by nix_get_attr_byname, nix_get_string,
nix_get_attrs_size, and nix_get_attr_name_byidx.

This is a non-trivial change (obviously, new public API surface) but not
invasive as no existing behavior is altered. 250~ lines added across one new .cc
file and one small addition to the public header. My implementation strategy is
relevatively simple: four new functions now live in a new nix_api_eval.cc
translation unit that was also added to meson.build. Each follows the
established C API conventions; error reporting through nix_c_context with
last_err_code, EvalState* wrapping the internal nix::EvalState&, and
NIXC_CATCH_ERRS / NIXC_CATCH_ERRS_NULL macros for exception-to-error-code
translation.

Initially I've wrapped nix::PackageInfo in an opaque nix_package_info handle
and exposed typed query functions (queryName, querySystem, queryOutputs,
queryMeta, etc.). I've since dropped this approach because every one of those
accessors reduces to an attrset key lookup on the forced derivation value. Those
are operations the existing C API (nix_get_attr_byname, nix_get_string,
nix_get_attrs_size) already supports. Pulling PackageInfo into the stable
ABI would also couple the C API to a C++ type whose signature can drift between
Nix versions, which I think would be defeating the purpose of the stable
bindings. The latest design I've settled on
keepsnix_get_derivationreturning a plainStorePath*`, and leaves derivation
introspection to the caller via the attrset API.


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@NotAShelf
NotAShelf requested a review from edolstra as a code owner May 12, 2026 07:39
@github-actions github-actions Bot added the c api Nix as a C library with a stable interface label May 12, 2026
@NotAShelf

NotAShelf commented May 12, 2026

Copy link
Copy Markdown
Member Author

Uh, for some reason the PR form discarded all of my text...

Edit: fixed, but I have no idea why that happened.

@NotAShelf NotAShelf changed the title Expose essential evaluator functions for derivation tree inspection ibexpr-c: expose essential evaluator functions for derivation tree inspection May 12, 2026
@NotAShelf NotAShelf changed the title ibexpr-c: expose essential evaluator functions for derivation tree inspection libexpr-c: expose essential evaluator functions for derivation tree inspection May 12, 2026
Comment thread src/libexpr-c/nix_api_expr.h Outdated
@xokdvium

Copy link
Copy Markdown
Contributor

Tbh I'm not too sure why this would be needed - i.e. afaict all of this can be implemented on the other side of the bindings in a more flexible manner? There's some argument to be made to keeping to bindings minimal while providing all the necessary facilities - the larger the interface surface - the larger the maintenance burden and less flexibility we have to change the C++ code. Is it particularly problematic / a lot of code to implement via existing bindings?

@roberth roberth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing some work on generalizing our evaluator interfaces, and interestingly, these functions...

nix::getDerivation, nix::findAlongAttrPath,
EvalState::autoCallFunction,

I wouldn't classify them as part of the evaluator. They're helpers for users of the evaluator that are not called by the evaluator itself.

Now that's currently "just semantics", but not in the context of my refactor. I guess this does reinforce that the C API should connect to the new evaluator interface, not just nix::EvalState.

Anyway, don't worry about it, I'll get back with an actual review.

Comment thread src/libexpr-c/nix_api_eval.cc Outdated
@NotAShelf

Copy link
Copy Markdown
Member Author

Thank you both for the comments. I might have overstepped a little bit. The additions were a direct mapping of what nix-eval-jobs actually calls today through internal headers, which I guess was the wrong instinct for a stable API. Asking myself the correct question ("which of those calls cannot be reproduced from the other side.") and testing a bit further with my patch applied, I'm dropping the redundant helpers.

@roberth re: the evaluator interface refactor - Good to know, and I agree these sit in the "user of the evaluator" layer rather than the evaluator proper. Whichever interface they land behind after the refactor, the C bindings just need to delegate to it; the shape of the public function shouldn't change. This is, of course, under your jurisdiction. I'll make the changes as necessary.

@NotAShelf
NotAShelf force-pushed the master branch 2 times, most recently from 0036198 to 10f09d2 Compare May 14, 2026 06:59
@nixos-discourse

Copy link
Copy Markdown

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/gradient-call-for-testers/77549/10

Comment thread src/libexpr-c/nix_api_expr.h Outdated
@NotAShelf

Copy link
Copy Markdown
Member Author

Is there anything else to do on my side? I'd like to get this in before there are more merge conflicts if possible, and provide the necessary APIs for downstream consumers :')

@DerDennisOP

Copy link
Copy Markdown

Is there any reason this can't be merged? I would really like to use this improvement.

@DerDennisOP

Copy link
Copy Markdown

How is this suppose to work? @xokdvium the work has been done, it's ready to be merged / reviewed once more. I can see that there are a lot other PRs open, but I would really appreciate if we could get this done sooner or later.

@xokdvium xokdvium left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd also have accompanying unit tests for every single function (and hopefully all code paths/error handling).

Also, we shouldn't really refer to the C++ API and document the semantic of this function separate from the C++ counterpart, since the latter is more of an internal thing.

Comment thread src/libexpr-c/nix_api_expr.h Outdated
@DerDennisOP

Copy link
Copy Markdown

@xokdvium pls help us out 🙏 thank you :)

@NotAShelf

Copy link
Copy Markdown
Member Author

I've tried to re-trigged the Windows CI, but don't think it's my fault it's failing. Seems to be a GH perms issue...

@xokdvium

Copy link
Copy Markdown
Contributor

This is just GHA being flaky :(

Comment thread src/libexpr-c/nix_api_eval.cc Outdated
@DerDennisOP

Copy link
Copy Markdown

@xokdvium can you please help us out again, we kinda need to get this moving for a few projects.

@NotAShelf

Copy link
Copy Markdown
Member Author

@xokdvium I hate to ping for this, but I'd like to get this PR moving again. It's generally a trivial addition, and I'm happy to get blockers out of the way. There are a few projects depending on this PR being merged, and the only alternative I can offer currently is horrendous.

Please let me know what needs to be done to get this moving again.

@xokdvium xokdvium left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, too much stuff :)

Left a few comments.

Another thing is release notes, but I'll just have to write those manually in #16051.

Would you like this in 2.35?

Comment thread src/libexpr-tests/nix_api_eval.cc Outdated
TEST_F(nix_api_expr_test, nix_get_derivation_returns_drv_path)
{
auto expr = R"(derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; })";
nix_expr_eval_from_string(ctx, state, expr, ".", value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that it would be great to be able to specify std::nullopt as the base empty from which the evaluation happens. "." - current working directory is a huge wart, but that's more of a C++ part concern.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this one open because I think it's out of scope for this PR. If you'd like me to address it anyway I can send a followup PR.

Comment thread src/libexpr-tests/nix_api_eval.cc Outdated
Comment thread src/libexpr-c/nix_api_expr.h Outdated
Comment thread src/libexpr-c/nix_api_expr.h Outdated
@NotAShelf

Copy link
Copy Markdown
Member Author

I'd really appreciate this for 2.35, yes. I can update my bindings in short notice if that's possible. Will be addressing your comments shortly, thank you for the feedback.

@xokdvium

Copy link
Copy Markdown
Contributor

@NotAShelf, did you forget to push the update?

@NotAShelf

Copy link
Copy Markdown
Member Author

Looks like I have, oops. Pushed.

…ion`

These encapsulate internal evaluator machinery that cannot be replicated
through the existing attrset/value access API, which makes them the bare
minimum necessary surface for consuemers wanting to to traverse and
inspect derivation trees.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1f6aa9222083068300de22e3f6aad3ac6a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ib3d476dc41ab653f151e00ef95ba3bd56a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia5e34d12e016a1670b3b64602b13a5256a6a6964
Comment thread src/libexpr-c/nix_api_eval.cc Outdated
Comment thread src/libexpr-c/nix_api_eval.cc Outdated
Ensure `nix_value_auto_call_function` treats `NULL` auto_args as empty
arguments, but forces any provided value and requires it to be an 
attribute set.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I17e494abe78f9216dd6346febd784bc26a6a6964
@NotAShelf

Copy link
Copy Markdown
Member Author

Both good catches, thank you. I've changed nix_api_eval.cc as requested:

  • thunked auto_args is forced and used
  • non-attrset auto_args errors

I've also self-reviewed the PR again after my changes so that I don't miss such oversights in the future.

Comment thread src/libexpr-c/nix_api_eval.cc Outdated
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia8cc4c1b4f78fcacac4334cd8b2d441f6a6a6964

@xokdvium xokdvium left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think I've tortured you enough (sorry, I didn't mean to!).

@xokdvium
xokdvium added this pull request to the merge queue Jul 2, 2026
Merged via the queue into NixOS:master with commit b41781e Jul 2, 2026
16 checks passed
@NotAShelf

Copy link
Copy Markdown
Member Author

It's alright, I appreciate you beating this PR into shape o7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c api Nix as a C library with a stable interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants