Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Towards) stable C bindings for libutil, libexpr #8699

Merged
merged 70 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
4702317
libutil: add C bindings
yorickvP Jul 14, 2023
1d41600
libstore: add C bindings
yorickvP Jul 14, 2023
e76652a
libexpr: add C bindings
yorickvP Jul 14, 2023
748b322
nix_api_value: fix primop arity
puckipedia Jul 23, 2023
4a49361
nix_api_value: fix documentation for get_attr_byname
yorickvP Jul 27, 2023
c3b5b8e
nix_api_expr, store: fix minor documentation issues
yorickvP Jul 27, 2023
efcddcd
nix_api_external: fix missing void* self param
yorickvP Jul 27, 2023
1e583c4
nix_api_value: nix_{get,set}_double -> nix_{get,set}_float
yorickvP Jul 27, 2023
1777e4a
nix_api_store: add userdata param to nix_store_build
yorickvP Jul 27, 2023
aa85f7d
nix_api_expr: merge nix_parse_expr and nix_expr_eval, remove Expr
yorickvP Jul 27, 2023
022b918
nix_api_expr: remove bindingsbuilder refcounting
yorickvP Jul 27, 2023
bebee70
nix_api_external: own return strings on the nix side
yorickvP Jul 28, 2023
ded0ef6
nix_api_expr: switch to refcounting
yorickvP Jul 28, 2023
ada2af4
nix_api_expr: add nix_gc_now()
yorickvP Jul 28, 2023
866558a
nix_api_expr: add error handling to incref, decref
yorickvP Jul 28, 2023
b0741f7
external-api-doc: introduce and improve documentation
yorickvP Jul 30, 2023
f41a7e3
nix_err_code: do not fail
yorickvP Jul 30, 2023
e58a938
nix_api_expr, nix_api_util: slightly improve documentation
yorickvP Jul 31, 2023
e74d6c1
nix_api_expr: document nix_value_force
yorickvP Aug 3, 2023
f0afe7f
nix_api_util: throw nix::error instead of new nix::Error for null ctx's
yorickvP Aug 4, 2023
c48b9b8
nix_api_util: tests
yorickvP Aug 4, 2023
9cccb8b
nix_api_expr: always force values before giving them to the user
yorickvP Aug 7, 2023
e891aac
nix_api_value: add nix_get_attr_name_byidx get attr names without for…
yorickvP Aug 7, 2023
713f10a
nix_api_value: Add nix_register_primop to add builtins
yorickvP Aug 7, 2023
dc0f7d8
initPlugins: run nix_plugin_entry() on dlopen'd plugins
yorickvP Aug 7, 2023
df9401e
nix_api_store: add nix_init_plugins
yorickvP Aug 7, 2023
e642bbc
C API: move to src/lib*/c/
yorickvP Aug 7, 2023
3b41830
docs/external-api: write main page
yorickvP Aug 7, 2023
40f5d48
Apply documentation suggestions from code review
yorickvP Aug 28, 2023
5d82d6e
nix_api: fix missing includes in headers
yorickvP Aug 11, 2023
9d380c0
C API: clarify some documentation
yorickvP Aug 28, 2023
91e53de
C API: update README example
yorickvP Aug 28, 2023
e1bb799
C API: reformat according to proposed clang-format file
yorickvP Aug 28, 2023
9e423de
C API: update after rebase
yorickvP Aug 28, 2023
48aa575
primops: change to std::function, allowing the passing of user data
yorickvP Aug 28, 2023
3d79f38
C API: add user_data argument to nix_alloc_primop
yorickvP Aug 28, 2023
ab92502
C API: add a way to throw errors from primops
yorickvP Aug 28, 2023
c6e28d8
C API: fix: macos doesn't have std::bind_front
yorickvP Aug 28, 2023
550af11
String value refactor
jlesquembre Nov 28, 2023
46f5d0e
Apply suggestions from code review
jlesquembre Dec 6, 2023
41f1669
C API: add tests for libutil and libstore
jlesquembre Dec 14, 2023
5560196
C API: fix documentation build
jlesquembre Dec 14, 2023
ac3a9c6
C API: add nix_api_expr tests
jlesquembre Jan 3, 2024
92dacec
C API: Apply documentation suggestions
jlesquembre Jan 5, 2024
24604d0
C API: fix docs build after rebase
jlesquembre Jan 9, 2024
5356941
C API: rename State to EvalState
jlesquembre Jan 10, 2024
d5ec1d0
C API: nix_store_open, check for empty strings
jlesquembre Jan 11, 2024
415583a
C API: use bool argument consistently
jlesquembre Jan 11, 2024
51ff547
C API: add more tests to nix_api_expr
jlesquembre Jan 15, 2024
dfdb90d
C API: Consolidate initializers
jlesquembre Feb 21, 2024
24c8f68
C API: if store doesn't have a version, return an empty string
jlesquembre Feb 21, 2024
b9cd24a
C API: fix api_expr tests
jlesquembre Feb 22, 2024
6c231dc
C API: disable test
jlesquembre Feb 24, 2024
2349185
C API: fix after rebase
jlesquembre Feb 24, 2024
7c602d9
C API: add tests for external values
jlesquembre Feb 24, 2024
c49b88b
C API: update docs based on PR feedback
jlesquembre Feb 25, 2024
693e8ec
C API: unify makefile after rebase
jlesquembre Feb 25, 2024
2e1dbbe
C API: refactor test support
jlesquembre Feb 25, 2024
1093ab6
C API: add more tests
jlesquembre Feb 25, 2024
34d15e8
C API: rename nix_store_build -> nix_store_realise
jlesquembre Feb 27, 2024
1a574c6
C API: refactor ListBuilder
jlesquembre Feb 27, 2024
31fbb24
C API: refactor nix_store_realise
jlesquembre Feb 29, 2024
940ff65
C API: update libstore tests
jlesquembre Feb 29, 2024
d96b52b
C api: nix_export_std_string -> nix_observe_string
jlesquembre Mar 27, 2024
c57de60
C API: Keep the structure flat
jlesquembre Mar 28, 2024
925a8fd
C API: Use new ListBuilder helper
jlesquembre Mar 28, 2024
061140f
C API: remove unused argument
jlesquembre Mar 28, 2024
2bb609b
C API: rename nix_observe_string -> nix_get_string_callback
jlesquembre Mar 29, 2024
2d84433
C API: update documentation
jlesquembre Mar 29, 2024
926fbad
C API: add more tests
jlesquembre Mar 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ makefiles = \
src/libexpr/local.mk \
src/libcmd/local.mk \
src/nix/local.mk \
src/libutil-c/local.mk \
src/libstore-c/local.mk \
src/libexpr-c/local.mk \
src/resolve-system-dependencies/local.mk \
scripts/local.mk \
misc/bash/local.mk \
misc/fish/local.mk \
misc/zsh/local.mk \
misc/systemd/local.mk \
misc/launchd/local.mk \
misc/upstart/local.mk
misc/upstart/local.mk \
doc/manual/local.mk \
doc/internal-api/local.mk \
doc/external-api/local.mk
endif

