From a0a1d003700917b05acb6a21f5381c868297e0d8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 28 Mar 2025 13:10:35 +0000 Subject: [PATCH] nix-flake-c: Add basic flakeref parsing and locking --- src/libflake-c/nix_api_flake.cc | 110 +++++++++++++++++ src/libflake-c/nix_api_flake.h | 149 +++++++++++++++++++++++ src/libflake-c/nix_api_flake_internal.hh | 23 ++++ src/libflake-tests/nix_api_flake.cc | 136 ++++++++++++++++++++- 4 files changed, 416 insertions(+), 2 deletions(-) diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc index a1b586e82..6dc3d1476 100644 --- a/src/libflake-c/nix_api_flake.cc +++ b/src/libflake-c/nix_api_flake.cc @@ -1,7 +1,10 @@ #include "nix_api_flake.h" #include "nix_api_flake_internal.hh" +#include "nix_api_util.h" #include "nix_api_util_internal.h" #include "nix_api_expr_internal.h" +#include "nix_api_fetchers_internal.hh" +#include "nix_api_fetchers.h" #include "nix/flake/flake.hh" @@ -27,3 +30,110 @@ nix_err nix_flake_settings_add_to_eval_state_builder( } NIXC_CATCH_ERRS } + +nix_flake_reference_parse_flags * +nix_flake_reference_parse_flags_new(nix_c_context * context, nix_flake_settings * settings) +{ + nix_clear_err(context); + try { + return new nix_flake_reference_parse_flags{ + .baseDirectory = std::nullopt, + }; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_flake_reference_parse_flags_free(nix_flake_reference_parse_flags * flags) +{ + delete flags; +} + +nix_err nix_flake_reference_parse_flags_set_base_directory( + nix_c_context * context, + nix_flake_reference_parse_flags * flags, + const char * baseDirectory, + size_t baseDirectoryLen) +{ + nix_clear_err(context); + try { + flags->baseDirectory.emplace(nix::Path{std::string(baseDirectory, baseDirectoryLen)}); + return NIX_OK; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_reference_and_fragment_from_string( + nix_c_context * context, + nix_fetchers_settings * fetchSettings, + nix_flake_settings * flakeSettings, + nix_flake_reference_parse_flags * parseFlags, + const char * strData, + size_t strSize, + nix_flake_reference ** flakeReferenceOut, + nix_get_string_callback fragmentCallback, + void * fragmentCallbackUserData) +{ + nix_clear_err(context); + *flakeReferenceOut = nullptr; + try { + std::string str(strData, 0, strSize); + + auto [flakeRef, fragment] = + nix::parseFlakeRefWithFragment(*fetchSettings->settings, str, parseFlags->baseDirectory, true); + *flakeReferenceOut = new nix_flake_reference{nix::make_ref(flakeRef)}; + return call_nix_get_string_callback(fragment, fragmentCallback, fragmentCallbackUserData); + } + NIXC_CATCH_ERRS +} + +void nix_flake_reference_free(nix_flake_reference * flakeReference) +{ + delete flakeReference; +} + +nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_flake_settings * settings) +{ + nix_clear_err(context); + try { + auto lockSettings = nix::make_ref(); + return new nix_flake_lock_flags{lockSettings}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_flake_lock_flags_free(nix_flake_lock_flags * flags) +{ + delete flags; +} + +nix_locked_flake * nix_flake_lock( + nix_c_context * context, + nix_flake_settings * settings, + EvalState * eval_state, + nix_flake_lock_flags * flags, + nix_flake_reference * flakeReference) +{ + try { + auto lockedFlake = nix::make_ref(nix::flake::lockFlake( + *settings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags)); + return new nix_locked_flake{lockedFlake}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_locked_flake_free(nix_locked_flake * lockedFlake) +{ + delete lockedFlake; +} + +nix_value * nix_locked_flake_get_output_attrs( + nix_c_context * context, nix_flake_settings * settings, EvalState * evalState, nix_locked_flake * lockedFlake) +{ + nix_clear_err(context); + try { + auto v = nix_alloc_value(context, evalState); + nix::flake::callFlake(evalState->state, *lockedFlake->lockedFlake, v->value); + return v; + } + NIXC_CATCH_ERRS_NULL +} diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h index 75675835e..c82fe29c3 100644 --- a/src/libflake-c/nix_api_flake.h +++ b/src/libflake-c/nix_api_flake.h @@ -9,6 +9,7 @@ * @brief Main entry for the libflake C bindings */ +#include "nix_api_fetchers.h" #include "nix_api_store.h" #include "nix_api_util.h" #include "nix_api_expr.h" @@ -18,8 +19,46 @@ extern "C" { #endif // cffi start +/** + * @brief A settings object for configuring the behavior of the nix-flake-c library. + * @see nix_flake_settings_new + * @see nix_flake_settings_free + */ typedef struct nix_flake_settings nix_flake_settings; +/** + * @brief Context and paramaters for parsing a flake reference + * @see nix_flake_reference_parse_flags_free + * @see nix_flake_reference_parse_string + */ +typedef struct nix_flake_reference_parse_flags nix_flake_reference_parse_flags; + +/** + * @brief A reference to a flake + * + * A flake reference specifies how to fetch a flake. + * + * @see nix_flake_reference_from_string + * @see nix_flake_reference_free + */ +typedef struct nix_flake_reference nix_flake_reference; + +/** + * @brief Parameters for locking a flake + * @see nix_flake_lock_flags_new + * @see nix_flake_lock_flags_free + * @see nix_flake_lock + */ +typedef struct nix_flake_lock_flags nix_flake_lock_flags; + +/** + * @brief A flake with a suitable lock (file or otherwise) + * @see nix_flake_lock + * @see nix_locked_flake_free + * @see nix_locked_flake_get_output_attrs + */ +typedef struct nix_locked_flake nix_locked_flake; + // Function prototypes /** * Create a nix_flake_settings initialized with default values. @@ -38,6 +77,8 @@ void nix_flake_settings_free(nix_flake_settings * settings); * @brief Initialize a `nix_flake_settings` to contain `builtins.getFlake` and * potentially more. * + * @warning This does not put the eval state in pure mode! + * * @param[out] context Optional, stores error information * @param[in] settings The settings to use for e.g. `builtins.getFlake` * @param[in] builder The builder to modify @@ -45,6 +86,114 @@ void nix_flake_settings_free(nix_flake_settings * settings); nix_err nix_flake_settings_add_to_eval_state_builder( nix_c_context * context, nix_flake_settings * settings, nix_eval_state_builder * builder); +/** + * @brief A new `nix_flake_reference_parse_flags` with defaults + */ +nix_flake_reference_parse_flags * +nix_flake_reference_parse_flags_new(nix_c_context * context, nix_flake_settings * settings); + +/** + * @brief Deallocate and release the resources associated with a `nix_flake_reference_parse_flags`. + * Does not fail. + * @param[in] flags the `nix_flake_reference_parse_flags *` to free + */ +void nix_flake_reference_parse_flags_free(nix_flake_reference_parse_flags * flags); + +/** + * @brief Provide a base directory for parsing relative flake references + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] baseDirectory The base directory to add + * @param[in] baseDirectoryLen The length of baseDirectory + * @return NIX_OK on success, NIX_ERR on failure + */ +nix_err nix_flake_reference_parse_flags_set_base_directory( + nix_c_context * context, + nix_flake_reference_parse_flags * flags, + const char * baseDirectory, + size_t baseDirectoryLen); + +/** + * @brief A new `nix_flake_lock_flags` with defaults + * @param[in] settings Flake settings that may affect the defaults + */ +nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_flake_settings * settings); + +/** + * @brief Deallocate and release the resources associated with a `nix_flake_lock_flags`. + * Does not fail. + * @param[in] settings the `nix_flake_lock_flags *` to free + */ +void nix_flake_lock_flags_free(nix_flake_lock_flags * settings); + +/** + * @brief Lock a flake, if not already locked. + * @param[out] context Optional, stores error information + * @param[in] settings The flake (and fetch) settings to use + * @param[in] flags The locking flags to use + * @param[in] flake The flake to lock + */ +nix_locked_flake * nix_flake_lock( + nix_c_context * context, + nix_flake_settings * settings, + EvalState * eval_state, + nix_flake_lock_flags * flags, + nix_flake_reference * flake); + +/** + * @brief Deallocate and release the resources associated with a `nix_locked_flake`. + * Does not fail. + * @param[in] locked_flake the `nix_locked_flake *` to free + */ +void nix_locked_flake_free(nix_locked_flake * locked_flake); + +/** + * @brief Parse a URL-like string into a `nix_flake_reference`. + * + * @param[out] context **context** – Optional, stores error information + * @param[in] fetchSettings **context** – The fetch settings to use + * @param[in] flakeSettings **context** – The flake settings to use + * @param[in] parseFlags **context** – Specific context and parameters such as base directory + * + * @param[in] str **input** – The URI-like string to parse + * @param[in] strLen **input** – The length of `str` + * + * @param[out] flakeReferenceOut **result** – The resulting flake reference + * @param[in] fragmentCallback **result** – A callback to call with the fragment part of the URL + * @param[in] fragmentCallbackUserData **result** – User data to pass to the fragment callback + * + * @return NIX_OK on success, NIX_ERR on failure + */ +nix_err nix_flake_reference_and_fragment_from_string( + nix_c_context * context, + nix_fetchers_settings * fetchSettings, + nix_flake_settings * flakeSettings, + nix_flake_reference_parse_flags * parseFlags, + const char * str, + size_t strLen, + nix_flake_reference ** flakeReferenceOut, + nix_get_string_callback fragmentCallback, + void * fragmentCallbackUserData); + +/** + * @brief Deallocate and release the resources associated with a `nix_flake_reference`. + * + * Does not fail. + * + * @param[in] store the `nix_flake_reference *` to free + */ +void nix_flake_reference_free(nix_flake_reference * store); + +/** + * @brief Get the output attributes of a flake. + * @param[out] context Optional, stores error information + * @param[in] settings The settings to use + * @param[in] locked_flake the flake to get the output attributes from + * @return A new nix_value or NULL on failure. Release the `nix_value` with `nix_value_decref`. + */ +nix_value * nix_locked_flake_get_output_attrs( + nix_c_context * context, nix_flake_settings * settings, EvalState * evalState, nix_locked_flake * lockedFlake); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/libflake-c/nix_api_flake_internal.hh b/src/libflake-c/nix_api_flake_internal.hh index f7c5e7838..fbc6574d6 100644 --- a/src/libflake-c/nix_api_flake_internal.hh +++ b/src/libflake-c/nix_api_flake_internal.hh @@ -1,9 +1,32 @@ #pragma once +#include #include "nix/util/ref.hh" +#include "nix/flake/flake.hh" +#include "nix/flake/flakeref.hh" #include "nix/flake/settings.hh" struct nix_flake_settings { nix::ref settings; }; + +struct nix_flake_reference_parse_flags +{ + std::optional baseDirectory; +}; + +struct nix_flake_reference +{ + nix::ref flakeRef; +}; + +struct nix_flake_lock_flags +{ + nix::ref lockFlags; +}; + +struct nix_locked_flake +{ + nix::ref lockedFlake; +}; diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc index 21be2c766..eb1f7c486 100644 --- a/src/libflake-tests/nix_api_flake.cc +++ b/src/libflake-tests/nix_api_flake.cc @@ -1,7 +1,6 @@ +#include "nix/util/file-system.hh" #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 "nix_api_flake.h" @@ -51,4 +50,137 @@ TEST_F(nix_api_store_test, nix_api_init_getFlake_exists) ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(ctx, value)); } +TEST_F(nix_api_store_test, nix_api_flake_reference_not_absolute_no_basedir_fail) +{ + nix_libstore_init(ctx); + assert_ctx_ok(); + nix_libexpr_init(ctx); + assert_ctx_ok(); + + auto settings = nix_flake_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, settings); + + auto fetchSettings = nix_fetchers_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, fetchSettings); + + auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings); + + std::string str(".#legacyPackages.aarch127-unknown...orion"); + std::string fragment; + nix_flake_reference * flakeReference = nullptr; + auto r = nix_flake_reference_and_fragment_from_string( + ctx, + fetchSettings, + settings, + parseFlags, + str.data(), + str.size(), + &flakeReference, + OBSERVE_STRING(fragment)); + + ASSERT_NE(NIX_OK, r); + ASSERT_EQ(nullptr, flakeReference); + + nix_flake_reference_parse_flags_free(parseFlags); +} + +TEST_F(nix_api_store_test, nix_api_load_flake) +{ + auto tmpDir = nix::createTempDir(); + nix::AutoDelete delTmpDir(tmpDir, true); + + nix::writeFile(tmpDir + "/flake.nix", R"( + { + outputs = { ... }: { + hello = "potato"; + }; + } + )"); + + nix_libstore_init(ctx); + assert_ctx_ok(); + nix_libexpr_init(ctx); + assert_ctx_ok(); + + auto fetchSettings = nix_fetchers_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, fetchSettings); + + auto settings = nix_flake_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, settings); + + nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store); + ASSERT_NE(nullptr, builder); + assert_ctx_ok(); + + auto state = nix_eval_state_build(ctx, builder); + assert_ctx_ok(); + ASSERT_NE(nullptr, state); + + nix_eval_state_builder_free(builder); + + auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings); + assert_ctx_ok(); + ASSERT_NE(nullptr, parseFlags); + + auto r0 = nix_flake_reference_parse_flags_set_base_directory( + ctx, + parseFlags, + tmpDir.c_str(), + tmpDir.size()); + assert_ctx_ok(); + ASSERT_EQ(NIX_OK, r0); + + std::string fragment; + const std::string ref = ".#legacyPackages.aarch127-unknown...orion"; + nix_flake_reference * flakeReference = nullptr; + auto r = nix_flake_reference_and_fragment_from_string( + ctx, + fetchSettings, + settings, + parseFlags, + ref.data(), + ref.size(), + &flakeReference, + OBSERVE_STRING(fragment)); + assert_ctx_ok(); + ASSERT_EQ(NIX_OK, r); + ASSERT_NE(nullptr, flakeReference); + ASSERT_EQ(fragment, "legacyPackages.aarch127-unknown...orion"); + + nix_flake_reference_parse_flags_free(parseFlags); + + auto lockFlags = nix_flake_lock_flags_new(ctx, settings); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockFlags); + + auto lockedFlake = nix_flake_lock(ctx, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + nix_flake_lock_flags_free(lockFlags); + + auto value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + auto helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + std::string helloStr; + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("potato", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + nix_flake_reference_free(flakeReference); + nix_state_free(state); + nix_flake_settings_free(settings); +} + } // namespace nixC