diff --git a/src/libexpr-c/nix_api_expr.cc b/src/libexpr-c/nix_api_expr.cc index 6144a7986..27a70afb6 100644 --- a/src/libexpr-c/nix_api_expr.cc +++ b/src/libexpr-c/nix_api_expr.cc @@ -6,6 +6,7 @@ #include "eval-gc.hh" #include "globals.hh" #include "eval-settings.hh" +#include "ref.hh" #include "nix_api_expr.h" #include "nix_api_expr_internal.h" @@ -93,7 +94,46 @@ nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_val NIXC_CATCH_ERRS } -EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c, Store * store) +nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Store * store) +{ + if (context) + context->last_err_code = NIX_OK; + try { + // Allocate ahead of time, because .settings needs self-reference + void * p = ::operator new( + sizeof(nix_eval_state_builder), + static_cast(alignof(nix_eval_state_builder))); + auto * p2 = static_cast(p); + new (p) nix_eval_state_builder{ + .store = nix::ref(store->ptr), + .settings = nix::EvalSettings{/* &bool */ p2->readOnlyMode}, + .fetchSettings = nix::fetchers::Settings{}, + .readOnlyMode = true, + }; + return p2; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_eval_state_builder_free(nix_eval_state_builder * builder) +{ + delete builder; +} + +nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder) +{ + if (context) + context->last_err_code = NIX_OK; + try { + // TODO: load in one go? + builder->settings.readOnlyMode = nix::settings.readOnlyMode; + loadConfFile(builder->settings); + loadConfFile(builder->fetchSettings); + } + NIXC_CATCH_ERRS +} + +nix_err nix_eval_state_builder_set_lookup_path(nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath_c) { if (context) context->last_err_code = NIX_OK; @@ -102,28 +142,51 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c if (lookupPath_c != nullptr) for (size_t i = 0; lookupPath_c[i] != nullptr; i++) lookupPath.push_back(lookupPath_c[i]); + builder->lookupPath = nix::LookupPath::parse(lookupPath); + } + NIXC_CATCH_ERRS +} +EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder * builder) +{ + if (context) + context->last_err_code = NIX_OK; + try { + // Allocate ahead of time, because .state init needs self-reference void * p = ::operator new( sizeof(EvalState), static_cast(alignof(EvalState))); auto * p2 = static_cast(p); new (p) EvalState { - .fetchSettings = nix::fetchers::Settings{}, - .settings = nix::EvalSettings{ - nix::settings.readOnlyMode, - }, + .fetchSettings = std::move(builder->fetchSettings), + .settings = std::move(builder->settings), .state = nix::EvalState( - nix::LookupPath::parse(lookupPath), - store->ptr, + builder->lookupPath, + builder->store, p2->fetchSettings, p2->settings), }; - loadConfFile(p2->settings); return p2; } NIXC_CATCH_ERRS_NULL } +EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c, Store * store) +{ + auto builder = nix_eval_state_builder_new(context, store); + if (builder == nullptr) + return nullptr; + + if (nix_eval_state_builder_load(context, builder) != NIX_OK) + return nullptr; + + if (nix_eval_state_builder_set_lookup_path(context, builder, lookupPath_c) + != NIX_OK) + return nullptr; + + return nix_eval_state_build(context, builder); +} + void nix_state_free(EvalState * state) { delete state; diff --git a/src/libexpr-c/nix_api_expr.h b/src/libexpr-c/nix_api_expr.h index e680f5ff1..f8d181452 100644 --- a/src/libexpr-c/nix_api_expr.h +++ b/src/libexpr-c/nix_api_expr.h @@ -30,6 +30,11 @@ extern "C" { // cffi start // Type definitions +/** + * @brief Builder for EvalState + */ +typedef struct nix_eval_state_builder nix_eval_state_builder; + /** * @brief Represents a state of the Nix language evaluator. * @@ -174,12 +179,70 @@ nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value * nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value); /** - * @brief Create a new Nix language evaluator state. + * @brief Create a new nix_eval_state_builder + * + * The settings are initialized to their default value. + * Values can be sourced elsewhere with nix_eval_state_builder_load. + * + * @param[out] context Optional, stores error information + * @param[in] store The Nix store to use. + * @return A new nix_eval_state_builder or NULL on failure. + */ +nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Store * store); + +/** + * @brief Read settings from the ambient environment + * + * Settings are sourced from environment variables and configuration files, + * as documented in the Nix manual. + * + * @param[out] context Optional, stores error information + * @param[out] builder The builder to modify. + * @return NIX_OK if successful, an error code otherwise. + */ +nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder); + +/** + * @brief Set the lookup path for `<...>` expressions + * + * @param[in] context Optional, stores error information + * @param[in] builder The builder to modify. + * @param[in] lookupPath Null-terminated array of strings corresponding to entries in NIX_PATH. + */ +nix_err nix_eval_state_builder_set_lookup_path( + nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath); + +/** + * @brief Create a new Nix language evaluator state + * + * Remember to nix_eval_state_builder_free after building the state. + * + * @param[out] context Optional, stores error information + * @param[in] builder The builder to use and free + * @return A new Nix state or NULL on failure. + * @see nix_eval_state_builder_new, nix_eval_state_builder_free + */ +EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder * builder); + +/** + * @brief Free a nix_eval_state_builder + * + * Does not fail. + * + * @param[in] builder The builder to free. + */ +void nix_eval_state_builder_free(nix_eval_state_builder * builder); + +/** + * @brief Create a new Nix language evaluator state + * + * For more control, use nix_eval_state_builder * * @param[out] context Optional, stores error information * @param[in] lookupPath Null-terminated 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. + * @see nix_state_builder_new */ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath, Store * store); diff --git a/src/libexpr-c/nix_api_expr_internal.h b/src/libexpr-c/nix_api_expr_internal.h index 12f24b6eb..f59664011 100644 --- a/src/libexpr-c/nix_api_expr_internal.h +++ b/src/libexpr-c/nix_api_expr_internal.h @@ -6,6 +6,17 @@ #include "eval-settings.hh" #include "attr-set.hh" #include "nix_api_value.h" +#include "search-path.hh" + +struct nix_eval_state_builder +{ + nix::ref store; + nix::EvalSettings settings; + nix::fetchers::Settings fetchSettings; + nix::LookupPath lookupPath; + // TODO: make an EvalSettings setting own this instead? + bool readOnlyMode; +}; struct EvalState { diff --git a/src/libexpr-tests/nix_api_expr.cc b/src/libexpr-tests/nix_api_expr.cc index b37ac44b3..5ed78d2fc 100644 --- a/src/libexpr-tests/nix_api_expr.cc +++ b/src/libexpr-tests/nix_api_expr.cc @@ -7,12 +7,49 @@ #include "tests/nix_api_expr.hh" #include "tests/string_callback.hh" +#include "file-system.hh" #include #include namespace nixC { +TEST_F(nix_api_store_test, nix_eval_state_lookup_path) +{ + auto tmpDir = nix::createTempDir(); + auto delTmpDir = std::make_unique(tmpDir, true); + auto nixpkgs = tmpDir + "/pkgs"; + auto nixos = tmpDir + "/cfg"; + std::filesystem::create_directories(nixpkgs); + std::filesystem::create_directories(nixos); + + std::string nixpkgsEntry = "nixpkgs=" + nixpkgs; + std::string nixosEntry = "nixos-config=" + nixos; + const char * lookupPath[] = {nixpkgsEntry.c_str(), nixosEntry.c_str(), nullptr}; + + auto builder = nix_eval_state_builder_new(ctx, store); + assert_ctx_ok(); + + ASSERT_EQ(NIX_OK, nix_eval_state_builder_set_lookup_path(ctx, builder, lookupPath)); + assert_ctx_ok(); + + auto state = nix_eval_state_build(ctx, builder); + assert_ctx_ok(); + + nix_eval_state_builder_free(builder); + + Value * value = nix_alloc_value(ctx, state); + nix_expr_eval_from_string(ctx, state, "builtins.seq ", ".", value); + assert_ctx_ok(); + + ASSERT_EQ(nix_get_type(ctx, value), NIX_TYPE_PATH); + assert_ctx_ok(); + + auto pathStr = nix_get_path_string(ctx, value); + assert_ctx_ok(); + ASSERT_EQ(0, strcmp(pathStr, nixpkgs.c_str())); +} + TEST_F(nix_api_expr_test, nix_expr_eval_from_string) { nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value);