ifeq ($(ENABLE_UNIT_TESTS), yes)
Expand Down Expand Up @@ -59,6 +65,10 @@ ifeq ($(ENABLE_INTERNAL_API_DOCS), yes)
makefiles-late += doc/internal-api/local.mk
endif

ifeq ($(ENABLE_EXTERNAL_API_DOCS), yes)
makefiles-late += doc/external-api/local.mk
endif

# Miscellaneous global Flags

OPTIMIZE = 1
Expand Down Expand Up @@ -123,3 +133,10 @@ internal-api-html:
@echo "Internal API docs are disabled. Configure with '--enable-internal-api-docs', or avoid calling 'make internal-api-html'."
@exit 1
endif

ifneq ($(ENABLE_EXTERNAL_API_DOCS), yes)
.PHONY: external-api-html
external-api-html:
@echo "External API docs are disabled. Configure with '--enable-external-api-docs', or avoid calling 'make external-api-html'."
@exit 1
endif
1 change: 1 addition & 0 deletions Makefile.config.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ENABLE_BUILD = @ENABLE_BUILD@
ENABLE_DOC_GEN = @ENABLE_DOC_GEN@
ENABLE_FUNCTIONAL_TESTS = @ENABLE_FUNCTIONAL_TESTS@
ENABLE_INTERNAL_API_DOCS = @ENABLE_INTERNAL_API_DOCS@
ENABLE_EXTERNAL_API_DOCS = @ENABLE_EXTERNAL_API_DOCS@
ENABLE_S3 = @ENABLE_S3@
ENABLE_UNIT_TESTS = @ENABLE_UNIT_TESTS@
GTEST_LIBS = @GTEST_LIBS@
Expand Down
9 changes: 9 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ AC_ARG_ENABLE(unit-tests, AS_HELP_STRING([--disable-unit-tests],[Do not build th
ENABLE_UNIT_TESTS=$enableval, ENABLE_UNIT_TESTS=$ENABLE_BUILD)
AC_SUBST(ENABLE_UNIT_TESTS)

# Build external API docs by default
AC_ARG_ENABLE(external_api_docs, AS_HELP_STRING([--enable-external-api-docs],[Build API docs for Nix's C interface]),
external_api_docs=$enableval, external_api_docs=yes)
AC_SUBST(external_api_docs)

AS_IF(
[test "$ENABLE_BUILD" == "no" && test "$ENABLE_UNIT_TESTS" == "yes"],
[AC_MSG_ERROR([Cannot enable unit tests when building overall is disabled. Please do not pass '--enable-unit-tests' or do not pass '--disable-build'.])])
Expand All @@ -172,6 +177,10 @@ AC_ARG_ENABLE(internal-api-docs, AS_HELP_STRING([--enable-internal-api-docs],[Bu
ENABLE_INTERNAL_API_DOCS=$enableval, ENABLE_INTERNAL_API_DOCS=no)
AC_SUBST(ENABLE_INTERNAL_API_DOCS)

AC_ARG_ENABLE(external-api-docs, AS_HELP_STRING([--enable-external-api-docs],[Build API docs for Nix's external unstable C interfaces]),
ENABLE_EXTERNAL_API_DOCS=$enableval, ENABLE_EXTERNAL_API_DOCS=no)
AC_SUBST(ENABLE_EXTERNAL_API_DOCS)

AS_IF(
[test "$ENABLE_FUNCTIONAL_TESTS" == "yes" || test "$ENABLE_DOC_GEN" == "yes"],
[NEED_PROG(jq, jq)])
Expand Down
3 changes: 3 additions & 0 deletions doc/external-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/doxygen.cfg
/html
/latex
108 changes: 108 additions & 0 deletions doc/external-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Getting started

> **Warning** These bindings are **experimental**, which means they can change
> at any time or be removed outright; nevertheless the plan is to provide a
> stable external C API to the Nix language and the Nix store.

The language library allows evaluating Nix expressions and interacting with Nix
language values. The Nix store API is still rudimentary, and only allows
initialising and connecting to a store for the Nix language evaluator to
interact with.

Currently there are two ways to interface with the Nix language evaluator
programmatically:

1. Embedding the evaluator
2. Writing language plug-ins

Embedding means you link the Nix C libraries in your program and use them from
there. Adding a plug-in means you make a library that gets loaded by the Nix
language evaluator, specified through a configuration option.

Many of the components and mechanisms involved are not yet documented, therefore
please refer to the [Nix source code](https://github.com/NixOS/nix/) for
details. Additions to in-code documentation and the reference manual are highly
appreciated.

The following examples, for simplicity, don't include error handling. See the
[Handling errors](@ref errors) section for more information.

# Embedding the Nix Evaluator

In this example we programmatically start the Nix language evaluator with a
dummy store (that has no store paths and cannot be written to), and evaluate the
Nix expression `builtins.nixVersion`.

**main.c:**

```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>
#include <stdio.h>

// NOTE: This example lacks all error handling. Production code must check for
// errors, as some return values will be undefined.
int main() {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int main() {
// NOTE: This example lacks all error handling. Production code must check for errors, as some return values will be undefined.
int main() {

nix_libexpr_init(NULL);

Store* store = nix_store_open(NULL, "dummy://", NULL);
EvalState* state = nix_state_create(NULL, NULL, store); // empty search path (NIX_PATH)
Value *value = nix_alloc_value(NULL, state);

nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
nix_value_force(NULL, state, value);
printf("Nix version: %s\n", nix_get_string(NULL, value));

nix_gc_decref(NULL, value);
nix_state_free(state);
nix_store_free(store);
return 0;
}
```

**Usage:**

```ShellSession
$ gcc main.c $(pkg-config nix-expr-c --libs --cflags) -o main
$ ./main
Nix version: 2.17
```

# Writing a Nix language plug-in

In this example we add a custom primitive operation (_primop_) to `builtins`. It
will increment the argument if it is an integer and throw an error otherwise.

yorickvP marked this conversation as resolved.
Show resolved Hide resolved
**plugin.c:**

```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>

void increment(void* user_data, nix_c_context* ctx, EvalState* state, Value** args, Value* v) {
nix_value_force(NULL, state, args[0]);
if (nix_get_type(NULL, args[0]) == NIX_TYPE_INT) {
nix_init_int(NULL, v, nix_get_int(NULL, args[0]) + 1);
} else {
nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "First argument should be an integer.");
}
}

void nix_plugin_entry() {
const char* args[] = {"n", NULL};
PrimOp *p = nix_alloc_primop(NULL, increment, 1, "increment", args, "Example custom built-in function: increments an integer", NULL);
nix_register_primop(NULL, p);
nix_gc_decref(NULL, p);
}
```

**Usage:**

```ShellSession
$ gcc plugin.c $(pkg-config nix-expr-c --libs --cflags) -shared -o plugin.so
$ nix --plugin-files ./plugin.so repl
nix-repl> builtins.increment 1
2
```
57 changes: 57 additions & 0 deletions doc/external-api/doxygen.cfg.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Doxyfile 1.9.5

# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME = "Nix"

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = @PACKAGE_VERSION@

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF = "Nix, the purely functional package manager: C API (experimental)"

# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.

GENERATE_LATEX = NO

# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

# FIXME Make this list more maintainable somehow. We could maybe generate this
# in the Makefile, but we would need to change how `.in` files are preprocessed
# so they can expand variables despite configure variables.

INPUT = \
src/libutil-c \
src/libexpr-c \
src/libstore-c \
doc/external-api/README.md

FILE_PATTERNS = nix_api_*.h *.md

# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.

INCLUDE_PATH = @RAPIDCHECK_HEADERS@
EXCLUDE_PATTERNS = *_internal.h
GENERATE_TREEVIEW = YES
OPTIMIZE_OUTPUT_FOR_C = YES

USE_MDFILE_AS_MAINPAGE = doc/external-api/README.md
7 changes: 7 additions & 0 deletions doc/external-api/local.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$(docdir)/external-api/html/index.html $(docdir)/external-api/latex: $(d)/doxygen.cfg
mkdir -p $(docdir)/external-api
{ cat $< ; echo "OUTPUT_DIRECTORY=$(docdir)/external-api" ; } | doxygen -

# Generate the HTML API docs for Nix's unstable C bindings
.PHONY: external-api-html
external-api-html: $(docdir)/external-api/html/index.html
7 changes: 7 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@
enableInternalAPIDocs = true;
};

# API docs for Nix's C bindings.
external-api-docs = nixpkgsFor.x86_64-linux.native.callPackage ./package.nix {
inherit fileset;
doBuild = false;
enableExternalAPIDocs = true;
};

# System tests.
tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // {

Expand Down
2 changes: 1 addition & 1 deletion local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ GLOBAL_CXXFLAGS += -Wno-deprecated-declarations -Werror=switch
# Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers.
ERROR_SWITCH_ENUM = -Werror=switch-enum

$(foreach i, config.h $(wildcard src/lib*/*.hh), \
$(foreach i, config.h $(wildcard src/lib*/*.hh) $(wildcard src/lib*/*.h $(filter-out %_internal.h, $(wildcard src/lib*c/*.h))), \
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))

$(GCH): src/libutil/util.hh config.h
Expand Down
19 changes: 15 additions & 4 deletions package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
, autoreconfHook
, aws-sdk-cpp
, boehmgc
, buildPackages
, nlohmann_json
, bison
, boost
Expand Down Expand Up @@ -91,9 +92,10 @@
# - readline
, readlineFlavor ? if stdenv.hostPlatform.isWindows then "readline" else "editline"

# Whether to build the internal API docs, can be done separately from
# Whether to build the internal/external API docs, can be done separately from
# everything else.
, enableInternalAPIDocs ? false
, enableExternalAPIDocs ? false

# Whether to install unit tests. This is useful when cross compiling
# since we cannot run them natively during the build, but can do so
Expand Down Expand Up @@ -182,6 +184,9 @@ in {
./doc/manual
] ++ lib.optionals enableInternalAPIDocs [
./doc/internal-api
] ++ lib.optionals enableExternalAPIDocs [
./doc/external-api
] ++ lib.optionals (enableInternalAPIDocs || enableExternalAPIDocs) [
# Source might not be compiled, but still must be available
# for Doxygen to gather comments.
./src
Expand All @@ -199,7 +204,7 @@ in {
++ lib.optional doBuild "dev"
# If we are doing just build or just docs, the one thing will use
# "out". We only need additional outputs if we are doing both.
++ lib.optional (doBuild && (enableManual || enableInternalAPIDocs)) "doc"
++ lib.optional (doBuild && (enableManual || enableInternalAPIDocs || enableExternalAPIDocs)) "doc"
++ lib.optional installUnitTests "check";

nativeBuildInputs = [
Expand All @@ -221,7 +226,7 @@ in {
] ++ lib.optionals (doInstallCheck || enableManual) [
jq # Also for custom mdBook preprocessor.
] ++ lib.optional stdenv.hostPlatform.isLinux util-linux
++ lib.optional enableInternalAPIDocs doxygen
++ lib.optional (enableInternalAPIDocs || enableExternalAPIDocs) doxygen
;

buildInputs = lib.optionals doBuild [
Expand Down Expand Up @@ -285,6 +290,7 @@ in {
(lib.enableFeature buildUnitTests "unit-tests")
(lib.enableFeature doInstallCheck "functional-tests")
(lib.enableFeature enableInternalAPIDocs "internal-api-docs")
(lib.enableFeature enableExternalAPIDocs "external-api-docs")
(lib.enableFeature enableManual "doc-gen")
(lib.enableFeature enableGC "gc")
(lib.enableFeature enableMarkdown "markdown")
Expand All @@ -309,7 +315,8 @@ in {
makeFlags = "profiledir=$(out)/etc/profile.d PRECOMPILE_HEADERS=1";

installTargets = lib.optional doBuild "install"
++ lib.optional enableInternalAPIDocs "internal-api-html";
++ lib.optional enableInternalAPIDocs "internal-api-html"
++ lib.optional enableExternalAPIDocs "external-api-html";

installFlags = "sysconfdir=$(out)/etc";

Expand All @@ -336,6 +343,10 @@ in {
'' + lib.optionalString enableInternalAPIDocs ''
mkdir -p ''${!outputDoc}/nix-support
echo "doc internal-api-docs $out/share/doc/nix/internal-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
''
+ lib.optionalString enableExternalAPIDocs ''
mkdir -p ''${!outputDoc}/nix-support
echo "doc external-api-docs $out/share/doc/nix/external-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
'';

# So the check output gets links for DLLs in the out output.
Expand Down
25 changes: 25 additions & 0 deletions src/libexpr-c/local.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
libraries += libexprc

libexprc_NAME = libnixexprc

libexprc_DIR := $(d)

libexprc_SOURCES := \
$(wildcard $(d)/*.cc) \

# Not just for this library itself, but also for downstream libraries using this library

INCLUDE_libexprc := -I $(d)
libexprc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
$(INCLUDE_libfetchers) \
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
$(INCLUDE_libexpr) $(INCLUDE_libexprc)

libexprc_LIBS = libutil libutilc libstore libstorec libexpr

libexprc_LDFLAGS += -pthread

$(eval $(call install-file-in, $(d)/nix-expr-c.pc, $(libdir)/pkgconfig, 0644))

libexprc_FORCE_INSTALL := 1

10 changes: 10 additions & 0 deletions src/libexpr-c/nix-expr-c.pc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@

Name: Nix
Description: Nix Language Evaluator - C API
Version: @PACKAGE_VERSION@
Requires: nix-store-c
Libs: -L${libdir} -lnixexprc
Cflags: -I${includedir}/nix
Loading
Loading