From 9bc218ca3fc98889719684abba73b5d8a168cf3c Mon Sep 17 00:00:00 2001 From: John Ericson Date: Tue, 2 Sep 2025 11:20:51 -0400 Subject: [PATCH] Add new C API for working with derivations Also test the APIs we just added. --- src/libexpr-tests/main.cc | 39 ++---------- src/libstore-c/nix_api_store.cc | 33 +++++++++++ src/libstore-c/nix_api_store.h | 28 +++++++++ src/libstore-c/nix_api_store_internal.h | 6 ++ .../include/nix/store/tests/meson.build | 1 + .../include/nix/store/tests/nix_api_store.hh | 51 ++++++++++++---- .../include/nix/store/tests/test-main.hh | 13 ++++ src/libstore-test-support/meson.build | 1 + src/libstore-test-support/test-main.cc | 47 +++++++++++++++ .../data/derivation/ca/self-contained.json | 23 ++++++++ src/libstore-tests/main.cc | 15 +++++ src/libstore-tests/meson.build | 1 + src/libstore-tests/nix_api_store.cc | 59 +++++++++++++++++++ 13 files changed, 270 insertions(+), 47 deletions(-) create mode 100644 src/libstore-test-support/include/nix/store/tests/test-main.hh create mode 100644 src/libstore-test-support/test-main.cc create mode 100644 src/libstore-tests/data/derivation/ca/self-contained.json create mode 100644 src/libstore-tests/main.cc diff --git a/src/libexpr-tests/main.cc b/src/libexpr-tests/main.cc index 61b40e834..d6b0d0ab9 100644 --- a/src/libexpr-tests/main.cc +++ b/src/libexpr-tests/main.cc @@ -1,43 +1,14 @@ #include -#include -#include "nix/store/globals.hh" -#include "nix/util/logging.hh" + +#include "nix/store/tests/test-main.hh" using namespace nix; int main(int argc, char ** argv) { - if (argc > 1 && std::string_view(argv[1]) == "__build-remote") { - printError("test-build-remote: not supported in libexpr unit tests"); - return 1; - } - - // Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook. - settings.buildHook = {}; - -#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration. - - // When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's - // sandboxBuildDir, e.g.: Host - // storeDir = /nix/store - // sandboxBuildDir = /build - // This process - // storeDir = /build/foo/bar/store - // sandboxBuildDir = /build - // However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different - // sandboxBuildDir. - settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir"; -#endif - -#ifdef __APPLE__ - // Avoid this error, when already running in a sandbox: - // sandbox-exec: sandbox_apply: Operation not permitted - settings.sandboxMode = smDisabled; - setEnv("_NIX_TEST_NO_SANDBOX", "1"); -#endif - - // For pipe operator tests in trivial.cc - experimentalFeatureSettings.set("experimental-features", "pipe-operators"); + auto res = testMainForBuidingPre(argc, argv); + if (!res) + return res; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index 7ce63f5c2..a319c0c10 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -166,11 +166,44 @@ void nix_store_path_free(StorePath * sp) delete sp; } +void nix_derivation_free(nix_derivation * drv) +{ + delete drv; +} + StorePath * nix_store_path_clone(const StorePath * p) { return new StorePath{p->path}; } +nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto drv = nix::Derivation::fromJSON(*store->ptr, nlohmann::json::parse(json)); + + auto drvPath = nix::writeDerivation(*store->ptr, drv, nix::NoRepair, /* read only */ true); + + drv.checkInvariants(*store->ptr, drvPath); + + return new nix_derivation{drv}; + } + NIXC_CATCH_ERRS_NULL +} + +StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto ret = nix::writeDerivation(*store->ptr, derivation->drv, nix::NoRepair); + + return new StorePath{ret}; + } + NIXC_CATCH_ERRS_NULL +} + nix_err nix_store_copy_closure(nix_c_context * context, Store * srcStore, Store * dstStore, StorePath * path) { if (context) diff --git a/src/libstore-c/nix_api_store.h b/src/libstore-c/nix_api_store.h index 51bd1bc89..e76e376b4 100644 --- a/src/libstore-c/nix_api_store.h +++ b/src/libstore-c/nix_api_store.h @@ -23,6 +23,8 @@ extern "C" { typedef struct Store Store; /** @brief Nix store path */ typedef struct StorePath StorePath; +/** @brief Nix Derivation */ +typedef struct nix_derivation nix_derivation; /** * @brief Initializes the Nix store library @@ -207,6 +209,32 @@ nix_err nix_store_realise( nix_err nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data); +/** + * @brief Create a `nix_derivation` from a JSON representation of that derivation. + * + * @param[out] context Optional, stores error information. + * @param[in] store nix store reference. + * @param[in] json JSON of the derivation as a string. + */ +nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json); + +/** + * @brief Add the given `nix_derivation` to the given store + * + * @param[out] context Optional, stores error information. + * @param[in] store nix store reference. The derivation will be inserted here. + * @param[in] derivation nix_derivation to insert into the given store. + */ +StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation); + +/** + * @brief Deallocate a `nix_derivation` + * + * Does not fail. + * @param[in] drv the derivation to free + */ +void nix_derivation_free(nix_derivation * drv); + /** * @brief Copy the closure of `path` from `srcStore` to `dstStore`. * diff --git a/src/libstore-c/nix_api_store_internal.h b/src/libstore-c/nix_api_store_internal.h index cbe04b2c7..712d96488 100644 --- a/src/libstore-c/nix_api_store_internal.h +++ b/src/libstore-c/nix_api_store_internal.h @@ -1,6 +1,7 @@ #ifndef NIX_API_STORE_INTERNAL_H #define NIX_API_STORE_INTERNAL_H #include "nix/store/store-api.hh" +#include "nix/store/derivations.hh" extern "C" { @@ -14,6 +15,11 @@ struct StorePath nix::StorePath path; }; +struct nix_derivation +{ + nix::Derivation drv; +}; + } // extern "C" #endif diff --git a/src/libstore-test-support/include/nix/store/tests/meson.build b/src/libstore-test-support/include/nix/store/tests/meson.build index f79769d41..33524de38 100644 --- a/src/libstore-test-support/include/nix/store/tests/meson.build +++ b/src/libstore-test-support/include/nix/store/tests/meson.build @@ -9,4 +9,5 @@ headers = files( 'outputs-spec.hh', 'path.hh', 'protocol.hh', + 'test-main.hh', ) diff --git a/src/libstore-test-support/include/nix/store/tests/nix_api_store.hh b/src/libstore-test-support/include/nix/store/tests/nix_api_store.hh index 608aa63d6..7ecc5603b 100644 --- a/src/libstore-test-support/include/nix/store/tests/nix_api_store.hh +++ b/src/libstore-test-support/include/nix/store/tests/nix_api_store.hh @@ -12,33 +12,32 @@ #include namespace nixC { -class nix_api_store_test : public nix_api_util_context + +class nix_api_store_test_base : public nix_api_util_context { public: - nix_api_store_test() + nix_api_store_test_base() { nix_libstore_init(ctx); - init_local_store(); }; - ~nix_api_store_test() override + ~nix_api_store_test_base() override { - nix_store_free(store); - - for (auto & path : std::filesystem::recursive_directory_iterator(nixDir)) { - std::filesystem::permissions(path, std::filesystem::perms::owner_all); + if (exists(std::filesystem::path{nixDir})) { + for (auto & path : std::filesystem::recursive_directory_iterator(nixDir)) { + std::filesystem::permissions(path, std::filesystem::perms::owner_all); + } + std::filesystem::remove_all(nixDir); } - std::filesystem::remove_all(nixDir); } - Store * store; std::string nixDir; std::string nixStoreDir; std::string nixStateDir; std::string nixLogDir; protected: - void init_local_store() + Store * open_local_store() { #ifdef _WIN32 // no `mkdtemp` with MinGW @@ -66,11 +65,37 @@ protected: const char ** params[] = {p1, p2, p3, nullptr}; - store = nix_store_open(ctx, "local", params); + auto * store = nix_store_open(ctx, "local", params); if (!store) { std::string errMsg = nix_err_msg(nullptr, ctx, nullptr); - ASSERT_NE(store, nullptr) << "Could not open store: " << errMsg; + EXPECT_NE(store, nullptr) << "Could not open store: " << errMsg; + assert(store); }; + return store; } }; + +class nix_api_store_test : public nix_api_store_test_base +{ +public: + nix_api_store_test() + : nix_api_store_test_base{} + { + init_local_store(); + }; + + ~nix_api_store_test() override + { + nix_store_free(store); + } + + Store * store; + +protected: + void init_local_store() + { + store = open_local_store(); + } +}; + } // namespace nixC diff --git a/src/libstore-test-support/include/nix/store/tests/test-main.hh b/src/libstore-test-support/include/nix/store/tests/test-main.hh new file mode 100644 index 000000000..3a1897469 --- /dev/null +++ b/src/libstore-test-support/include/nix/store/tests/test-main.hh @@ -0,0 +1,13 @@ +#pragma once + +///@file + +namespace nix { + +/** + * Call this for a GTest test suite that will including performing Nix + * builds, before running tests. + */ +int testMainForBuidingPre(int argc, char ** argv); + +} // namespace nix diff --git a/src/libstore-test-support/meson.build b/src/libstore-test-support/meson.build index 5873680ea..8617225d7 100644 --- a/src/libstore-test-support/meson.build +++ b/src/libstore-test-support/meson.build @@ -34,6 +34,7 @@ sources = files( 'derived-path.cc', 'outputs-spec.cc', 'path.cc', + 'test-main.cc', ) subdir('include/nix/store/tests') diff --git a/src/libstore-test-support/test-main.cc b/src/libstore-test-support/test-main.cc new file mode 100644 index 000000000..0b9072dc0 --- /dev/null +++ b/src/libstore-test-support/test-main.cc @@ -0,0 +1,47 @@ +#include + +#include "nix/store/globals.hh" +#include "nix/util/logging.hh" + +#include "nix/store/tests/test-main.hh" + +namespace nix { + +int testMainForBuidingPre(int argc, char ** argv) +{ + if (argc > 1 && std::string_view(argv[1]) == "__build-remote") { + printError("test-build-remote: not supported in libexpr unit tests"); + return EXIT_FAILURE; + } + + // Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook. + settings.buildHook = {}; + + // No substituters, unless a test specifically requests. + settings.substituters = {}; + +#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration. + + // When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's + // sandboxBuildDir, e.g.: Host + // storeDir = /nix/store + // sandboxBuildDir = /build + // This process + // storeDir = /build/foo/bar/store + // sandboxBuildDir = /build + // However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different + // sandboxBuildDir. + settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir"; +#endif + +#ifdef __APPLE__ + // Avoid this error, when already running in a sandbox: + // sandbox-exec: sandbox_apply: Operation not permitted + settings.sandboxMode = smDisabled; + setEnv("_NIX_TEST_NO_SANDBOX", "1"); +#endif + + return EXIT_SUCCESS; +} + +} // namespace nix diff --git a/src/libstore-tests/data/derivation/ca/self-contained.json b/src/libstore-tests/data/derivation/ca/self-contained.json new file mode 100644 index 000000000..c4ca280ef --- /dev/null +++ b/src/libstore-tests/data/derivation/ca/self-contained.json @@ -0,0 +1,23 @@ +{ + "args": [ + "-c", + "echo $name foo > $out" + ], + "builder": "/bin/sh", + "env": { + "builder": "/bin/sh", + "name": "myname", + "out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9", + "system": "x86_64-linux" + }, + "inputDrvs": {}, + "inputSrcs": [], + "name": "myname", + "outputs": { + "out": { + "hashAlgo": "sha256", + "method": "nar" + } + }, + "system": "x86_64-linux" +} diff --git a/src/libstore-tests/main.cc b/src/libstore-tests/main.cc new file mode 100644 index 000000000..ffe981613 --- /dev/null +++ b/src/libstore-tests/main.cc @@ -0,0 +1,15 @@ +#include + +#include "nix/store/tests/test-main.hh" + +using namespace nix; + +int main(int argc, char ** argv) +{ + auto res = testMainForBuidingPre(argc, argv); + if (res) + return res; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/libstore-tests/meson.build b/src/libstore-tests/meson.build index 4c2840ab7..c494e6a35 100644 --- a/src/libstore-tests/meson.build +++ b/src/libstore-tests/meson.build @@ -66,6 +66,7 @@ sources = files( 'local-overlay-store.cc', 'local-store.cc', 'machines.cc', + 'main.cc', 'nar-info-disk-cache.cc', 'nar-info.cc', 'nix_api_store.cc', diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index c14fb6d9f..dfd554ec1 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -1,7 +1,10 @@ +#include + #include "nix_api_util.h" #include "nix_api_store.h" #include "nix/store/tests/nix_api_store.hh" +#include "nix/store/globals.hh" #include "nix/util/tests/string_callback.hh" #include "nix/util/url.hh" @@ -197,4 +200,60 @@ TEST_F(nix_api_util_context, nix_store_real_path_binary_cache) ASSERT_STREQ(path_raw.c_str(), rp.c_str()); } +template +struct LambdaAdapter +{ + F fun; + + template + static inline auto call(LambdaAdapter * ths, Args... args) + { + return ths->fun(args...); + } + + template + static auto call_void(void * ths, Args... args) + { + return call(static_cast *>(ths), args...); + } +}; + +TEST_F(nix_api_store_test_base, build_from_json) +{ + // FIXME get rid of these + nix::experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations"); + nix::settings.substituters = {}; + + auto * store = open_local_store(); + + std::filesystem::path unitTestData{getenv("_NIX_TEST_UNIT_DATA")}; + + std::ifstream t{unitTestData / "derivation/ca/self-contained.json"}; + std::stringstream buffer; + buffer << t.rdbuf(); + + auto * drv = nix_derivation_from_json(ctx, store, buffer.str().c_str()); + assert_ctx_ok(); + ASSERT_NE(drv, nullptr); + + auto * drvPath = nix_add_derivation(ctx, store, drv); + assert_ctx_ok(); + ASSERT_NE(drv, nullptr); + + auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath) { + auto is_valid_path = nix_store_is_valid_path(ctx, store, outPath); + ASSERT_EQ(is_valid_path, true); + }}; + + auto ret = nix_store_realise( + ctx, store, drvPath, static_cast(&cb), decltype(cb)::call_void); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + // Clean up + nix_store_path_free(drvPath); + nix_derivation_free(drv); + nix_store_free(store); +} + } // namespace nixC