diff --git a/Makefile b/Makefile index a33b8c458..9dc005f79 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,9 @@ 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 \ @@ -25,7 +28,10 @@ makefiles = \ 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) @@ -60,6 +66,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 @@ -124,3 +134,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 diff --git a/Makefile.config.in b/Makefile.config.in index d5c382630..7f517898c 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -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@ diff --git a/configure.ac b/configure.ac index 676b145a5..1d327d51d 100644 --- a/configure.ac +++ b/configure.ac @@ -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'.])]) @@ -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)]) diff --git a/doc/external-api/.gitignore b/doc/external-api/.gitignore new file mode 100644 index 000000000..dab28b6b0 --- /dev/null +++ b/doc/external-api/.gitignore @@ -0,0 +1,3 @@ +/doxygen.cfg +/html +/latex diff --git a/doc/external-api/README.md b/doc/external-api/README.md new file mode 100644 index 000000000..8a6f1c085 --- /dev/null +++ b/doc/external-api/README.md @@ -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 +#include +#include +#include + +// 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. + +**plugin.c:** + +```C +#include +#include +#include + +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 +``` diff --git a/doc/external-api/doxygen.cfg.in b/doc/external-api/doxygen.cfg.in new file mode 100644 index 000000000..cd8b4989b --- /dev/null +++ b/doc/external-api/doxygen.cfg.in @@ -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 diff --git a/doc/external-api/local.mk b/doc/external-api/local.mk new file mode 100644 index 000000000..c739bdaf0 --- /dev/null +++ b/doc/external-api/local.mk @@ -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 diff --git a/flake.nix b/flake.nix index 89b928e83..be4e68783 100644 --- a/flake.nix +++ b/flake.nix @@ -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; } // { diff --git a/local.mk b/local.mk index 408a82327..67ec35dcd 100644 --- a/local.mk +++ b/local.mk @@ -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))) ifdef HOST_UNIX diff --git a/package.nix b/package.nix index a334ae48c..c9e50c399 100644 --- a/package.nix +++ b/package.nix @@ -5,6 +5,7 @@ , autoreconfHook , aws-sdk-cpp , boehmgc +, buildPackages , nlohmann_json , bison , boost @@ -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 @@ -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 @@ -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 = [ @@ -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 [ @@ -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") @@ -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"; @@ -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. diff --git a/src/libexpr-c/local.mk b/src/libexpr-c/local.mk new file mode 100644 index 000000000..ce5d321d6 --- /dev/null +++ b/src/libexpr-c/local.mk @@ -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 + diff --git a/src/libexpr-c/nix-expr-c.pc.in b/src/libexpr-c/nix-expr-c.pc.in new file mode 100644 index 000000000..06897064d --- /dev/null +++ b/src/libexpr-c/nix-expr-c.pc.in @@ -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 diff --git a/src/libexpr-c/nix_api_expr.cc b/src/libexpr-c/nix_api_expr.cc new file mode 100644 index 000000000..f18ef399b --- /dev/null +++ b/src/libexpr-c/nix_api_expr.cc @@ -0,0 +1,178 @@ +#include +#include +#include +#include + +#include "config.hh" +#include "eval.hh" +#include "globals.hh" +#include "util.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" + +#ifdef HAVE_BOEHMGC +#include +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +nix_err nix_libexpr_init(nix_c_context * context) +{ + if (context) + context->last_err_code = NIX_OK; + { + auto ret = nix_libutil_init(context); + if (ret != NIX_OK) + return ret; + } + { + auto ret = nix_libstore_init(context); + if (ret != NIX_OK) + return ret; + } + try { + nix::initGC(); + } + NIXC_CATCH_ERRS +} + +nix_err nix_expr_eval_from_string( + nix_c_context * context, EvalState * state, const char * expr, const char * path, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path))); + state->state.eval(parsedExpr, *(nix::Value *) value); + state->state.forceValue(*(nix::Value *) value, nix::noPos); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + state->state.callFunction(*(nix::Value *) fn, *(nix::Value *) arg, *(nix::Value *) value, nix::noPos); + state->state.forceValue(*(nix::Value *) value, nix::noPos); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + state->state.forceValue(*(nix::Value *) value, nix::noPos); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + state->state.forceValueDeep(*(nix::Value *) value); + } + NIXC_CATCH_ERRS +} + +EvalState * nix_state_create(nix_c_context * context, const char ** searchPath_c, Store * store) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::Strings searchPath; + if (searchPath_c != nullptr) + for (size_t i = 0; searchPath_c[i] != nullptr; i++) + searchPath.push_back(searchPath_c[i]); + + return new EvalState{nix::EvalState(nix::SearchPath::parse(searchPath), store->ptr)}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_state_free(EvalState * state) +{ + delete state; +} + +#ifdef HAVE_BOEHMGC +std::unordered_map< + const void *, + unsigned int, + std::hash, + std::equal_to, + traceable_allocator>> + nix_refcounts; + +std::mutex nix_refcount_lock; + +nix_err nix_gc_incref(nix_c_context * context, const void * p) +{ + if (context) + context->last_err_code = NIX_OK; + try { + std::scoped_lock lock(nix_refcount_lock); + auto f = nix_refcounts.find(p); + if (f != nix_refcounts.end()) { + f->second++; + } else { + nix_refcounts[p] = 1; + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_gc_decref(nix_c_context * context, const void * p) +{ + + if (context) + context->last_err_code = NIX_OK; + try { + std::scoped_lock lock(nix_refcount_lock); + auto f = nix_refcounts.find(p); + if (f != nix_refcounts.end()) { + if (--f->second == 0) + nix_refcounts.erase(f); + } else + throw std::runtime_error("nix_gc_decref: object was not referenced"); + } + NIXC_CATCH_ERRS +} + +void nix_gc_now() +{ + GC_gcollect(); +} + +#else +void nix_gc_incref(nix_c_context * context, const void *) +{ + if (context) + context->last_err_code = NIX_OK; + return NIX_OK; +} +void nix_gc_decref(nix_c_context * context, const void *) +{ + if (context) + context->last_err_code = NIX_OK; + return NIX_OK; +} +void nix_gc_now() {} +#endif + +void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd)) +{ +#ifdef HAVE_BOEHMGC + GC_REGISTER_FINALIZER(obj, finalizer, cd, 0, 0); +#endif +} diff --git a/src/libexpr-c/nix_api_expr.h b/src/libexpr-c/nix_api_expr.h new file mode 100644 index 000000000..7504b5d7a --- /dev/null +++ b/src/libexpr-c/nix_api_expr.h @@ -0,0 +1,213 @@ +#ifndef NIX_API_EXPR_H +#define NIX_API_EXPR_H +/** @defgroup libexpr libexpr + * @brief Bindings to the Nix language evaluator + * + * Example (without error handling): + * @code{.c} + * int main() { + * nix_libexpr_init(NULL); + * + * Store* store = nix_store_open(NULL, "dummy", NULL); + * EvalState* state = nix_state_create(NULL, NULL, store); // empty 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; + * } + * @endcode + * @{ + */ +/** @file + * @brief Main entry for the libexpr C bindings + */ + +#include "nix_api_store.h" +#include "nix_api_util.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Type definitions +/** + * @brief Represents a state of the Nix language evaluator. + * + * Multiple states can be created for multi-threaded + * operation. + * @struct EvalState + * @see nix_state_create + */ +typedef struct EvalState EvalState; // nix::EvalState +/** + * @brief Represents a value in the Nix language. + * + * Owned by the garbage collector. + * @struct Value + * @see value_manip + */ +typedef void Value; // nix::Value + +// Function prototypes +/** + * @brief Initialize the Nix language evaluator. + * + * This function must be called at least once, + * at some point before constructing a EvalState for the first time. + * This function can be called multiple times, and is idempotent. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization was successful, an error code otherwise. + */ +nix_err nix_libexpr_init(nix_c_context * context); + +/** + * @brief Parses and evaluates a Nix expression from a string. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in] expr The Nix expression to parse. + * @param[in] path The file path to associate with the expression. + * This is required for expressions that contain relative paths (such as `./.`) that are resolved relative to the given + * directory. + * @param[out] value The result of the evaluation. You must allocate this + * yourself. + * @return NIX_OK if the evaluation was successful, an error code otherwise. + */ +nix_err nix_expr_eval_from_string( + nix_c_context * context, EvalState * state, const char * expr, const char * path, Value * value); + +/** + * @brief Calls a Nix function with an argument. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in] fn The Nix function to call. + * @param[in] arg The argument to pass to the function. + * @param[out] value The result of the function call. + * @return NIX_OK if the function call was successful, an error code otherwise. + */ +nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value); + +/** + * @brief Forces the evaluation of a Nix value. + * + * The Nix interpreter is lazy, and not-yet-evaluated Values can be + * of type NIX_TYPE_THUNK instead of their actual value. + * + * This function converts these Values into their final type. + * + * @note You don't need this function for basic API usage, since all functions + * that return a value call it for you. The only place you will see a + * NIX_TYPE_THUNK is in the arguments that are passed to a PrimOp function + * you supplied to nix_alloc_primop. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in,out] value The Nix value to force. + * @post value is not of type NIX_TYPE_THUNK + * @return NIX_OK if the force operation was successful, an error code + * otherwise. + */ +nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * value); + +/** + * @brief Forces the deep evaluation of a Nix value. + * + * Recursively calls nix_value_force + * + * @see nix_value_force + * @warning Calling this function on a recursive data structure will cause a + * stack overflow. + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in,out] value The Nix value to force. + * @return NIX_OK if the deep force operation was successful, an error code + * otherwise. + */ +nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, Value * value); + +/** + * @brief Create a new Nix language evaluator state. + * + * @param[out] context Optional, stores error information + * @param[in] searchPath Array of strings corresponding to entries in NIX_PATH. + * @param[in] store The Nix store to use. + * @return A new Nix state or NULL on failure. + */ +EvalState * nix_state_create(nix_c_context * context, const char ** searchPath, Store * store); + +/** + * @brief Frees a Nix state. + * + * Does not fail. + * + * @param[in] state The state to free. + */ +void nix_state_free(EvalState * state); + +/** @addtogroup GC + * @brief Reference counting and garbage collector operations + * + * The Nix language evaluator uses a garbage collector. To ease C interop, we implement + * a reference counting scheme, where objects will be deallocated + * when there are no references from the Nix side, and the reference count kept + * by the C API reaches `0`. + * + * Functions returning a garbage-collected object will automatically increase + * the refcount for you. You should make sure to call `nix_gc_decref` when + * you're done with a value returned by the evaluator. + * @{ + */ +/** + * @brief Increment the garbage collector reference counter for the given object. + * + * The Nix language evaluator C API keeps track of alive objects by reference counting. + * When you're done with a refcounted pointer, call nix_gc_decref(). + * + * @param[out] context Optional, stores error information + * @param[in] object The object to keep alive + */ +nix_err nix_gc_incref(nix_c_context * context, const void * object); +/** + * @brief Decrement the garbage collector reference counter for the given object + * + * @param[out] context Optional, stores error information + * @param[in] object The object to stop referencing + */ +nix_err nix_gc_decref(nix_c_context * context, const void * object); + +/** + * @brief Trigger the garbage collector manually + * + * You should not need to do this, but it can be useful for debugging. + */ +void nix_gc_now(); + +/** + * @brief Register a callback that gets called when the object is garbage + * collected. + * @note Objects can only have a single finalizer. This function overwrites existing values + * silently. + * @param[in] obj the object to watch + * @param[in] cd the data to pass to the finalizer + * @param[in] finalizer the callback function, called with obj and cd + */ +void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd)); + +/** @} */ +// cffi end +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif // NIX_API_EXPR_H diff --git a/src/libexpr-c/nix_api_expr_internal.h b/src/libexpr-c/nix_api_expr_internal.h new file mode 100644 index 000000000..b50a51347 --- /dev/null +++ b/src/libexpr-c/nix_api_expr_internal.h @@ -0,0 +1,38 @@ +#ifndef NIX_API_EXPR_INTERNAL_H +#define NIX_API_EXPR_INTERNAL_H + +#include "eval.hh" +#include "attr-set.hh" +#include "nix_api_value.h" + +struct EvalState +{ + nix::EvalState state; +}; + +struct BindingsBuilder +{ + nix::BindingsBuilder builder; +}; + +struct ListBuilder +{ + nix::ListBuilder builder; +}; + +struct nix_string_return +{ + std::string str; +}; + +struct nix_printer +{ + std::ostream & s; +}; + +struct nix_string_context +{ + nix::NixStringContext & ctx; +}; + +#endif // NIX_API_EXPR_INTERNAL_H diff --git a/src/libexpr-c/nix_api_external.cc b/src/libexpr-c/nix_api_external.cc new file mode 100644 index 000000000..c237cfb70 --- /dev/null +++ b/src/libexpr-c/nix_api_external.cc @@ -0,0 +1,198 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#include "globals.hh" +#include "value.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_external.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_value.h" +#include "value/context.hh" + +#include + +#ifdef HAVE_BOEHMGC +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +void nix_set_string_return(nix_string_return * str, const char * c) +{ + str->str = c; +} + +nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * c) +{ + if (context) + context->last_err_code = NIX_OK; + try { + printer->s << c; + } + NIXC_CATCH_ERRS +} + +nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * ctx, const char * c) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto r = nix::NixStringContextElem::parse(c); + ctx->ctx.insert(r); + } + NIXC_CATCH_ERRS +} + +class NixCExternalValue : public nix::ExternalValueBase +{ + NixCExternalValueDesc & desc; + void * v; + +public: + NixCExternalValue(NixCExternalValueDesc & desc, void * v) + : desc(desc) + , v(v){}; + void * get_ptr() + { + return v; + } + /** + * Print out the value + */ + virtual std::ostream & print(std::ostream & str) const override + { + nix_printer p{str}; + desc.print(v, &p); + return str; + } + + /** + * Return a simple string describing the type + */ + virtual std::string showType() const override + { + nix_string_return res; + desc.showType(v, &res); + return std::move(res.str); + } + + /** + * Return a string to be used in builtins.typeOf + */ + virtual std::string typeOf() const override + { + nix_string_return res; + desc.typeOf(v, &res); + return std::move(res.str); + } + + /** + * Coerce the value to a string. + */ + virtual std::string coerceToString( + nix::EvalState & state, + const nix::PosIdx & pos, + nix::NixStringContext & context, + bool copyMore, + bool copyToStore) const override + { + if (!desc.coerceToString) { + return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore); + } + nix_string_context ctx{context}; + nix_string_return res{""}; + // todo: pos, errors + desc.coerceToString(v, &ctx, copyMore, copyToStore, &res); + if (res.str.empty()) { + return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore); + } + return std::move(res.str); + } + + /** + * Compare to another value of the same type. + */ + virtual bool operator==(const ExternalValueBase & b) const override + { + if (!desc.equal) { + return false; + } + auto r = dynamic_cast(&b); + if (!r) + return false; + return desc.equal(v, r->v); + } + + /** + * Print the value as JSON. + */ + virtual nlohmann::json printValueAsJSON( + nix::EvalState & state, bool strict, nix::NixStringContext & context, bool copyToStore = true) const override + { + if (!desc.printValueAsJSON) { + return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore); + } + nix_string_context ctx{context}; + nix_string_return res{""}; + desc.printValueAsJSON(v, (EvalState *) &state, strict, &ctx, copyToStore, &res); + if (res.str.empty()) { + return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore); + } + return nlohmann::json::parse(res.str); + } + + /** + * Print the value as XML. + */ + virtual void printValueAsXML( + nix::EvalState & state, + bool strict, + bool location, + nix::XMLWriter & doc, + nix::NixStringContext & context, + nix::PathSet & drvsSeen, + const nix::PosIdx pos) const override + { + if (!desc.printValueAsXML) { + return nix::ExternalValueBase::printValueAsXML(state, strict, location, doc, context, drvsSeen, pos); + } + nix_string_context ctx{context}; + desc.printValueAsXML( + v, (EvalState *) &state, strict, location, &doc, &ctx, &drvsSeen, + *reinterpret_cast(&pos)); + } + + virtual ~NixCExternalValue() override{}; +}; + +ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto ret = new +#ifdef HAVE_BOEHMGC + (GC) +#endif + NixCExternalValue(*desc, v); + nix_gc_incref(nullptr, ret); + return (ExternalValue *) ret; + } + NIXC_CATCH_ERRS_NULL +} + +void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto r = dynamic_cast((nix::ExternalValueBase *) b); + if (r) + return r->get_ptr(); + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} diff --git a/src/libexpr-c/nix_api_external.h b/src/libexpr-c/nix_api_external.h new file mode 100644 index 000000000..12ea00407 --- /dev/null +++ b/src/libexpr-c/nix_api_external.h @@ -0,0 +1,196 @@ +#ifndef NIX_API_EXTERNAL_H +#define NIX_API_EXTERNAL_H +/** @ingroup libexpr + * @addtogroup Externals + * @brief Deal with external values + * @{ + */ +/** @file + * @brief libexpr C bindings dealing with external values + */ + +#include "nix_api_expr.h" +#include "nix_api_util.h" +#include "nix_api_value.h" +#include "stdbool.h" +#include "stddef.h" +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** + * @brief Represents a string owned by the Nix language evaluator. + * @see nix_set_owned_string + */ +typedef struct nix_string_return nix_string_return; +/** + * @brief Wraps a stream that can output multiple string pieces. + */ +typedef struct nix_printer nix_printer; +/** + * @brief A list of string context items + */ +typedef struct nix_string_context nix_string_context; + +/** + * @brief Sets the contents of a nix_string_return + * + * Copies the passed string. + * @param[out] str the nix_string_return to write to + * @param[in] c The string to copy + */ +void nix_set_string_return(nix_string_return * str, const char * c); + +/** + * Print to the nix_printer + * + * @param[out] context Optional, stores error information + * @param printer The nix_printer to print to + * @param[in] str The string to print + * @returns NIX_OK if everything worked + */ +nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * str); + +/** + * Add string context to the nix_string_context object + * @param[out] context Optional, stores error information + * @param[out] string_context The nix_string_context to add to + * @param[in] c The context string to add + * @returns NIX_OK if everything worked + */ +nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * string_context, const char * c); + +/** + * @brief Definition for a class of external values + * + * Create and implement one of these, then pass it to nix_create_external_value + * Make sure to keep it alive while the external value lives. + * + * Optional functions can be set to NULL + * + * @see nix_create_external_value + */ +typedef struct NixCExternalValueDesc +{ + /** + * @brief Called when printing the external value + * + * @param[in] self the void* passed to nix_create_external_value + * @param[out] printer The printer to print to, pass to nix_external_print + */ + void (*print)(void * self, nix_printer * printer); + /** + * @brief Called on :t + * @param[in] self the void* passed to nix_create_external_value + * @param[out] res the return value + */ + void (*showType)(void * self, nix_string_return * res); + /** + * @brief Called on `builtins.typeOf` + * @param self the void* passed to nix_create_external_value + * @param[out] res the return value + */ + void (*typeOf)(void * self, nix_string_return * res); + /** + * @brief Called on "${str}" and builtins.toString. + * + * The latter with coerceMore=true + * Optional, the default is to throw an error. + * @param[in] self the void* passed to nix_create_external_value + * @param[out] c writable string context for the resulting string + * @param[in] coerceMore boolean, try to coerce to strings in more cases + * instead of throwing an error + * @param[in] copyToStore boolean, whether to copy referenced paths to store + * or keep them as-is + * @param[out] res the return value. Not touching this, or setting it to the + * empty string, will make the conversion throw an error. + */ + void (*coerceToString)( + void * self, nix_string_context * c, int coerceMore, int copyToStore, nix_string_return * res); + /** + * @brief Try to compare two external values + * + * Optional, the default is always false. + * If the other object was not a Nix C external value, this comparison will + * also return false + * @param[in] self the void* passed to nix_create_external_value + * @param[in] other the void* passed to the other object's + * nix_create_external_value + * @returns true if the objects are deemed to be equal + */ + int (*equal)(void * self, void * other); + /** + * @brief Convert the external value to json + * + * Optional, the default is to throw an error + * @param[in] self the void* passed to nix_create_external_value + * @param[in] state The evaluator state + * @param[in] strict boolean Whether to force the value before printing + * @param[out] c writable string context for the resulting string + * @param[in] copyToStore whether to copy referenced paths to store or keep + * them as-is + * @param[out] res the return value. Gets parsed as JSON. Not touching this, + * or setting it to the empty string, will make the conversion throw an error. + */ + void (*printValueAsJSON)( + void * self, EvalState *, bool strict, nix_string_context * c, bool copyToStore, nix_string_return * res); + /** + * @brief Convert the external value to XML + * + * Optional, the default is to throw an error + * @todo The mechanisms for this call are incomplete. There are no C + * bindings to work with XML, pathsets and positions. + * @param[in] self the void* passed to nix_create_external_value + * @param[in] state The evaluator state + * @param[in] strict boolean Whether to force the value before printing + * @param[in] location boolean Whether to include position information in the + * xml + * @param[out] doc XML document to output to + * @param[out] c writable string context for the resulting string + * @param[in,out] drvsSeen a path set to avoid duplicating derivations + * @param[in] pos The position of the call. + */ + void (*printValueAsXML)( + void * self, + EvalState *, + int strict, + int location, + void * doc, + nix_string_context * c, + void * drvsSeen, + int pos); +} NixCExternalValueDesc; + +/** + * @brief Create an external value, that can be given to nix_init_external + * + * Owned by the GC. Use nix_gc_decref when you're done with the pointer. + * + * @param[out] context Optional, stores error information + * @param[in] desc a NixCExternalValueDesc, you should keep this alive as long + * as the ExternalValue lives + * @param[in] v the value to store + * @returns external value, owned by the garbage collector + * @see nix_init_external + */ +ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v); + +/** + * @brief Extract the pointer from a nix c external value. + * @param[out] context Optional, stores error information + * @param[in] b The external value + * @returns The pointer, or null if the external value was not from nix c. + * @see nix_get_external + */ +void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b); + +// cffi end +#ifdef __cplusplus +} +#endif +/** @} */ + +#endif // NIX_API_EXTERNAL_H diff --git a/src/libexpr-c/nix_api_value.cc b/src/libexpr-c/nix_api_value.cc new file mode 100644 index 000000000..80e853b87 --- /dev/null +++ b/src/libexpr-c/nix_api_value.cc @@ -0,0 +1,530 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#include "globals.hh" +#include "primops.hh" +#include "value.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_value.h" + +#ifdef HAVE_BOEHMGC +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +// Helper function to throw an exception if value is null +static const nix::Value & check_value_not_null(const Value * value) +{ + if (!value) { + throw std::runtime_error("Value is null"); + } + return *((const nix::Value *) value); +} + +static nix::Value & check_value_not_null(Value * value) +{ + if (!value) { + throw std::runtime_error("Value is null"); + } + return *((nix::Value *) value); +} + +/** + * Helper function to convert calls from nix into C API. + * + * Deals with errors and converts arguments from C++ into C types. + */ +static void nix_c_primop_wrapper( + PrimOpFun f, void * userdata, nix::EvalState & state, const nix::PosIdx pos, nix::Value ** args, nix::Value & v) +{ + nix_c_context ctx; + f(userdata, &ctx, (EvalState *) &state, (Value **) args, (Value *) &v); + /* TODO: In the future, this should throw different errors depending on the error code */ + if (ctx.last_err_code != NIX_OK) + state.error("Error from builtin function: %s", *ctx.last_err).atPos(pos).debugThrow(); +} + +PrimOp * nix_alloc_primop( + nix_c_context * context, + PrimOpFun fun, + int arity, + const char * name, + const char ** args, + const char * doc, + void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + using namespace std::placeholders; + auto p = new +#ifdef HAVE_BOEHMGC + (GC) +#endif + nix::PrimOp{ + .name = name, + .args = {}, + .arity = (size_t) arity, + .doc = doc, + .fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)}; + if (args) + for (size_t i = 0; args[i]; i++) + p->args.emplace_back(*args); + nix_gc_incref(nullptr, p); + return (PrimOp *) p; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::RegisterPrimOp r(std::move(*((nix::PrimOp *) primOp))); + } + NIXC_CATCH_ERRS +} + +Value * nix_alloc_value(nix_c_context * context, EvalState * state) +{ + if (context) + context->last_err_code = NIX_OK; + try { + Value * res = state->state.allocValue(); + nix_gc_incref(nullptr, res); + return res; + } + NIXC_CATCH_ERRS_NULL +} + +ValueType nix_get_type(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + using namespace nix; + switch (v.type()) { + case nThunk: + return NIX_TYPE_THUNK; + case nInt: + return NIX_TYPE_INT; + case nFloat: + return NIX_TYPE_FLOAT; + case nBool: + return NIX_TYPE_BOOL; + case nString: + return NIX_TYPE_STRING; + case nPath: + return NIX_TYPE_PATH; + case nNull: + return NIX_TYPE_NULL; + case nAttrs: + return NIX_TYPE_ATTRS; + case nList: + return NIX_TYPE_LIST; + case nFunction: + return NIX_TYPE_FUNCTION; + case nExternal: + return NIX_TYPE_EXTERNAL; + } + return NIX_TYPE_NULL; + } + NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL); +} + +const char * nix_get_typename(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + auto s = nix::showType(v); + return strdup(s.c_str()); + } + NIXC_CATCH_ERRS_NULL +} + +bool nix_get_bool(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nBool); + return v.boolean; + } + NIXC_CATCH_ERRS_RES(false); +} + +const char * nix_get_string(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nString); + return v.c_str(); + } + NIXC_CATCH_ERRS_NULL +} + +const char * nix_get_path_string(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nPath); + // NOTE (from @yorickvP) + // v._path.path should work but may not be how Eelco intended it. + // Long-term this function should be rewritten to copy some data into a + // user-allocated string. + // We could use v.path().to_string().c_str(), but I'm concerned this + // crashes. Looks like .path() allocates a CanonPath with a copy of the + // string, then it gets the underlying data from that. + return v._path.path; + } + NIXC_CATCH_ERRS_NULL +} + +unsigned int nix_get_list_size(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nList); + return v.listSize(); + } + NIXC_CATCH_ERRS_RES(0); +} + +unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + return v.attrs->size(); + } + NIXC_CATCH_ERRS_RES(0); +} + +double nix_get_float(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nFloat); + return v.fpoint; + } + NIXC_CATCH_ERRS_RES(0.0); +} + +int64_t nix_get_int(nix_c_context * context, const Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nInt); + return v.integer; + } + NIXC_CATCH_ERRS_RES(0); +} + +ExternalValue * nix_get_external(nix_c_context * context, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nExternal); + return (ExternalValue *) v.external; + } + NIXC_CATCH_ERRS_NULL; +} + +Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nList); + auto * p = v.listElems()[ix]; + nix_gc_incref(nullptr, p); + if (p != nullptr) + state->state.forceValue(*p, nix::noPos); + return (Value *) p; + } + NIXC_CATCH_ERRS_NULL +} + +Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + nix::Symbol s = state->state.symbols.create(name); + auto attr = v.attrs->get(s); + if (attr) { + nix_gc_incref(nullptr, attr->value); + state->state.forceValue(*attr->value, nix::noPos); + return attr->value; + } + nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute"); + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} + +bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + nix::Symbol s = state->state.symbols.create(name); + auto attr = v.attrs->get(s); + if (attr) + return true; + return false; + } + NIXC_CATCH_ERRS_RES(false); +} + +Value * +nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + const nix::Attr & a = (*v.attrs)[i]; + *name = ((const std::string &) (state->state.symbols[a.name])).c_str(); + nix_gc_incref(nullptr, a.value); + state->state.forceValue(*a.value, nix::noPos); + return a.value; + } + NIXC_CATCH_ERRS_NULL +} + +const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + const nix::Attr & a = (*v.attrs)[i]; + return ((const std::string &) (state->state.symbols[a.name])).c_str(); + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_init_bool(nix_c_context * context, Value * value, bool b) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkBool(b); + } + NIXC_CATCH_ERRS +} + +// todo string context +nix_err nix_init_string(nix_c_context * context, Value * value, const char * str) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkString(std::string_view(str)); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * value, const char * str) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkPath(s->state.rootPath(nix::CanonPath(str))); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_float(nix_c_context * context, Value * value, double d) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkFloat(d); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkInt(i); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_null(nix_c_context * context, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkNull(); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + auto r = (nix::ExternalValueBase *) val; + v.mkExternal(r); + } + NIXC_CATCH_ERRS +} + +ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto builder = state->state.buildList(capacity); + return new +#if HAVE_BOEHMGC + (NoGC) +#endif + ListBuilder{std::move(builder)}; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & e = check_value_not_null(value); + list_builder->builder[index] = &e; + } + NIXC_CATCH_ERRS +} + +void nix_list_builder_free(ListBuilder * list_builder) +{ +#if HAVE_BOEHMGC + GC_FREE(list_builder); +#else + delete bb; +#endif +} + +nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkList(list_builder->builder); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * p) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkPrimOp((nix::PrimOp *) p); + } + NIXC_CATCH_ERRS +} + +nix_err nix_copy_value(nix_c_context * context, Value * value, Value * source) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + auto & s = check_value_not_null(source); + v = s; + } + NIXC_CATCH_ERRS +} + +nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder * b) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + v.mkAttrs(b->builder); + } + NIXC_CATCH_ERRS +} + +BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto bb = state->state.buildBindings(capacity); + return new +#if HAVE_BOEHMGC + (NoGC) +#endif + BindingsBuilder{std::move(bb)}; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * bb, const char * name, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + nix::Symbol s = bb->builder.state.symbols.create(name); + bb->builder.insert(s, &v); + } + NIXC_CATCH_ERRS +} + +void nix_bindings_builder_free(BindingsBuilder * bb) +{ +#if HAVE_BOEHMGC + GC_FREE((nix::BindingsBuilder *) bb); +#else + delete (nix::BindingsBuilder *) bb; +#endif +} diff --git a/src/libexpr-c/nix_api_value.h b/src/libexpr-c/nix_api_value.h new file mode 100644 index 000000000..42218188c --- /dev/null +++ b/src/libexpr-c/nix_api_value.h @@ -0,0 +1,434 @@ +#ifndef NIX_API_VALUE_H +#define NIX_API_VALUE_H + +/** @addtogroup libexpr + * @{ + */ +/** @file + * @brief libexpr C bindings dealing with values + */ + +#include "nix_api_util.h" +#include "stdbool.h" +#include "stddef.h" +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Type definitions +typedef enum { + NIX_TYPE_THUNK, + NIX_TYPE_INT, + NIX_TYPE_FLOAT, + NIX_TYPE_BOOL, + NIX_TYPE_STRING, + NIX_TYPE_PATH, + NIX_TYPE_NULL, + NIX_TYPE_ATTRS, + NIX_TYPE_LIST, + NIX_TYPE_FUNCTION, + NIX_TYPE_EXTERNAL +} ValueType; + +// forward declarations +typedef void Value; +typedef struct EvalState EvalState; +// type defs +/** @brief Stores an under-construction set of bindings + * @ingroup value_manip + * + * Do not reuse. + * @see nix_make_bindings_builder, nix_bindings_builder_free, nix_make_attrs + * @see nix_bindings_builder_insert + */ +typedef struct BindingsBuilder BindingsBuilder; + +/** @brief Stores an under-construction list + * @ingroup value_manip + * + * Do not reuse. + * @see nix_make_list_builder, nix_list_builder_free, nix_make_list + * @see nix_list_builder_insert + */ +typedef struct ListBuilder ListBuilder; + +/** @brief PrimOp function + * @ingroup primops + * + * Owned by the GC + * @see nix_alloc_primop, nix_init_primop + */ +typedef struct PrimOp PrimOp; +/** @brief External Value + * @ingroup Externals + * + * Owned by the GC + */ +typedef struct ExternalValue ExternalValue; + +/** @defgroup primops + * @brief Create your own primops + * @{ + */ +/** @brief Function pointer for primops + * When you want to return an error, call nix_set_err_msg(context, NIX_ERR_UNKNOWN, "your error message here"). + * + * @param[in] user_data Arbitrary data that was initially supplied to nix_alloc_primop + * @param[out] context Stores error information. + * @param[in] state Evaluator state + * @param[in] args list of arguments. Note that these can be thunks and should be forced using nix_value_force before + * use. + * @param[out] ret return value + * @see nix_alloc_primop, nix_init_primop + */ +typedef void (*PrimOpFun)(void * user_data, nix_c_context * context, EvalState * state, Value ** args, Value * ret); + +/** @brief Allocate a PrimOp + * + * Owned by the garbage collector. + * Use nix_gc_decref() when you're done with the returned PrimOp. + * + * @param[out] context Optional, stores error information + * @param[in] fun callback + * @param[in] arity expected number of function arguments + * @param[in] name function name + * @param[in] args array of argument names, NULL-terminated + * @param[in] doc optional, documentation for this primop + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called + * @return primop, or null in case of errors + * @see nix_init_primop + */ +PrimOp * nix_alloc_primop( + nix_c_context * context, + PrimOpFun fun, + int arity, + const char * name, + const char ** args, + const char * doc, + void * user_data); + +/** @brief add a primop to the `builtins` attribute set + * + * Only applies to States created after this call. + * + * Moves your PrimOp content into the global evaluator + * registry, meaning your input PrimOp pointer is no longer usable. + * You are free to remove your references to it, + * after which it will be garbage collected. + * + * @param[out] context Optional, stores error information + * @return primop, or null in case of errors + * + */ +nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp); +/** @} */ + +// Function prototypes + +/** @brief Allocate a Nix value + * + * Owned by the GC. Use nix_gc_decref() when you're done with the pointer + * @param[out] context Optional, stores error information + * @param[in] state nix evaluator state + * @return value, or null in case of errors + * + */ +Value * nix_alloc_value(nix_c_context * context, EvalState * state); + +/** @addtogroup value_manip Manipulating values + * @brief Functions to inspect and change Nix language values, represented by Value. + * @{ + */ +/** @name Getters + */ +/**@{*/ +/** @brief Get value type + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return type of nix value + */ +ValueType nix_get_type(nix_c_context * context, const Value * value); + +/** @brief Get type name of value as defined in the evaluator + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return type name, owned string + * @todo way to free the result + */ +const char * nix_get_typename(nix_c_context * context, const Value * value); + +/** @brief Get boolean value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return true or false, error info via context + */ +bool nix_get_bool(nix_c_context * context, const Value * value); + +/** @brief Get string + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return string + * @return NULL in case of error. + */ +const char * nix_get_string(nix_c_context * context, const Value * value); + +/** @brief Get path as string + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return string + * @return NULL in case of error. + */ +const char * nix_get_path_string(nix_c_context * context, const Value * value); + +/** @brief Get the length of a list + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return length of list, error info via context + */ +unsigned int nix_get_list_size(nix_c_context * context, const Value * value); + +/** @brief Get the element count of an attrset + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return attrset element count, error info via context + */ +unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value); + +/** @brief Get float value in 64 bits + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return float contents, error info via context + */ +double nix_get_float(nix_c_context * context, const Value * value); + +/** @brief Get int value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return int contents, error info via context + */ +int64_t nix_get_int(nix_c_context * context, const Value * value); + +/** @brief Get external reference + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return reference to external, NULL in case of error + */ +ExternalValue * nix_get_external(nix_c_context * context, Value *); + +/** @brief Get the ix'th element of a list + * + * Owned by the GC. Use nix_gc_decref when you're done with the pointer + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] ix list element to get + * @return value, NULL in case of errors + */ +Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix); + +/** @brief Get an attr by name + * + * Owned by the GC. Use nix_gc_decref when you're done with the pointer + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] name attribute name + * @return value, NULL in case of errors + */ +Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name); + +/** @brief Check if an attribute name exists on a value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] name attribute name + * @return value, error info via context + */ +bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name); + +/** @brief Get an attribute by index in the sorted bindings + * + * Also gives you the name. + * + * Owned by the GC. Use nix_gc_decref when you're done with the pointer + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] i attribute index + * @param[out] name will store a pointer to the attribute name + * @return value, NULL in case of errors + */ +Value * +nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name); + +/** @brief Get an attribute name by index in the sorted bindings + * + * Useful when you want the name but want to avoid evaluation. + * + * Owned by the nix EvalState + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] i attribute index + * @return name, NULL in case of errors + */ +const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i); + +/**@}*/ +/** @name Initializers + * + * Values are typically "returned" by initializing already allocated memory that serves as the return value. + * For this reason, the construction of values is not tied their allocation. + * Nix is a language with immutable values. Respect this property by only initializing Values once; and only initialize + * Values that are meant to be initialized by you. Failing to adhere to these rules may lead to undefined behavior. + */ +/**@{*/ +/** @brief Set boolean value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] b the boolean value + * @return error code, NIX_OK on success. + */ +nix_err nix_init_bool(nix_c_context * context, Value * value, bool b); + +/** @brief Set a string + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] str the string, copied + * @return error code, NIX_OK on success. + */ +nix_err nix_init_string(nix_c_context * context, Value * value, const char * str); + +/** @brief Set a path + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] str the path string, copied + * @return error code, NIX_OK on success. + */ +nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * value, const char * str); + +/** @brief Set a float + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] d the float, 64-bits + * @return error code, NIX_OK on success. + */ +nix_err nix_init_float(nix_c_context * context, Value * value, double d); + +/** @brief Set an int + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] i the int + * @return error code, NIX_OK on success. + */ + +nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i); +/** @brief Set null + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @return error code, NIX_OK on success. + */ + +nix_err nix_init_null(nix_c_context * context, Value * value); +/** @brief Set an external value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] val the external value to set. Will be GC-referenced by the value. + * @return error code, NIX_OK on success. + */ +nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val); + +/** @brief Create a list from a list builder + * @param[out] context Optional, stores error information + * @param[in] list_builder list builder to use. Make sure to unref this afterwards. + * @param[out] value Nix value to modify + * @return error code, NIX_OK on success. + */ +nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value * value); + +/** @brief Create a list builder + * @param[out] context Optional, stores error information + * @param[in] state nix evaluator state + * @param[in] capacity how many bindings you'll add. Don't exceed. + * @return owned reference to a list builder. Make sure to unref when you're done. + */ +ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity); + +/** @brief Insert bindings into a builder + * @param[out] context Optional, stores error information + * @param[in] list_builder ListBuilder to insert into + * @param[in] index index to manipulate + * @param[in] value value to insert + * @return error code, NIX_OK on success. + */ +nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, Value * value); + +/** @brief Free a list builder + * + * Does not fail. + * @param[in] builder the builder to free + */ +void nix_list_builder_free(ListBuilder * list_builder); + +/** @brief Create an attribute set from a bindings builder + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] b bindings builder to use. Make sure to unref this afterwards. + * @return error code, NIX_OK on success. + */ +nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder * b); + +/** @brief Set primop + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] op primop, will be gc-referenced by the value + * @see nix_alloc_primop + * @return error code, NIX_OK on success. + */ +nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * op); +/** @brief Copy from another value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] source value to copy from + * @return error code, NIX_OK on success. + */ +nix_err nix_copy_value(nix_c_context * context, Value * value, Value * source); +/**@}*/ + +/** @brief Create a bindings builder +* @param[out] context Optional, stores error information +* @param[in] state nix evaluator state +* @param[in] capacity how many bindings you'll add. Don't exceed. +* @return owned reference to a bindings builder. Make sure to unref when you're +done. +*/ +BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity); + +/** @brief Insert bindings into a builder + * @param[out] context Optional, stores error information + * @param[in] builder BindingsBuilder to insert into + * @param[in] name attribute name, copied into the symbol store + * @param[in] value value to give the binding + * @return error code, NIX_OK on success. + */ +nix_err +nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, Value * value); + +/** @brief Free a bindings builder + * + * Does not fail. + * @param[in] builder the builder to free + */ +void nix_bindings_builder_free(BindingsBuilder * builder); +/**@}*/ + +// cffi end +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif // NIX_API_VALUE_H diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 86251adf7..648032ea3 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -897,7 +897,6 @@ void Value::mkStringMove(const char * s, const NixStringContext & context) copyContextToValue(*this, context); } - void Value::mkPath(const SourcePath & path) { mkPath(&*path.accessor, makeImmutableString(path.path.abs())); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index f45971290..4d53bcde6 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -17,6 +17,7 @@ #include #include #include +#include namespace nix { @@ -79,7 +80,7 @@ struct PrimOp /** * Implementation of the primop. */ - PrimOpFun fun; + std::function::type> fun; /** * Optional experimental for this to be gated on. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 729271828..752176178 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -3404,8 +3404,11 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value callFunction. */ /* TODO: (layus) this is absurd. An optimisation like this should be outside the lambda creation */ - if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) - return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b); + if (args[0]->isPrimOp()) { + auto ptr = args[0]->primOp->fun.target(); + if (ptr && *ptr == prim_lessThan) + return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b); + } Value * vs[] = {a, b}; Value vBool; diff --git a/src/libexpr/search-path.cc b/src/libexpr/search-path.cc index a25767496..e2c3e050a 100644 --- a/src/libexpr/search-path.cc +++ b/src/libexpr/search-path.cc @@ -44,7 +44,7 @@ SearchPath::Elem SearchPath::Elem::parse(std::string_view rawElem) } -SearchPath parseSearchPath(const Strings & rawElems) +SearchPath SearchPath::parse(const Strings & rawElems) { SearchPath res; for (auto & rawElem : rawElems) diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 335801b34..b7b3c6434 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -328,6 +328,7 @@ public: } void mkPath(const SourcePath & path); + void mkPath(std::string_view path); inline void mkPath(InputAccessor * accessor, const char * path) { diff --git a/src/libstore-c/local.mk b/src/libstore-c/local.mk new file mode 100644 index 000000000..36a8e77a4 --- /dev/null +++ b/src/libstore-c/local.mk @@ -0,0 +1,21 @@ +libraries += libstorec + +libstorec_NAME = libnixstorec + +libstorec_DIR := $(d) + +libstorec_SOURCES := $(wildcard $(d)/*.cc) + +libstorec_LIBS = libutil libstore libutilc + +libstorec_LDFLAGS += -pthread + +# Not just for this library itself, but also for downstream libraries using this library + +INCLUDE_libstorec := -I $(d) +libstorec_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \ + $(INCLUDE_libstore) $(INCLUDE_libstorec) + +$(eval $(call install-file-in, $(d)/nix-store-c.pc, $(libdir)/pkgconfig, 0644)) + +libstorec_FORCE_INSTALL := 1 diff --git a/src/libstore-c/nix-store-c.pc.in b/src/libstore-c/nix-store-c.pc.in new file mode 100644 index 000000000..de3c7b4c6 --- /dev/null +++ b/src/libstore-c/nix-store-c.pc.in @@ -0,0 +1,9 @@ +prefix=@prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Nix +Description: Nix Store - C API +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lnixstorec -lnixutilc +Cflags: -I${includedir}/nix diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc new file mode 100644 index 000000000..d80ba332e --- /dev/null +++ b/src/libstore-c/nix_api_store.cc @@ -0,0 +1,134 @@ +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" + +#include "path.hh" +#include "store-api.hh" +#include "build-result.hh" + +#include "globals.hh" + +nix_err nix_libstore_init(nix_c_context * context) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::initLibStore(); + } + NIXC_CATCH_ERRS +} + +nix_err nix_init_plugins(nix_c_context * context) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::initPlugins(); + } + NIXC_CATCH_ERRS +} + +Store * nix_store_open(nix_c_context * context, const char * uri, const char *** params) +{ + if (context) + context->last_err_code = NIX_OK; + try { + std::string uri_str = uri ? uri : ""; + + if (uri_str.empty()) + return new Store{nix::openStore()}; + + if (!params) + return new Store{nix::openStore(uri_str)}; + + nix::Store::Params params_map; + for (size_t i = 0; params[i] != nullptr; i++) { + params_map[params[i][0]] = params[i][1]; + } + return new Store{nix::openStore(uri_str, params_map)}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_store_free(Store * store) +{ + delete store; +} + +nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto res = store->ptr->getUri(); + return call_nix_get_string_callback(res, callback, user_data); + } + NIXC_CATCH_ERRS +} + +nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto res = store->ptr->getVersion(); + return call_nix_get_string_callback(res.value_or(""), callback, user_data); + } + NIXC_CATCH_ERRS +} + +bool nix_store_is_valid_path(nix_c_context * context, Store * store, StorePath * path) +{ + if (context) + context->last_err_code = NIX_OK; + try { + return store->ptr->isValidPath(path->path); + } + NIXC_CATCH_ERRS_RES(false); +} + +StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::StorePath s = store->ptr->parseStorePath(path); + return new StorePath{std::move(s)}; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_store_realise( + nix_c_context * context, + Store * store, + StorePath * path, + void * userdata, + void (*callback)(void * userdata, const char *, const char *)) +{ + if (context) + context->last_err_code = NIX_OK; + try { + + const std::vector paths{nix::DerivedPath::Built{ + .drvPath = nix::makeConstantStorePathRef(path->path), .outputs = nix::OutputsSpec::All{}}}; + + const auto nixStore = store->ptr; + auto results = nixStore->buildPathsWithResults(paths, nix::bmNormal, nixStore); + + if (callback) { + for (const auto & result : results) { + for (const auto & [outputName, realisation] : result.builtOutputs) { + auto op = store->ptr->printStorePath(realisation.outPath); + callback(userdata, outputName.c_str(), op.c_str()); + } + } + } + } + NIXC_CATCH_ERRS +} + +void nix_store_path_free(StorePath * sp) +{ + delete sp; +} diff --git a/src/libstore-c/nix_api_store.h b/src/libstore-c/nix_api_store.h new file mode 100644 index 000000000..1309f99b7 --- /dev/null +++ b/src/libstore-c/nix_api_store.h @@ -0,0 +1,148 @@ +#ifndef NIX_API_STORE_H +#define NIX_API_STORE_H +/** + * @defgroup libstore libstore + * @brief C bindings for nix libstore + * + * libstore is used for talking to a Nix store + * @{ + */ +/** @file + * @brief Main entry for the libstore C bindings + */ + +#include "nix_api_util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** @brief Reference to a Nix store */ +typedef struct Store Store; +/** @brief Nix store path */ +typedef struct StorePath StorePath; + +/** + * @brief Initializes the Nix store library + * + * This function should be called before creating a Store + * This function can be called multiple times. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization was successful, an error code otherwise. + */ +nix_err nix_libstore_init(nix_c_context * context); + +/** + * @brief Loads the plugins specified in Nix's plugin-files setting. + * + * Call this once, after calling your desired init functions and setting + * relevant settings. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization was successful, an error code otherwise. + */ +nix_err nix_init_plugins(nix_c_context * context); + +/** + * @brief Open a nix store + * Store instances may share state and resources behind the scenes. + * @param[out] context Optional, stores error information + * @param[in] uri URI of the nix store, copied + * @param[in] params optional, array of key-value pairs, {{"endpoint", + * "https://s3.local"}} + * @return a Store pointer, NULL in case of errors + * @see nix_store_free + */ +Store * nix_store_open(nix_c_context *, const char * uri, const char *** params); + +/** + * @brief Deallocate a nix store and free any resources if not also held by other Store instances. + * + * Does not fail. + * + * @param[in] store the store to free + */ +void nix_store_free(Store * store); + +/** + * @brief get the URI of a nix store + * @param[out] context Optional, stores error information + * @param[in] store nix store reference + * @param[in] callback Called with the URI. + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called. + * @see nix_get_string_callback + * @return error code, NIX_OK on success. + */ +nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data); + +// returns: owned StorePath* +/** + * @brief Parse a Nix store path into a StorePath + * + * @note Don't forget to free this path using nix_store_path_free()! + * @param[out] context Optional, stores error information + * @param[in] store nix store reference + * @param[in] path Path string to parse, copied + * @return owned store path, NULL on error + */ +StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path); + +/** @brief Deallocate a StorePath + * + * Does not fail. + * @param[in] p the path to free + */ +void nix_store_path_free(StorePath * p); + +/** + * @brief Check if a StorePath is valid (i.e. that corresponding store object and its closure of references exists in + * the store) + * @param[out] context Optional, stores error information + * @param[in] store Nix Store reference + * @param[in] path Path to check + * @return true or false, error info in context + */ +bool nix_store_is_valid_path(nix_c_context * context, Store * store, StorePath * path); +// nix_err nix_store_ensure(Store*, const char*); +// nix_err nix_store_build_paths(Store*); +/** + * @brief Realise a Nix store path + * + * Blocking, calls callback once for each realised output + * + * @param[out] context Optional, stores error information + * @param[in] store Nix Store reference + * @param[in] path Path to build + * @param[in] userdata data to pass to every callback invocation + * @param[in] callback called for every realised output + */ +nix_err nix_store_realise( + nix_c_context * context, + Store * store, + StorePath * path, + void * userdata, + void (*callback)(void * userdata, const char * outname, const char * out)); + +/** + * @brief get the version of a nix store. + * If the store doesn't have a version (like the dummy store), returns an empty string. + * @param[out] context Optional, stores error information + * @param[in] store nix store reference + * @param[in] callback Called with the version. + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called. + * @see nix_get_string_callback + * @return error code, NIX_OK on success. + */ +nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data); + +// cffi end +#ifdef __cplusplus +} +#endif +/** + * @} + */ +#endif // NIX_API_STORE_H diff --git a/src/libstore-c/nix_api_store_internal.h b/src/libstore-c/nix_api_store_internal.h new file mode 100644 index 000000000..13db0c07c --- /dev/null +++ b/src/libstore-c/nix_api_store_internal.h @@ -0,0 +1,15 @@ +#ifndef NIX_API_STORE_INTERNAL_H +#define NIX_API_STORE_INTERNAL_H +#include "store-api.hh" + +struct Store +{ + nix::ref ptr; +}; + +struct StorePath +{ + nix::StorePath path; +}; + +#endif diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 306e98e2d..4229fb4df 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -345,6 +345,12 @@ void initPlugins() dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!handle) throw Error("could not dynamically open plugin file '%s': %s", file, dlerror()); + + /* Older plugins use a statically initialized object to run their code. + Newer plugins can also export nix_plugin_entry() */ + void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry"); + if (nix_plugin_entry) + nix_plugin_entry(); } } @@ -403,6 +409,7 @@ void assertLibStoreInitialized() { } void initLibStore() { + if (initLibStoreDone) return; initLibUtil(); diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index b8958898d..4bdbe3333 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -1157,9 +1157,10 @@ public: this, {}, "plugin-files", R"( A list of plugin files to be loaded by Nix. Each of these files will - be dlopened by Nix, allowing them to affect execution through static - initialization. In particular, these plugins may construct static - instances of RegisterPrimOp to add new primops or constants to the + be dlopened by Nix. If they contain the symbol `nix_plugin_entry()`, + this symbol will be called. Alternatively, they can affect execution + through static initialization. In particular, these plugins may construct + static instances of RegisterPrimOp to add new primops or constants to the expression language, RegisterStoreImplementation to add new store implementations, RegisterCommand to add new subcommands to the `nix` command, and RegisterSetting to add new nix config settings. See the diff --git a/src/libutil-c/local.mk b/src/libutil-c/local.mk new file mode 100644 index 000000000..342dc2d8b --- /dev/null +++ b/src/libutil-c/local.mk @@ -0,0 +1,18 @@ +libraries += libutilc + +libutilc_NAME = libnixutilc + +libutilc_DIR := $(d) + +libutilc_SOURCES := $(wildcard $(d)/*.cc) + +# Not just for this library itself, but also for downstream libraries using this library + +INCLUDE_libutilc := -I $(d) +libutilc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) + +libutilc_LIBS = libutil + +libutilc_LDFLAGS += -pthread + +libutilc_FORCE_INSTALL := 1 diff --git a/src/libutil-c/nix_api_util.cc b/src/libutil-c/nix_api_util.cc new file mode 100644 index 000000000..8d0f7ac38 --- /dev/null +++ b/src/libutil-c/nix_api_util.cc @@ -0,0 +1,148 @@ +#include "nix_api_util.h" +#include "config.hh" +#include "error.hh" +#include "nix_api_util_internal.h" +#include "util.hh" + +#include +#include + +nix_c_context * nix_c_context_create() +{ + return new nix_c_context(); +} + +void nix_c_context_free(nix_c_context * context) +{ + delete context; +} + +nix_err nix_context_error(nix_c_context * context) +{ + if (context == nullptr) { + throw; + } + try { + throw; + } catch (nix::Error & e) { + /* Storing this exception is annoying, take what we need here */ + context->last_err = e.what(); + context->info = e.info(); + int status; + const char * demangled = abi::__cxa_demangle(typeid(e).name(), 0, 0, &status); + if (demangled) { + context->name = demangled; + // todo: free(demangled); + } else { + context->name = typeid(e).name(); + } + context->last_err_code = NIX_ERR_NIX_ERROR; + return context->last_err_code; + } catch (const std::exception & e) { + context->last_err = e.what(); + context->last_err_code = NIX_ERR_UNKNOWN; + return context->last_err_code; + } + // unreachable +} + +nix_err nix_set_err_msg(nix_c_context * context, nix_err err, const char * msg) +{ + if (context == nullptr) { + // todo last_err_code + throw nix::Error("Nix C api error: %s", msg); + } + context->last_err_code = err; + context->last_err = msg; + return err; +} + +const char * nix_version_get() +{ + return PACKAGE_VERSION; +} + +// Implementations + +nix_err nix_setting_get(nix_c_context * context, const char * key, void * callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + std::map settings; + nix::globalConfig.getSettings(settings); + if (settings.contains(key)) { + return call_nix_get_string_callback(settings[key].value, callback, user_data); + } else { + return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found"); + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_setting_set(nix_c_context * context, const char * key, const char * value) +{ + if (context) + context->last_err_code = NIX_OK; + if (nix::globalConfig.set(key, value)) + return NIX_OK; + else { + return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found"); + } +} + +nix_err nix_libutil_init(nix_c_context * context) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::initLibUtil(); + return NIX_OK; + } + NIXC_CATCH_ERRS +} + +const char * nix_err_msg(nix_c_context * context, const nix_c_context * read_context, unsigned int * n) +{ + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err) { + if (n) + *n = read_context->last_err->size(); + return read_context->last_err->c_str(); + } + nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message"); + return nullptr; +} + +nix_err nix_err_name(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err_code != NIX_ERR_NIX_ERROR) { + return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error"); + } + return call_nix_get_string_callback(read_context->name, callback, user_data); +} + +nix_err nix_err_info_msg(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err_code != NIX_ERR_NIX_ERROR) { + return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error"); + } + return call_nix_get_string_callback(read_context->info->msg.str(), callback, user_data); +} + +nix_err nix_err_code(const nix_c_context * read_context) +{ + return read_context->last_err_code; +} + +// internal +nix_err call_nix_get_string_callback(const std::string str, void * callback, void * user_data) +{ + ((nix_get_string_callback) callback)(str.c_str(), str.size(), user_data); + return NIX_OK; +} diff --git a/src/libutil-c/nix_api_util.h b/src/libutil-c/nix_api_util.h new file mode 100644 index 000000000..cb506ca90 --- /dev/null +++ b/src/libutil-c/nix_api_util.h @@ -0,0 +1,301 @@ +#ifndef NIX_API_UTIL_H +#define NIX_API_UTIL_H +/** + * @defgroup libutil libutil + * @brief C bindings for nix libutil + * + * libutil is used for functionality shared between + * different Nix modules. + * @{ + */ +/** @file + * @brief Main entry for the libutil C bindings + * + * Also contains error handling utilities + */ + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** @defgroup errors Handling errors + * @brief Dealing with errors from the Nix side + * + * To handle errors that can be returned from the Nix API, + * a nix_c_context can be passed to any function that potentially returns an + * error. + * + * Error information will be stored in this context, and can be retrieved + * using nix_err_code and nix_err_msg. + * + * Passing NULL instead will cause the API to throw C++ errors. + * + * Example: + * @code{.c} + * int main() { + * nix_c_context* ctx = nix_c_context_create(); + * nix_libutil_init(ctx); + * if (nix_err_code(ctx) != NIX_OK) { + * printf("error: %s\n", nix_err_msg(NULL, ctx, NULL)); + * return 1; + * } + * return 0; + * } + * @endcode + * @{ + */ +// Error codes +/** + * @brief Type for error codes in the NIX system + * + * This type can have one of several predefined constants: + * - NIX_OK: No error occurred (0) + * - NIX_ERR_UNKNOWN: An unknown error occurred (-1) + * - NIX_ERR_OVERFLOW: An overflow error occurred (-2) + * - NIX_ERR_KEY: A key error occurred (-3) + * - NIX_ERR_NIX_ERROR: A generic Nix error occurred (-4) + */ +typedef int nix_err; + +/** + * @brief No error occurred. + * + * This error code is returned when no error has occurred during the function + * execution. + */ +#define NIX_OK 0 + +/** + * @brief An unknown error occurred. + * + * This error code is returned when an unknown error occurred during the + * function execution. + */ +#define NIX_ERR_UNKNOWN -1 + +/** + * @brief An overflow error occurred. + * + * This error code is returned when an overflow error occurred during the + * function execution. + */ +#define NIX_ERR_OVERFLOW -2 + +/** + * @brief A key error occurred. + * + * This error code is returned when a key error occurred during the function + * execution. + */ +#define NIX_ERR_KEY -3 + +/** + * @brief A generic Nix error occurred. + * + * This error code is returned when a generic Nix error occurred during the + * function execution. + */ +#define NIX_ERR_NIX_ERROR -4 + +/** + * @brief This object stores error state. + * @struct nix_c_context + * + * Passed as a first parameter to functions that can fail, to store error + * information. + * + * Optional wherever it can be used, passing NULL instead will throw a C++ + * exception. + * + * The struct is laid out so that it can also be cast to nix_err* to inspect + * directly: + * @code{.c} + * assert(*(nix_err*)ctx == NIX_OK); + * @endcode + * @note These can be reused between different function calls, + * but make sure not to use them for multiple calls simultaneously (which can + * happen in callbacks). + */ +typedef struct nix_c_context nix_c_context; + +/** + * @brief Called to get the value of a string owned by Nix. + * + * @param[in] start the string to copy. + * @param[in] n the string length. + * @param[in] user_data optional, arbitrary data, passed to the nix_get_string_callback when it's called. + */ +typedef void (*nix_get_string_callback)(const char * start, unsigned int n, void * user_data); + +// Function prototypes + +/** + * @brief Allocate a new nix_c_context. + * @throws std::bad_alloc + * @return allocated nix_c_context, owned by the caller. Free using + * `nix_c_context_free`. + */ +nix_c_context * nix_c_context_create(); +/** + * @brief Free a nix_c_context. Does not fail. + * @param[out] context The context to free, mandatory. + */ +void nix_c_context_free(nix_c_context * context); +/** + * @} + */ + +/** + * @brief Initializes nix_libutil and its dependencies. + * + * This function can be called multiple times, but should be called at least + * once prior to any other nix function. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization is successful, or an error code + * otherwise. + */ +nix_err nix_libutil_init(nix_c_context * context); + +/** @defgroup settings + * @{ + */ +/** + * @brief Retrieves a setting from the nix global configuration. + * + * This function requires nix_libutil_init() to be called at least once prior to + * its use. + * + * @param[out] context optional, Stores error information + * @param[in] key The key of the setting to retrieve. + * @param[in] callback Called with the setting value. + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called. + * @see nix_get_string_callback + * @return NIX_ERR_KEY if the setting is unknown, or NIX_OK if the setting was retrieved + * successfully. + */ +nix_err nix_setting_get(nix_c_context * context, const char * key, void * callback, void * user_data); + +/** + * @brief Sets a setting in the nix global configuration. + * + * Use "extra-" to append to the setting's value. + * + * Settings only apply for new State%s. Call nix_plugins_init() when you are + * done with the settings to load any plugins. + * + * @param[out] context optional, Stores error information + * @param[in] key The key of the setting to set. + * @param[in] value The value to set for the setting. + * @return NIX_ERR_KEY if the setting is unknown, or NIX_OK if the setting was + * set successfully. + */ +nix_err nix_setting_set(nix_c_context * context, const char * key, const char * value); + +/** + * @} + */ +// todo: nix_plugins_init() + +/** + * @brief Retrieves the nix library version. + * + * Does not fail. + * @return A static string representing the version of the nix library. + */ +const char * nix_version_get(); + +/** @addtogroup errors + * @{ + */ +/** + * @brief Retrieves the most recent error message from a context. + * + * @pre This function should only be called after a previous nix function has + * returned an error. + * + * @param[out] context optional, the context to store errors in if this function + * fails + * @param[in] ctx the context to retrieve the error message from + * @param[out] n optional: a pointer to an unsigned int that is set to the + * length of the error. + * @return nullptr if no error message was ever set, + * a borrowed pointer to the error message otherwise. + */ +const char * nix_err_msg(nix_c_context * context, const nix_c_context * ctx, unsigned int * n); + +/** + * @brief Retrieves the error message from errorInfo in a context. + * + * Used to inspect nix Error messages. + * + * @pre This function should only be called after a previous nix function has + * returned a NIX_ERR_NIX_ERROR + * + * @param[out] context optional, the context to store errors in if this function + * fails + * @param[in] read_context the context to retrieve the error message from. + * @param[in] callback Called with the error message. + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called. + * @see nix_get_string_callback + * @return NIX_OK if there were no errors, an error code otherwise. + */ +nix_err +nix_err_info_msg(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data); + +/** + * @brief Retrieves the error name from a context. + * + * Used to inspect nix Error messages. + * + * @pre This function should only be called after a previous nix function has + * returned a NIX_ERR_NIX_ERROR + * + * @param context optional, the context to store errors in if this function + * fails + * @param[in] read_context the context to retrieve the error message from + * @param[in] callback Called with the error name. + * @param[in] user_data optional, arbitrary data, passed to the callback when it's called. + * @see nix_get_string_callback + * @return NIX_OK if there were no errors, an error code otherwise. + */ +nix_err nix_err_name(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data); + +/** + * @brief Retrieves the most recent error code from a nix_c_context + * + * Equivalent to reading the first field of the context. + * + * Does not fail + * + * @param[in] read_context the context to retrieve the error message from + * @return most recent error code stored in the context. + */ +nix_err nix_err_code(const nix_c_context * read_context); + +/** + * @brief Set an error message on a nix context. + * + * This should be used when you want to throw an error from a PrimOp callback. + * + * All other use is internal to the API. + * + * @param context context to write the error message to, or NULL + * @param err The error code to set and return + * @param msg The error message to set. + * @returns the error code set + */ +nix_err nix_set_err_msg(nix_c_context * context, nix_err err, const char * msg); + +/** + * @} + */ + +// cffi end +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif // NIX_API_UTIL_H diff --git a/src/libutil-c/nix_api_util_internal.h b/src/libutil-c/nix_api_util_internal.h new file mode 100644 index 000000000..6e8eac020 --- /dev/null +++ b/src/libutil-c/nix_api_util_internal.h @@ -0,0 +1,49 @@ +#ifndef NIX_API_UTIL_INTERNAL_H +#define NIX_API_UTIL_INTERNAL_H + +#include +#include + +#include "error.hh" +#include "nix_api_util.h" + +struct nix_c_context +{ + nix_err last_err_code = NIX_OK; + std::optional last_err = {}; + std::optional info = {}; + std::string name = ""; +}; + +nix_err nix_context_error(nix_c_context * context); + +/** + * Internal use only. + * + * Helper to invoke nix_get_string_callback + * @param context optional, the context to store errors in if this function + * fails + * @param str The string to observe + * @param callback Called with the observed string. + * @param user_data optional, arbitrary data, passed to the callback when it's called. + * @return NIX_OK if there were no errors. + * @see nix_get_string_callback + */ +nix_err call_nix_get_string_callback(const std::string str, void * callback, void * user_data); + +#define NIXC_CATCH_ERRS \ + catch (...) \ + { \ + return nix_context_error(context); \ + } \ + return NIX_OK; + +#define NIXC_CATCH_ERRS_RES(def) \ + catch (...) \ + { \ + nix_context_error(context); \ + return def; \ + } +#define NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_RES(nullptr) + +#endif // NIX_API_UTIL_INTERNAL_H diff --git a/src/libutil/error.hh b/src/libutil/error.hh index d23625a54..445b1e19c 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -150,6 +150,10 @@ public: : err(e) { } + std::string message() { + return err.msg.str(); + } + const char * what() const noexcept override { return calcWhat().c_str(); } const std::string & msg() const { return calcWhat(); } const ErrorInfo & info() const { calcWhat(); return err; } diff --git a/tests/unit/libexpr-support/tests/nix_api_expr.hh b/tests/unit/libexpr-support/tests/nix_api_expr.hh new file mode 100644 index 000000000..d1840d034 --- /dev/null +++ b/tests/unit/libexpr-support/tests/nix_api_expr.hh @@ -0,0 +1,31 @@ +#pragma once +///@file +#include "nix_api_expr.h" +#include "nix_api_value.h" +#include "tests/nix_api_store.hh" + +#include + +namespace nixC { + +class nix_api_expr_test : public nix_api_store_test +{ +protected: + + nix_api_expr_test() + { + nix_libexpr_init(ctx); + state = nix_state_create(nullptr, nullptr, store); + value = nix_alloc_value(nullptr, state); + } + ~nix_api_expr_test() + { + nix_gc_decref(nullptr, value); + nix_state_free(state); + } + + EvalState * state; + Value * value; +}; + +} diff --git a/tests/unit/libexpr/local.mk b/tests/unit/libexpr/local.mk index eda65508d..8df1a5207 100644 --- a/tests/unit/libexpr/local.mk +++ b/tests/unit/libexpr/local.mk @@ -24,14 +24,17 @@ libexpr-tests_EXTRA_INCLUDES = \ -I tests/unit/libstore-support \ -I tests/unit/libutil-support \ $(INCLUDE_libexpr) \ + $(INCLUDE_libexprc) \ $(INCLUDE_libfetchers) \ $(INCLUDE_libstore) \ - $(INCLUDE_libutil) + $(INCLUDE_libstorec) \ + $(INCLUDE_libutil) \ + $(INCLUDE_libutilc) libexpr-tests_CXXFLAGS += $(libexpr-tests_EXTRA_INCLUDES) libexpr-tests_LIBS = \ libexpr-test-support libstore-test-support libutils-test-support \ - libexpr libfetchers libstore libutil + libexpr libexprc libfetchers libstore libstorec libutil libutilc libexpr-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) -lgmock diff --git a/tests/unit/libexpr/nix_api_expr.cc b/tests/unit/libexpr/nix_api_expr.cc new file mode 100644 index 000000000..9d54a62f8 --- /dev/null +++ b/tests/unit/libexpr/nix_api_expr.cc @@ -0,0 +1,100 @@ +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_expr.h" +#include "nix_api_value.h" + +#include "tests/nix_api_expr.hh" + +#include + +namespace nixC { + +TEST_F(nix_api_expr_test, nix_expr_eval_from_string) +{ + nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value); + nix_value_force(nullptr, state, value); + auto result = nix_get_string(nullptr, value); + + ASSERT_STREQ(PACKAGE_VERSION, result); +} + +TEST_F(nix_api_expr_test, nix_expr_eval_add_numbers) +{ + nix_expr_eval_from_string(nullptr, state, "1 + 1", ".", value); + nix_value_force(nullptr, state, value); + auto result = nix_get_int(nullptr, value); + + ASSERT_EQ(2, result); +} + +TEST_F(nix_api_expr_test, nix_expr_eval_drv) +{ + auto expr = R"(derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; })"; + nix_expr_eval_from_string(nullptr, state, expr, ".", value); + ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(nullptr, value)); + + EvalState * stateFn = nix_state_create(nullptr, nullptr, store); + Value * valueFn = nix_alloc_value(nullptr, state); + nix_expr_eval_from_string(nullptr, stateFn, "builtins.toString", ".", valueFn); + ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(nullptr, valueFn)); + + EvalState * stateResult = nix_state_create(nullptr, nullptr, store); + Value * valueResult = nix_alloc_value(nullptr, stateResult); + nix_value_call(ctx, stateResult, valueFn, value, valueResult); + ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(nullptr, valueResult)); + + std::string p = nix_get_string(nullptr, valueResult); + std::string pEnd = "-myname"; + ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size())); + + // Clean up + nix_gc_decref(nullptr, valueFn); + nix_state_free(stateFn); + + nix_gc_decref(nullptr, valueResult); + nix_state_free(stateResult); +} + +TEST_F(nix_api_expr_test, nix_build_drv) +{ + auto expr = R"(derivation { name = "myname"; + system = builtins.currentSystem; + builder = "/bin/sh"; + args = [ "-c" "echo foo > $out" ]; + })"; + nix_expr_eval_from_string(nullptr, state, expr, ".", value); + + Value * drvPathValue = nix_get_attr_byname(nullptr, value, state, "drvPath"); + const char * drvPath = nix_get_string(nullptr, drvPathValue); + + std::string p = drvPath; + std::string pEnd = "-myname.drv"; + ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size())); + + StorePath * drvStorePath = nix_store_parse_path(ctx, store, drvPath); + ASSERT_EQ(true, nix_store_is_valid_path(ctx, store, drvStorePath)); + + Value * outPathValue = nix_get_attr_byname(ctx, value, state, "outPath"); + const char * outPath = nix_get_string(ctx, outPathValue); + + p = outPath; + pEnd = "-myname"; + ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size())); + ASSERT_EQ(true, drvStorePath->path.isDerivation()); + + StorePath * outStorePath = nix_store_parse_path(ctx, store, outPath); + ASSERT_EQ(false, nix_store_is_valid_path(ctx, store, outStorePath)); + + // TODO figure out why fails. + // `make libexpr-tests_RUN` works, but `nix build .` enters an infinite loop + /* nix_store_realise(ctx, store, drvStorePath, nullptr, nullptr); */ + /* auto is_valid_path = nix_store_is_valid_path(ctx, store, outStorePath); */ + /* ASSERT_EQ(true, is_valid_path); */ + + // Clean up + nix_store_path_free(drvStorePath); + nix_store_path_free(outStorePath); +} +} diff --git a/tests/unit/libexpr/nix_api_external.cc b/tests/unit/libexpr/nix_api_external.cc new file mode 100644 index 000000000..7e2caed1b --- /dev/null +++ b/tests/unit/libexpr/nix_api_external.cc @@ -0,0 +1,63 @@ +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_value.h" +#include "nix_api_external.h" +#include "tests/nix_api_expr.hh" + +#include + +namespace nixC { + +class MyExternalValueDesc : public NixCExternalValueDesc +{ +public: + MyExternalValueDesc(int x) + : _x(x) + { + print = print_function; + showType = show_type_function; + typeOf = type_of_function; + } + +private: + int _x; + static void print_function(void * self, nix_printer * printer) {} + + static void show_type_function(void * self, nix_string_return * res) {} + + static void type_of_function(void * self, nix_string_return * res) + { + MyExternalValueDesc * obj = static_cast(self); + + std::string type_string = "nix-external_x); + type_string += " )>"; + res->str = &*type_string.begin(); + } +}; + +TEST_F(nix_api_expr_test, nix_expr_eval_external) +{ + MyExternalValueDesc * external = new MyExternalValueDesc(42); + ExternalValue * val = nix_create_external_value(ctx, external, external); + nix_init_external(ctx, value, val); + + EvalState * stateResult = nix_state_create(nullptr, nullptr, store); + Value * valueResult = nix_alloc_value(nullptr, stateResult); + + EvalState * stateFn = nix_state_create(nullptr, nullptr, store); + Value * valueFn = nix_alloc_value(nullptr, stateFn); + + nix_expr_eval_from_string(nullptr, state, "builtins.typeOf", ".", valueFn); + + ASSERT_EQ(NIX_TYPE_EXTERNAL, nix_get_type(nullptr, value)); + + nix_value_call(ctx, state, valueFn, value, valueResult); + + ASSERT_STREQ("nix-external", nix_get_string(nullptr, valueResult)); +} +} diff --git a/tests/unit/libexpr/nix_api_value.cc b/tests/unit/libexpr/nix_api_value.cc new file mode 100644 index 000000000..726960638 --- /dev/null +++ b/tests/unit/libexpr/nix_api_value.cc @@ -0,0 +1,184 @@ +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_expr.h" +#include "nix_api_value.h" + +#include "tests/nix_api_expr.hh" + +#include +#include + +namespace nixC { + +TEST_F(nix_api_expr_test, nix_value_set_get_int) +{ + ASSERT_EQ(0, nix_get_int(ctx, nullptr)); + ASSERT_DEATH(nix_get_int(ctx, value), ""); + + int myInt = 1; + nix_init_int(ctx, value, myInt); + + ASSERT_EQ(myInt, nix_get_int(ctx, value)); + ASSERT_STREQ("an integer", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_INT, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_value_set_get_float) +{ + ASSERT_FLOAT_EQ(0.0, nix_get_float(ctx, nullptr)); + ASSERT_DEATH(nix_get_float(ctx, value), ""); + + float myDouble = 1.0; + nix_init_float(ctx, value, myDouble); + + ASSERT_FLOAT_EQ(myDouble, nix_get_float(ctx, value)); + ASSERT_STREQ("a float", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_FLOAT, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_value_set_get_bool) +{ + ASSERT_EQ(false, nix_get_bool(ctx, nullptr)); + ASSERT_DEATH(nix_get_bool(ctx, value), ""); + + bool myBool = true; + nix_init_bool(ctx, value, myBool); + + ASSERT_EQ(myBool, nix_get_bool(ctx, value)); + ASSERT_STREQ("a Boolean", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_BOOL, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_value_set_get_string) +{ + ASSERT_EQ(nullptr, nix_get_string(ctx, nullptr)); + ASSERT_DEATH(nix_get_string(ctx, value), ""); + + const char * myString = "some string"; + nix_init_string(ctx, value, myString); + + ASSERT_STREQ(myString, nix_get_string(ctx, value)); + ASSERT_STREQ("a string", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_value_set_get_null) +{ + ASSERT_DEATH(nix_get_typename(ctx, value), ""); + + nix_init_null(ctx, value); + + ASSERT_STREQ("null", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_NULL, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_value_set_get_path) +{ + ASSERT_EQ(nullptr, nix_get_path_string(ctx, nullptr)); + ASSERT_DEATH(nix_get_path_string(ctx, value), ""); + + const char * p = "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname"; + nix_init_path_string(ctx, state, value, p); + + ASSERT_STREQ(p, nix_get_path_string(ctx, value)); + ASSERT_STREQ("a path", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_PATH, nix_get_type(ctx, value)); +} + +TEST_F(nix_api_expr_test, nix_build_and_init_list) +{ + ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, nullptr, state, 0)); + ASSERT_EQ(0, nix_get_list_size(ctx, nullptr)); + + ASSERT_DEATH(nix_get_list_byidx(ctx, value, state, 0), ""); + ASSERT_DEATH(nix_get_list_size(ctx, value), ""); + + int size = 10; + ListBuilder * builder = nix_make_list_builder(ctx, state, size); + + Value * intValue = nix_alloc_value(ctx, state); + nix_init_int(ctx, intValue, 42); + nix_list_builder_insert(ctx, builder, 0, intValue); + nix_make_list(ctx, builder, value); + nix_list_builder_free(builder); + + ASSERT_EQ(42, nix_get_int(ctx, nix_get_list_byidx(ctx, value, state, 0))); + ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 1)); + ASSERT_EQ(10, nix_get_list_size(ctx, value)); + + ASSERT_STREQ("a list", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_LIST, nix_get_type(ctx, value)); + + // Clean up + nix_gc_decref(ctx, intValue); +} + +TEST_F(nix_api_expr_test, nix_build_and_init_attr) +{ + ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0)); + ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, nullptr, state, 0, nullptr)); + ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, nullptr, state, 0)); + ASSERT_EQ(0, nix_get_attrs_size(ctx, nullptr)); + ASSERT_EQ(false, nix_has_attr_byname(ctx, nullptr, state, "no-value")); + + ASSERT_DEATH(nix_get_attr_byname(ctx, value, state, 0), ""); + ASSERT_DEATH(nix_get_attr_byidx(ctx, value, state, 0, nullptr), ""); + ASSERT_DEATH(nix_get_attr_name_byidx(ctx, value, state, 0), ""); + ASSERT_DEATH(nix_get_attrs_size(ctx, value), ""); + ASSERT_DEATH(nix_has_attr_byname(ctx, value, state, "no-value"), ""); + + int size = 10; + const char ** out_name = (const char **) malloc(sizeof(char *)); + + BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, size); + + Value * intValue = nix_alloc_value(ctx, state); + nix_init_int(ctx, intValue, 42); + + Value * stringValue = nix_alloc_value(ctx, state); + nix_init_string(ctx, stringValue, "foo"); + + nix_bindings_builder_insert(ctx, builder, "a", intValue); + nix_bindings_builder_insert(ctx, builder, "b", stringValue); + nix_make_attrs(ctx, value, builder); + nix_bindings_builder_free(builder); + + ASSERT_EQ(2, nix_get_attrs_size(ctx, value)); + + Value * out_value = nix_get_attr_byname(ctx, value, state, "a"); + ASSERT_EQ(42, nix_get_int(ctx, out_value)); + nix_gc_decref(ctx, out_value); + + out_value = nix_get_attr_byidx(ctx, value, state, 0, out_name); + ASSERT_EQ(42, nix_get_int(ctx, out_value)); + ASSERT_STREQ("a", *out_name); + nix_gc_decref(ctx, out_value); + + ASSERT_STREQ("a", nix_get_attr_name_byidx(ctx, value, state, 0)); + + ASSERT_EQ(true, nix_has_attr_byname(ctx, value, state, "b")); + ASSERT_EQ(false, nix_has_attr_byname(ctx, value, state, "no-value")); + + out_value = nix_get_attr_byname(ctx, value, state, "b"); + ASSERT_STREQ("foo", nix_get_string(ctx, out_value)); + nix_gc_decref(nullptr, out_value); + + out_value = nix_get_attr_byidx(ctx, value, state, 1, out_name); + ASSERT_STREQ("foo", nix_get_string(ctx, out_value)); + ASSERT_STREQ("b", *out_name); + nix_gc_decref(nullptr, out_value); + + ASSERT_STREQ("b", nix_get_attr_name_byidx(ctx, value, state, 1)); + + ASSERT_STREQ("a set", nix_get_typename(ctx, value)); + ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(ctx, value)); + + // Clean up + nix_gc_decref(ctx, intValue); + nix_gc_decref(ctx, stringValue); + free(out_name); +} + +} diff --git a/tests/unit/libstore-support/tests/nix_api_store.hh b/tests/unit/libstore-support/tests/nix_api_store.hh new file mode 100644 index 000000000..a8b60fbc3 --- /dev/null +++ b/tests/unit/libstore-support/tests/nix_api_store.hh @@ -0,0 +1,54 @@ +#pragma once +///@file +#include "tests/nix_api_util.hh" + +#include "nix_api_store.h" +#include "nix_api_store_internal.h" + +#include +#include + +namespace fs = std::filesystem; + +namespace nixC { +class nix_api_store_test : public nix_api_util_context +{ +public: + nix_api_store_test() + { + nix_libstore_init(ctx); + init_local_store(); + }; + + ~nix_api_store_test() override + { + nix_store_free(store); + + for (auto & path : fs::recursive_directory_iterator(nixDir)) { + fs::permissions(path, fs::perms::owner_all); + } + fs::remove_all(nixDir); + } + + Store * store; + std::string nixDir; + std::string nixStoreDir; + +protected: + void init_local_store() + { + auto tmpl = nix::getEnv("TMPDIR").value_or("/tmp") + "/tests_nix-store.XXXXXX"; + nixDir = mkdtemp((char *) tmpl.c_str()); + nixStoreDir = nixDir + "/my_nix_store"; + + // Options documented in `nix help-stores` + const char * p1[] = {"store", nixStoreDir.c_str()}; + const char * p2[] = {"state", (new std::string(nixDir + "/my_state"))->c_str()}; + const char * p3[] = {"log", (new std::string(nixDir + "/my_log"))->c_str()}; + + const char ** params[] = {p1, p2, p3, nullptr}; + + store = nix_store_open(ctx, "local", params); + } +}; +} diff --git a/tests/unit/libstore/local.mk b/tests/unit/libstore/local.mk index 960dece89..b8f895fad 100644 --- a/tests/unit/libstore/local.mk +++ b/tests/unit/libstore/local.mk @@ -20,12 +20,14 @@ libstore-tests_EXTRA_INCLUDES = \ -I tests/unit/libstore-support \ -I tests/unit/libutil-support \ $(INCLUDE_libstore) \ - $(INCLUDE_libutil) + $(INCLUDE_libstorec) \ + $(INCLUDE_libutil) \ + $(INCLUDE_libutilc) libstore-tests_CXXFLAGS += $(libstore-tests_EXTRA_INCLUDES) libstore-tests_LIBS = \ libstore-test-support libutil-test-support \ - libstore libutil + libstore libstorec libutil libutilc libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) diff --git a/tests/unit/libstore/nix_api_store.cc b/tests/unit/libstore/nix_api_store.cc new file mode 100644 index 000000000..a31d66a4c --- /dev/null +++ b/tests/unit/libstore/nix_api_store.cc @@ -0,0 +1,93 @@ +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_store.h" +#include "nix_api_store_internal.h" + +#include "tests/nix_api_store.hh" + +namespace nixC { + +void observe_string_cb(const char * start, unsigned int n, std::string * user_data) +{ + *user_data = std::string(start); +} + +std::string PATH_SUFFIX = "/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-name"; + +TEST_F(nix_api_util_context, nix_libstore_init) +{ + auto ret = nix_libstore_init(ctx); + ASSERT_EQ(NIX_OK, ret); +} + +TEST_F(nix_api_store_test, nix_store_get_uri) +{ + std::string str; + auto ret = nix_store_get_uri(ctx, store, (void *) observe_string_cb, &str); + ASSERT_EQ(NIX_OK, ret); + ASSERT_STREQ("local", str.c_str()); +} + +TEST_F(nix_api_store_test, InvalidPathFails) +{ + nix_store_parse_path(ctx, store, "invalid-path"); + ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR); +} + +TEST_F(nix_api_store_test, ReturnsValidStorePath) +{ + StorePath * result = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + ASSERT_NE(result, nullptr); + ASSERT_STREQ("name", result->path.name().data()); + ASSERT_STREQ(PATH_SUFFIX.substr(1).c_str(), result->path.to_string().data()); +} + +TEST_F(nix_api_store_test, SetsLastErrCodeToNixOk) +{ + nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + ASSERT_EQ(ctx->last_err_code, NIX_OK); +} + +TEST_F(nix_api_store_test, DoesNotCrashWhenContextIsNull) +{ + ASSERT_NO_THROW(nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str())); +} + +TEST_F(nix_api_store_test, get_version) +{ + std::string str; + auto ret = nix_store_get_version(ctx, store, (void *) observe_string_cb, &str); + ASSERT_EQ(NIX_OK, ret); + ASSERT_STREQ(PACKAGE_VERSION, str.c_str()); +} + +TEST_F(nix_api_util_context, nix_store_open_dummy) +{ + nix_libstore_init(ctx); + Store * store = nix_store_open(ctx, "dummy://", nullptr); + ASSERT_EQ(NIX_OK, ctx->last_err_code); + ASSERT_STREQ("dummy", store->ptr->getUri().c_str()); + + std::string str; + nix_store_get_version(ctx, store, (void *) observe_string_cb, &str); + ASSERT_STREQ("", str.c_str()); + + nix_store_free(store); +} + +TEST_F(nix_api_util_context, nix_store_open_invalid) +{ + nix_libstore_init(ctx); + Store * store = nix_store_open(ctx, "invalid://", nullptr); + ASSERT_EQ(NIX_ERR_NIX_ERROR, ctx->last_err_code); + ASSERT_EQ(nullptr, store); + nix_store_free(store); +} + +TEST_F(nix_api_store_test, nix_store_is_valid_path_not_in_store) +{ + StorePath * path = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + ASSERT_EQ(false, nix_store_is_valid_path(ctx, store, path)); +} + +} diff --git a/tests/unit/libutil-support/tests/nix_api_util.hh b/tests/unit/libutil-support/tests/nix_api_util.hh new file mode 100644 index 000000000..0dfb38f7b --- /dev/null +++ b/tests/unit/libutil-support/tests/nix_api_util.hh @@ -0,0 +1,27 @@ +#pragma once +///@file +#include "nix_api_util.h" + +#include + +namespace nixC { + +class nix_api_util_context : public ::testing::Test +{ +protected: + + nix_api_util_context() + { + ctx = nix_c_context_create(); + nix_libutil_init(ctx); + }; + + ~nix_api_util_context() override + { + nix_c_context_free(ctx); + ctx = nullptr; + } + + nix_c_context * ctx; +}; +} diff --git a/tests/unit/libutil/local.mk b/tests/unit/libutil/local.mk index 536607322..39b4c0782 100644 --- a/tests/unit/libutil/local.mk +++ b/tests/unit/libutil/local.mk @@ -18,11 +18,12 @@ libutil-tests_SOURCES := $(wildcard $(d)/*.cc) libutil-tests_EXTRA_INCLUDES = \ -I tests/unit/libutil-support \ - $(INCLUDE_libutil) + $(INCLUDE_libutil) \ + $(INCLUDE_libutilc) libutil-tests_CXXFLAGS += $(libutil-tests_EXTRA_INCLUDES) -libutil-tests_LIBS = libutil-test-support libutil +libutil-tests_LIBS = libutil-test-support libutil libutilc libutil-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) diff --git a/tests/unit/libutil/nix_api_util.cc b/tests/unit/libutil/nix_api_util.cc new file mode 100644 index 000000000..09f3f3e05 --- /dev/null +++ b/tests/unit/libutil/nix_api_util.cc @@ -0,0 +1,145 @@ +#include "config.hh" +#include "args.hh" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "tests/nix_api_util.hh" + +#include + +namespace nixC { + +void observe_string_cb(const char * start, unsigned int n, std::string * user_data) +{ + *user_data = std::string(start); +} + +TEST_F(nix_api_util_context, nix_context_error) +{ + std::string err_msg_ref; + try { + throw nix::Error("testing error"); + } catch (nix::Error & e) { + err_msg_ref = e.what(); + nix_context_error(ctx); + } + ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR); + ASSERT_EQ(ctx->name, "nix::Error"); + ASSERT_EQ(*ctx->last_err, err_msg_ref); + ASSERT_EQ(ctx->info->msg.str(), "testing error"); + + try { + throw std::runtime_error("testing exception"); + } catch (std::exception & e) { + err_msg_ref = e.what(); + nix_context_error(ctx); + } + ASSERT_EQ(ctx->last_err_code, NIX_ERR_UNKNOWN); + ASSERT_EQ(*ctx->last_err, err_msg_ref); +} + +TEST_F(nix_api_util_context, nix_set_err_msg) +{ + ASSERT_EQ(ctx->last_err_code, NIX_OK); + nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "unknown test error"); + ASSERT_EQ(ctx->last_err_code, NIX_ERR_UNKNOWN); + ASSERT_EQ(*ctx->last_err, "unknown test error"); +} + +TEST(nix_api_util, nix_version_get) +{ + ASSERT_EQ(std::string(nix_version_get()), PACKAGE_VERSION); +} + +struct MySettings : nix::Config +{ + nix::Setting settingSet{this, "empty", "setting-name", "Description"}; +}; + +MySettings mySettings; +static nix::GlobalConfig::Register rs(&mySettings); + +TEST_F(nix_api_util_context, nix_setting_get) +{ + ASSERT_EQ(ctx->last_err_code, NIX_OK); + std::string setting_value; + nix_err result = nix_setting_get(ctx, "invalid-key", (void *) observe_string_cb, &setting_value); + ASSERT_EQ(result, NIX_ERR_KEY); + + result = nix_setting_get(ctx, "setting-name", (void *) observe_string_cb, &setting_value); + ASSERT_EQ(result, NIX_OK); + ASSERT_STREQ("empty", setting_value.c_str()); +} + +TEST_F(nix_api_util_context, nix_setting_set) +{ + nix_err result = nix_setting_set(ctx, "invalid-key", "new-value"); + ASSERT_EQ(result, NIX_ERR_KEY); + + result = nix_setting_set(ctx, "setting-name", "new-value"); + ASSERT_EQ(result, NIX_OK); + + std::string setting_value; + result = nix_setting_get(ctx, "setting-name", (void *) observe_string_cb, &setting_value); + ASSERT_EQ(result, NIX_OK); + ASSERT_STREQ("new-value", setting_value.c_str()); +} + +TEST_F(nix_api_util_context, nix_err_msg) +{ + // no error + EXPECT_THROW(nix_err_msg(nullptr, ctx, NULL), nix::Error); + + // set error + nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "unknown test error"); + + // basic usage + std::string err_msg = nix_err_msg(NULL, ctx, NULL); + ASSERT_EQ(err_msg, "unknown test error"); + + // advanced usage + unsigned int sz; + err_msg = nix_err_msg(nix_c_context_create(), ctx, &sz); + ASSERT_EQ(sz, err_msg.size()); +} + +TEST_F(nix_api_util_context, nix_err_info_msg) +{ + std::string err_info; + + // no error + EXPECT_THROW(nix_err_info_msg(NULL, ctx, (void *) observe_string_cb, &err_info), nix::Error); + + try { + throw nix::Error("testing error"); + } catch (...) { + nix_context_error(ctx); + } + nix_err_info_msg(nix_c_context_create(), ctx, (void *) observe_string_cb, &err_info); + ASSERT_STREQ("testing error", err_info.c_str()); +} + +TEST_F(nix_api_util_context, nix_err_name) +{ + std::string err_name; + + // no error + EXPECT_THROW(nix_err_name(NULL, ctx, (void *) observe_string_cb, &err_name), nix::Error); + + std::string err_msg_ref; + try { + throw nix::Error("testing error"); + } catch (...) { + nix_context_error(ctx); + } + nix_err_name(nix_c_context_create(), ctx, (void *) observe_string_cb, &err_name); + ASSERT_EQ(std::string(err_name), "nix::Error"); +} + +TEST_F(nix_api_util_context, nix_err_code) +{ + ASSERT_EQ(nix_err_code(ctx), NIX_OK); + nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "unknown test error"); + ASSERT_EQ(nix_err_code(ctx), NIX_ERR_UNKNOWN); +} + +}