From 0dc9b6b7c748e125e24938964bb8de515b970ab0 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 2 Apr 2025 17:00:59 +0200 Subject: [PATCH 1/9] libutil-tests-support: Add file/line to ctx errors --- .../include/nix/util/tests/nix_api_util.hh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh b/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh index 006dc497c..382c7b292 100644 --- a/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh +++ b/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh @@ -3,6 +3,7 @@ #include "nix_api_util.h" #include +#include namespace nixC { @@ -24,7 +25,12 @@ protected: nix_c_context * ctx; - inline void assert_ctx_ok() + inline std::string loc(const char * file, int line) + { + return std::string(file) + ":" + std::to_string(line); + } + + inline void assert_ctx_ok(const char * file, int line) { if (nix_err_code(ctx) == NIX_OK) { return; @@ -32,16 +38,18 @@ protected: unsigned int n; const char * p = nix_err_msg(nullptr, ctx, &n); std::string msg(p, n); - throw std::runtime_error(std::string("nix_err_code(ctx) != NIX_OK, message: ") + msg); + throw std::runtime_error(loc(file, line) + ": nix_err_code(ctx) != NIX_OK, message: " + msg); } +#define assert_ctx_ok() assert_ctx_ok(__FILE__, __LINE__) - inline void assert_ctx_err() + inline void assert_ctx_err(const char * file, int line) { if (nix_err_code(ctx) != NIX_OK) { return; } - throw std::runtime_error("Got NIX_OK, but expected an error!"); + throw std::runtime_error(loc(file, line) + ": Got NIX_OK, but expected an error!"); } +#define assert_ctx_err() assert_ctx_err(__FILE__, __LINE__) }; } From 60b4b220d8f0e55aaf1f52fe81ff96b2bbbec36f Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 28 Mar 2025 13:10:49 +0000 Subject: [PATCH 2/9] test: Fixup test name --- src/libflake-tests/nix_api_flake.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc index 455fcb15d..21be2c766 100644 --- a/src/libflake-tests/nix_api_flake.cc +++ b/src/libflake-tests/nix_api_flake.cc @@ -14,7 +14,7 @@ namespace nixC { -TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists) +TEST_F(nix_api_store_test, nix_api_init_getFlake_exists) { nix_libstore_init(ctx); assert_ctx_ok(); From 02360dd65cb4d393a9be142dfcff32d1fdbcba18 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 27 Mar 2025 15:47:02 +0000 Subject: [PATCH 3/9] nix-expr: Expose nix_api_expr_internal.h intentionally This is required for other bindings like nix-flake-c to hook into nix-expr-c appropriately. The `_internal` part should be a sufficient deterrent normally, and it may also be useful for bindings that migrate from the C++ interface. --- src/libexpr-c/meson.build | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libexpr-c/meson.build b/src/libexpr-c/meson.build index 7c11ca9cb..ed4582e40 100644 --- a/src/libexpr-c/meson.build +++ b/src/libexpr-c/meson.build @@ -37,13 +37,11 @@ include_dirs = [include_directories('.')] headers = files( 'nix_api_expr.h', + 'nix_api_expr_internal.h', 'nix_api_external.h', 'nix_api_value.h', ) -# TODO move this header to libexpr, maybe don't use it in tests? -headers += files('nix_api_expr_internal.h') - subdir('nix-meson-build-support/export-all-symbols') subdir('nix-meson-build-support/windows-version') From 05e5bd2140320ae332391557262708105fbdb9af Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 27 Mar 2025 15:50:22 +0000 Subject: [PATCH 4/9] Docs --- src/libexpr-c/nix_api_expr.h | 5 +++++ src/libexpr/value-to-json.cc | 1 + src/libflake/include/nix/flake/flake.hh | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libexpr-c/nix_api_expr.h b/src/libexpr-c/nix_api_expr.h index f8d181452..2be739955 100644 --- a/src/libexpr-c/nix_api_expr.h +++ b/src/libexpr-c/nix_api_expr.h @@ -286,6 +286,11 @@ nix_err nix_gc_incref(nix_c_context * context, const void * object); /** * @brief Decrement the garbage collector reference counter for the given object * + * We also provide typed `nix_*_decref` functions, which are + * - safer to use + * - easier to integrate when deriving bindings + * - allow more flexibility + * * @param[out] context Optional, stores error information * @param[in] object The object to stop referencing */ diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 51652db1f..4c0667d9e 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -10,6 +10,7 @@ namespace nix { using json = nlohmann::json; +// TODO: rename. It doesn't print. json printValueAsJSON(EvalState & state, bool strict, Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore) { diff --git a/src/libflake/include/nix/flake/flake.hh b/src/libflake/include/nix/flake/flake.hh index 3336f8557..3b98f1400 100644 --- a/src/libflake/include/nix/flake/flake.hh +++ b/src/libflake/include/nix/flake/flake.hh @@ -63,7 +63,7 @@ struct ConfigFile }; /** - * The contents of a flake.nix file. + * A flake in context */ struct Flake { From 1061a0965a75a9946fbfadccbe8c4aff48b5df96 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 27 Mar 2025 19:40:46 +0000 Subject: [PATCH 5/9] nix-flake-c: Add missing bits --- flake.nix | 1 + src/libflake-c/meson.build | 1 + 2 files changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index bb11b62d8..e4a513a7a 100644 --- a/flake.nix +++ b/flake.nix @@ -369,6 +369,7 @@ "nix-expr-tests" = { }; "nix-flake" = { }; + "nix-flake-c" = { }; "nix-flake-tests" = { }; "nix-main" = { }; diff --git a/src/libflake-c/meson.build b/src/libflake-c/meson.build index fd3cdd01b..435035b86 100644 --- a/src/libflake-c/meson.build +++ b/src/libflake-c/meson.build @@ -37,6 +37,7 @@ include_dirs = [include_directories('.')] headers = files( 'nix_api_flake.h', + 'nix_api_flake_internal.hh', ) # TODO move this header to libexpr, maybe don't use it in tests? From 60bffbd41bd134b05828f805b1be47140a007144 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 27 Mar 2025 19:55:00 +0000 Subject: [PATCH 6/9] nix-fetchers-c: Init with settings object Also make it a dependency of nix-flake-c; we'll need that. --- flake.nix | 1 + meson.build | 1 + packaging/components.nix | 1 + packaging/everything.nix | 3 + packaging/hydra.nix | 1 + src/libfetchers-c/.version | 1 + src/libfetchers-c/meson.build | 65 +++++++++++++++++++ src/libfetchers-c/nix-meson-build-support | 1 + src/libfetchers-c/nix_api_fetchers.cc | 19 ++++++ src/libfetchers-c/nix_api_fetchers.h | 32 +++++++++ .../nix_api_fetchers_internal.hh | 12 ++++ src/libfetchers-c/package.nix | 50 ++++++++++++++ src/libfetchers-tests/meson.build | 2 + src/libfetchers-tests/nix_api_fetchers.cc | 18 +++++ src/libfetchers-tests/package.nix | 2 + src/libflake-c/meson.build | 2 + src/libflake-c/package.nix | 2 + 17 files changed, 213 insertions(+) create mode 120000 src/libfetchers-c/.version create mode 100644 src/libfetchers-c/meson.build create mode 120000 src/libfetchers-c/nix-meson-build-support create mode 100644 src/libfetchers-c/nix_api_fetchers.cc create mode 100644 src/libfetchers-c/nix_api_fetchers.h create mode 100644 src/libfetchers-c/nix_api_fetchers_internal.hh create mode 100644 src/libfetchers-c/package.nix create mode 100644 src/libfetchers-tests/nix_api_fetchers.cc diff --git a/flake.nix b/flake.nix index e4a513a7a..c2e938360 100644 --- a/flake.nix +++ b/flake.nix @@ -361,6 +361,7 @@ "nix-store-tests" = { }; "nix-fetchers" = { }; + "nix-fetchers-c" = { }; "nix-fetchers-tests" = { }; "nix-expr" = { }; diff --git a/meson.build b/meson.build index 49adf9832..621946608 100644 --- a/meson.build +++ b/meson.build @@ -33,6 +33,7 @@ endif # External C wrapper libraries subproject('libutil-c') subproject('libstore-c') +subproject('libfetchers-c') subproject('libexpr-c') subproject('libflake-c') subproject('libmain-c') diff --git a/packaging/components.nix b/packaging/components.nix index ea5d97399..6351ac712 100644 --- a/packaging/components.nix +++ b/packaging/components.nix @@ -333,6 +333,7 @@ in nix-store-tests = callPackage ../src/libstore-tests/package.nix { }; nix-fetchers = callPackage ../src/libfetchers/package.nix { }; + nix-fetchers-c = callPackage ../src/libfetchers-c/package.nix { }; nix-fetchers-tests = callPackage ../src/libfetchers-tests/package.nix { }; nix-expr = callPackage ../src/libexpr/package.nix { }; diff --git a/packaging/everything.nix b/packaging/everything.nix index 1835eefb6..5bf57f95a 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -15,6 +15,7 @@ nix-store-tests, nix-fetchers, + nix-fetchers-c, nix-fetchers-tests, nix-expr, @@ -54,6 +55,7 @@ let nix-store nix-store-c nix-fetchers + nix-fetchers-c nix-expr nix-expr-c nix-flake @@ -230,6 +232,7 @@ stdenv.mkDerivation (finalAttrs: { "nix-expr" "nix-expr-c" "nix-fetchers" + "nix-fetchers-c" "nix-flake" "nix-flake-c" "nix-main" diff --git a/packaging/hydra.nix b/packaging/hydra.nix index 74e245f26..ee4cabe62 100644 --- a/packaging/hydra.nix +++ b/packaging/hydra.nix @@ -48,6 +48,7 @@ let "nix-store-test-support" "nix-store-tests" "nix-fetchers" + "nix-fetchers-c" "nix-fetchers-tests" "nix-expr" "nix-expr-c" diff --git a/src/libfetchers-c/.version b/src/libfetchers-c/.version new file mode 120000 index 000000000..b7badcd0c --- /dev/null +++ b/src/libfetchers-c/.version @@ -0,0 +1 @@ +../../.version \ No newline at end of file diff --git a/src/libfetchers-c/meson.build b/src/libfetchers-c/meson.build new file mode 100644 index 000000000..e34997f09 --- /dev/null +++ b/src/libfetchers-c/meson.build @@ -0,0 +1,65 @@ +project('nix-fetchers-c', 'cpp', + version : files('.version'), + default_options : [ + 'cpp_std=c++2a', + # TODO(Qyriad): increase the warning level + 'warning_level=1', + 'errorlogs=true', # Please print logs for tests that fail + ], + meson_version : '>= 1.1', + license : 'LGPL-2.1-or-later', +) + +cxx = meson.get_compiler('cpp') + +subdir('nix-meson-build-support/deps-lists') + +deps_private_maybe_subproject = [ + dependency('nix-util'), + dependency('nix-store'), + dependency('nix-fetchers'), +] +deps_public_maybe_subproject = [ + dependency('nix-util-c'), + dependency('nix-store-c'), +] +subdir('nix-meson-build-support/subprojects') + +add_project_arguments( + language : 'cpp', +) + +subdir('nix-meson-build-support/common') + +sources = files( + 'nix_api_fetchers.cc', +) + +include_dirs = [include_directories('.')] + +headers = files( + 'nix_api_fetchers.h', + 'nix_api_fetchers_internal.hh', +) + +# TODO move this header to libexpr, maybe don't use it in tests? +headers += files('nix_api_fetchers.h') + +subdir('nix-meson-build-support/export-all-symbols') +subdir('nix-meson-build-support/windows-version') + +this_library = library( + 'nixfetchersc', + sources, + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args: linker_export_flags, + prelink : true, # For C++ static initializers + install : true, +) + +install_headers(headers, preserve_path : true) + +libraries_private = [] + +subdir('nix-meson-build-support/export') diff --git a/src/libfetchers-c/nix-meson-build-support b/src/libfetchers-c/nix-meson-build-support new file mode 120000 index 000000000..0b140f56b --- /dev/null +++ b/src/libfetchers-c/nix-meson-build-support @@ -0,0 +1 @@ +../../nix-meson-build-support \ No newline at end of file diff --git a/src/libfetchers-c/nix_api_fetchers.cc b/src/libfetchers-c/nix_api_fetchers.cc new file mode 100644 index 000000000..4e8037a5e --- /dev/null +++ b/src/libfetchers-c/nix_api_fetchers.cc @@ -0,0 +1,19 @@ +#include "nix_api_fetchers.h" +#include "nix_api_fetchers_internal.hh" +#include "nix_api_util_internal.h" + +nix_fetchers_settings * nix_fetchers_settings_new(nix_c_context * context) +{ + try { + auto fetchersSettings = nix::make_ref(nix::fetchers::Settings{}); + return new nix_fetchers_settings{ + .settings = fetchersSettings, + }; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_fetchers_settings_free(nix_fetchers_settings * settings) +{ + delete settings; +} diff --git a/src/libfetchers-c/nix_api_fetchers.h b/src/libfetchers-c/nix_api_fetchers.h new file mode 100644 index 000000000..19da112a6 --- /dev/null +++ b/src/libfetchers-c/nix_api_fetchers.h @@ -0,0 +1,32 @@ +#ifndef NIX_API_FETCHERS_H +#define NIX_API_FETCHERS_H +/** @defgroup libfetchers libfetchers + * @brief Bindings to the Nix fetchers library + * @{ + */ +/** @file + * @brief Main entry for the libfetchers C bindings + */ + +#include "nix_api_util.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Type definitions +/** + * @brief Shared settings object + */ +typedef struct nix_fetchers_settings nix_fetchers_settings; + +nix_fetchers_settings * nix_fetchers_settings_new(nix_c_context * context); + +void nix_fetchers_settings_free(nix_fetchers_settings * settings); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // NIX_API_FETCHERS_H \ No newline at end of file diff --git a/src/libfetchers-c/nix_api_fetchers_internal.hh b/src/libfetchers-c/nix_api_fetchers_internal.hh new file mode 100644 index 000000000..b0dea5754 --- /dev/null +++ b/src/libfetchers-c/nix_api_fetchers_internal.hh @@ -0,0 +1,12 @@ +#pragma once +#include "nix/fetchers/fetch-settings.hh" +#include "nix/util/ref.hh" + +/** + * A shared reference to `nix::fetchers::Settings` + * @see nix::fetchers::Settings + */ +struct nix_fetchers_settings +{ + nix::ref settings; +}; diff --git a/src/libfetchers-c/package.nix b/src/libfetchers-c/package.nix new file mode 100644 index 000000000..9a601d704 --- /dev/null +++ b/src/libfetchers-c/package.nix @@ -0,0 +1,50 @@ +{ + lib, + mkMesonLibrary, + + nix-store-c, + nix-expr-c, + nix-util-c, + nix-fetchers, + + # Configuration Options + + version, +}: + +let + inherit (lib) fileset; +in + +mkMesonLibrary (finalAttrs: { + pname = "nix-fetchers-c"; + inherit version; + + workDir = ./.; + fileset = fileset.unions [ + ../../nix-meson-build-support + ./nix-meson-build-support + ../../.version + ./.version + ./meson.build + # ./meson.options + (fileset.fileFilter (file: file.hasExt "cc") ./.) + (fileset.fileFilter (file: file.hasExt "hh") ./.) + (fileset.fileFilter (file: file.hasExt "h") ./.) + ]; + + propagatedBuildInputs = [ + nix-util-c + nix-expr-c + nix-store-c + nix-fetchers + ]; + + mesonFlags = [ + ]; + + meta = { + platforms = lib.platforms.unix ++ lib.platforms.windows; + }; + +}) diff --git a/src/libfetchers-tests/meson.build b/src/libfetchers-tests/meson.build index 12b748e65..33bc7f30e 100644 --- a/src/libfetchers-tests/meson.build +++ b/src/libfetchers-tests/meson.build @@ -17,6 +17,7 @@ subdir('nix-meson-build-support/deps-lists') deps_private_maybe_subproject = [ dependency('nix-store-test-support'), dependency('nix-fetchers'), + dependency('nix-fetchers-c'), ] deps_public_maybe_subproject = [ ] @@ -39,6 +40,7 @@ subdir('nix-meson-build-support/common') sources = files( 'access-tokens.cc', 'git-utils.cc', + 'nix_api_fetchers.cc', 'public-key.cc', ) diff --git a/src/libfetchers-tests/nix_api_fetchers.cc b/src/libfetchers-tests/nix_api_fetchers.cc new file mode 100644 index 000000000..8f3e6e3c5 --- /dev/null +++ b/src/libfetchers-tests/nix_api_fetchers.cc @@ -0,0 +1,18 @@ +#include "gmock/gmock.h" +#include + +#include "nix_api_fetchers.h" +#include "nix/store/tests/nix_api_store.hh" + +namespace nixC { + +TEST_F(nix_api_store_test, nix_api_fetchers_new_free) +{ + nix_fetchers_settings * settings = nix_fetchers_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, settings); + + nix_fetchers_settings_free(settings); +} + +} // namespace nixC diff --git a/src/libfetchers-tests/package.nix b/src/libfetchers-tests/package.nix index 6e3581183..48c1a07d8 100644 --- a/src/libfetchers-tests/package.nix +++ b/src/libfetchers-tests/package.nix @@ -5,6 +5,7 @@ mkMesonExecutable, nix-fetchers, + nix-fetchers-c, nix-store-test-support, libgit2, @@ -40,6 +41,7 @@ mkMesonExecutable (finalAttrs: { buildInputs = [ nix-fetchers + nix-fetchers-c nix-store-test-support rapidcheck gtest diff --git a/src/libflake-c/meson.build b/src/libflake-c/meson.build index 435035b86..5a81618c8 100644 --- a/src/libflake-c/meson.build +++ b/src/libflake-c/meson.build @@ -17,12 +17,14 @@ subdir('nix-meson-build-support/deps-lists') deps_private_maybe_subproject = [ dependency('nix-util'), dependency('nix-store'), + dependency('nix-fetchers'), dependency('nix-expr'), dependency('nix-flake'), ] deps_public_maybe_subproject = [ dependency('nix-util-c'), dependency('nix-store-c'), + dependency('nix-fetchers-c'), dependency('nix-expr-c'), ] subdir('nix-meson-build-support/subprojects') diff --git a/src/libflake-c/package.nix b/src/libflake-c/package.nix index 114950852..8c6883d9c 100644 --- a/src/libflake-c/package.nix +++ b/src/libflake-c/package.nix @@ -4,6 +4,7 @@ nix-store-c, nix-expr-c, + nix-fetchers-c, nix-flake, # Configuration Options @@ -35,6 +36,7 @@ mkMesonLibrary (finalAttrs: { propagatedBuildInputs = [ nix-expr-c nix-store-c + nix-fetchers-c nix-flake ]; From a0a1d003700917b05acb6a21f5381c868297e0d8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 28 Mar 2025 13:10:35 +0000 Subject: [PATCH 7/9] 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 From 1a3789e222bb74a6e7122133af2a2d69268921f8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 2 Apr 2025 16:55:59 +0200 Subject: [PATCH 8/9] fix: nix_clear_err in nix_flake_* functions --- src/libflake-c/nix_api_flake.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc index 6dc3d1476..4ce597952 100644 --- a/src/libflake-c/nix_api_flake.cc +++ b/src/libflake-c/nix_api_flake.cc @@ -10,6 +10,7 @@ nix_flake_settings * nix_flake_settings_new(nix_c_context * context) { + nix_clear_err(context); try { auto settings = nix::make_ref(); return new nix_flake_settings{settings}; @@ -25,6 +26,7 @@ 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) { + nix_clear_err(context); try { settings->settings->configureEvalSettings(builder->settings); } From 8c903e0402f24f1bf1c215f2650979c369f51847 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 2 Apr 2025 16:59:07 +0200 Subject: [PATCH 9/9] nix-flake-c: Add lock flags Going with a slightly more limited, high level API supporting the three main use cases. This should allow the underlying code to evolve more freely. --- src/libflake-c/nix_api_flake.cc | 67 +++++++- src/libflake-c/nix_api_flake.h | 44 +++++ src/libflake-tests/nix_api_flake.cc | 243 +++++++++++++++++++++++++--- 3 files changed, 328 insertions(+), 26 deletions(-) diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc index 4ce597952..06b139f91 100644 --- a/src/libflake-c/nix_api_flake.cc +++ b/src/libflake-c/nix_api_flake.cc @@ -97,7 +97,16 @@ nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_fla { nix_clear_err(context); try { - auto lockSettings = nix::make_ref(); + auto lockSettings = nix::make_ref(nix::flake::LockFlags{ + .recreateLockFile = false, + .updateLockFile = true, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .writeLockFile = true, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .failOnUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .useRegistries = false, + .allowUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .commitLockFile = false, + + }); return new nix_flake_lock_flags{lockSettings}; } NIXC_CATCH_ERRS_NULL @@ -108,16 +117,68 @@ void nix_flake_lock_flags_free(nix_flake_lock_flags * flags) delete flags; } +nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = true; + flags->lockFlags->writeLockFile = false; + flags->lockFlags->failOnUnlocked = false; + flags->lockFlags->allowUnlocked = true; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = true; + flags->lockFlags->writeLockFile = true; + flags->lockFlags->failOnUnlocked = false; + flags->lockFlags->allowUnlocked = true; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = false; + flags->lockFlags->writeLockFile = false; + flags->lockFlags->failOnUnlocked = true; + flags->lockFlags->allowUnlocked = false; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_add_input_override( + nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef) +{ + nix_clear_err(context); + try { + auto path = nix::flake::parseInputAttrPath(inputPath); + flags->lockFlags->inputOverrides.emplace(path, *flakeRef->flakeRef); + if (flags->lockFlags->writeLockFile) { + return nix_flake_lock_flags_set_mode_virtual(context, flags); + } + } + NIXC_CATCH_ERRS +} + nix_locked_flake * nix_flake_lock( nix_c_context * context, - nix_flake_settings * settings, + nix_fetchers_settings * fetchSettings, + nix_flake_settings * flakeSettings, EvalState * eval_state, nix_flake_lock_flags * flags, nix_flake_reference * flakeReference) { + nix_clear_err(context); try { auto lockedFlake = nix::make_ref(nix::flake::lockFlake( - *settings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags)); + *flakeSettings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags)); return new nix_locked_flake{lockedFlake}; } NIXC_CATCH_ERRS_NULL diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h index c82fe29c3..f5b9dc542 100644 --- a/src/libflake-c/nix_api_flake.h +++ b/src/libflake-c/nix_api_flake.h @@ -126,6 +126,49 @@ nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_fla */ void nix_flake_lock_flags_free(nix_flake_lock_flags * settings); +/** + * @brief Put the lock flags in a mode that checks whether the lock is up to date. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @return NIX_OK on success, NIX_ERR on failure + * + * This causes `nix_flake_lock` to fail if the lock needs to be updated. + */ +nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Put the lock flags in a mode that updates the lock file in memory, if needed. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] update Whether to allow updates + * + * This will cause `nix_flake_lock` to update the lock file in memory, if needed. + */ +nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Put the lock flags in a mode that updates the lock file on disk, if needed. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] update Whether to allow updates + * + * This will cause `nix_flake_lock` to update the lock file on disk, if needed. + */ +nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Add input overrides to the lock flags + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] inputPath The input path to override + * @param[in] flakeRef The flake reference to use as the override + * + * This switches the `flags` to `nix_flake_lock_flags_set_mode_virtual` if not in mode + * `nix_flake_lock_flags_set_mode_check`. + */ +nix_err nix_flake_lock_flags_add_input_override( + nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef); + /** * @brief Lock a flake, if not already locked. * @param[out] context Optional, stores error information @@ -135,6 +178,7 @@ void nix_flake_lock_flags_free(nix_flake_lock_flags * settings); */ nix_locked_flake * nix_flake_lock( nix_c_context * context, + nix_fetchers_settings * fetchSettings, nix_flake_settings * settings, EvalState * eval_state, nix_flake_lock_flags * flags, diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc index eb1f7c486..f7e0cb719 100644 --- a/src/libflake-tests/nix_api_flake.cc +++ b/src/libflake-tests/nix_api_flake.cc @@ -71,15 +71,8 @@ TEST_F(nix_api_store_test, nix_api_flake_reference_not_absolute_no_basedir_fail) 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)); - + ctx, fetchSettings, settings, parseFlags, str.data(), str.size(), &flakeReference, OBSERVE_STRING(fragment)); + ASSERT_NE(NIX_OK, r); ASSERT_EQ(nullptr, flakeReference); @@ -126,11 +119,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) assert_ctx_ok(); ASSERT_NE(nullptr, parseFlags); - auto r0 = nix_flake_reference_parse_flags_set_base_directory( - ctx, - parseFlags, - tmpDir.c_str(), - tmpDir.size()); + 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); @@ -138,14 +127,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) 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)); + ctx, fetchSettings, settings, parseFlags, ref.data(), ref.size(), &flakeReference, OBSERVE_STRING(fragment)); assert_ctx_ok(); ASSERT_EQ(NIX_OK, r); ASSERT_NE(nullptr, flakeReference); @@ -157,7 +139,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) assert_ctx_ok(); ASSERT_NE(nullptr, lockFlags); - auto lockedFlake = nix_flake_lock(ctx, settings, state, lockFlags, flakeReference); + auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); assert_ctx_ok(); ASSERT_NE(nullptr, lockedFlake); @@ -183,4 +165,219 @@ TEST_F(nix_api_store_test, nix_api_load_flake) nix_flake_settings_free(settings); } +TEST_F(nix_api_store_test, nix_api_load_flake_with_flags) +{ + nix_libstore_init(ctx); + assert_ctx_ok(); + nix_libexpr_init(ctx); + assert_ctx_ok(); + + auto tmpDir = nix::createTempDir(); + nix::AutoDelete delTmpDir(tmpDir, true); + + nix::createDirs(tmpDir + "/b"); + nix::writeFile(tmpDir + "/b/flake.nix", R"( + { + outputs = { ... }: { + hello = "BOB"; + }; + } + )"); + + nix::createDirs(tmpDir + "/a"); + nix::writeFile(tmpDir + "/a/flake.nix", R"( + { + inputs.b.url = ")" + tmpDir + R"(/b"; + outputs = { b, ... }: { + hello = b.hello; + }; + } + )"); + + nix::createDirs(tmpDir + "/c"); + nix::writeFile(tmpDir + "/c/flake.nix", R"( + { + outputs = { ... }: { + hello = "Claire"; + }; + } + )"); + + nix_libstore_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 = "./a"; + 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, ""); + + // Step 1: Do not update, fails + + auto lockFlags = nix_flake_lock_flags_new(ctx, settings); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockFlags); + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + assert_ctx_ok(); + + // Step 2: Update but do not write, succeeds + + auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_err(); + ASSERT_EQ(nullptr, lockedFlake); + + nix_flake_lock_flags_set_mode_virtual(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + 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("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 3: Lock was not written, so Step 1 would fail again + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_err(); + ASSERT_EQ(nullptr, lockedFlake); + + // Step 4: Update and write, succeeds + + nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 5: Lock was written, so Step 1 would succeed + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 6: Lock with override, do not write + + nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags); + assert_ctx_ok(); + + nix_flake_reference * overrideFlakeReference = nullptr; + nix_flake_reference_and_fragment_from_string( + ctx, fetchSettings, settings, parseFlags, "./c", 3, &overrideFlakeReference, OBSERVE_STRING(fragment)); + assert_ctx_ok(); + ASSERT_NE(nullptr, overrideFlakeReference); + + nix_flake_lock_flags_add_input_override(ctx, lockFlags, "b", overrideFlakeReference); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("Claire", helloStr); + + nix_flake_reference_parse_flags_free(parseFlags); + nix_flake_lock_flags_free(lockFlags); + nix_flake_reference_free(flakeReference); + nix_state_free(state); + nix_flake_settings_free(settings); +} + } // namespace nixC