mirror of
https://github.com/NixOS/nix.git
synced 2025-11-28 05:00:58 +01:00
Merge pull request #14555 from NixOS/more-store-ffi
libstore-c: Add new derivation and store path functions
This commit is contained in:
commit
05990fb2ec
6 changed files with 330 additions and 3 deletions
|
|
@ -1,3 +1,6 @@
|
|||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
#include "nix_api_store.h"
|
||||
#include "nix_api_store_internal.h"
|
||||
#include "nix_api_util.h"
|
||||
|
|
@ -8,6 +11,7 @@
|
|||
#include "nix/store/store-open.hh"
|
||||
#include "nix/store/build-result.hh"
|
||||
#include "nix/store/local-fs-store.hh"
|
||||
#include "nix/util/base-nix-32.hh"
|
||||
|
||||
#include "nix/store/globals.hh"
|
||||
|
||||
|
|
@ -215,7 +219,65 @@ void nix_derivation_free(nix_derivation * drv)
|
|||
|
||||
StorePath * nix_store_path_clone(const StorePath * p)
|
||||
{
|
||||
return new StorePath{p->path};
|
||||
try {
|
||||
return new StorePath{p->path};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
template<size_t S>
|
||||
static auto to_cpp_array(const uint8_t (&r)[S])
|
||||
{
|
||||
return reinterpret_cast<const std::array<std::byte, S> &>(r);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
nix_err
|
||||
nix_store_path_hash(nix_c_context * context, const StorePath * store_path, nix_store_path_hash_part * hash_part_out)
|
||||
{
|
||||
try {
|
||||
auto hashPart = store_path->path.hashPart();
|
||||
// Decode from Nix32 (base32) encoding to raw bytes
|
||||
auto decoded = nix::BaseNix32::decode(hashPart);
|
||||
|
||||
assert(decoded.size() == sizeof(hash_part_out->bytes));
|
||||
std::memcpy(hash_part_out->bytes, decoded.data(), sizeof(hash_part_out->bytes));
|
||||
return NIX_OK;
|
||||
}
|
||||
NIXC_CATCH_ERRS
|
||||
}
|
||||
|
||||
StorePath * nix_store_create_from_parts(
|
||||
nix_c_context * context, const nix_store_path_hash_part * hash, const char * name, size_t name_len)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
// Encode the 20 raw bytes to Nix32 (base32) format
|
||||
auto hashStr = nix::BaseNix32::encode(std::span<const std::byte>{to_cpp_array(hash->bytes)});
|
||||
|
||||
// Construct the store path basename: <hash>-<name>
|
||||
std::string baseName;
|
||||
baseName += hashStr;
|
||||
baseName += "-";
|
||||
baseName += std::string_view{name, name_len};
|
||||
|
||||
return new StorePath{nix::StorePath(std::move(baseName))};
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
nix_derivation * nix_derivation_clone(const nix_derivation * d)
|
||||
{
|
||||
try {
|
||||
return new nix_derivation{d->drv};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json)
|
||||
|
|
@ -228,6 +290,20 @@ nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store
|
|||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
nix_err nix_derivation_to_json(
|
||||
nix_c_context * context, const nix_derivation * drv, nix_get_string_callback callback, void * userdata)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto result = static_cast<nlohmann::json>(drv->drv).dump();
|
||||
if (callback) {
|
||||
callback(result.data(), result.size(), userdata);
|
||||
}
|
||||
}
|
||||
NIXC_CATCH_ERRS
|
||||
}
|
||||
|
||||
StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation)
|
||||
{
|
||||
if (context)
|
||||
|
|
@ -252,4 +328,14 @@ nix_err nix_store_copy_closure(nix_c_context * context, Store * srcStore, Store
|
|||
NIXC_CATCH_ERRS
|
||||
}
|
||||
|
||||
nix_derivation * nix_store_drv_from_store_path(nix_c_context * context, Store * store, const StorePath * path)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
return new nix_derivation{store->ptr->derivationFromPath(path->path)};
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ nix_err
|
|||
nix_store_get_storedir(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
/**
|
||||
* @brief Parse a Nix store path into a StorePath
|
||||
* @brief Parse a Nix store path that includes the store dir into a StorePath
|
||||
*
|
||||
* @note Don't forget to free this path using nix_store_path_free()!
|
||||
* @param[out] context Optional, stores error information
|
||||
|
|
@ -188,9 +188,16 @@ nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_cal
|
|||
/**
|
||||
* @brief Create a `nix_derivation` from a JSON representation of that derivation.
|
||||
*
|
||||
* @note Unlike `nix_derivation_to_json`, this needs a `Store`. This is because
|
||||
* over time we expect the internal representation of derivations in Nix to
|
||||
* differ from accepted derivation formats. The store argument is here to help
|
||||
* any logic needed to convert from JSON to the internal representation, in
|
||||
* excess of just parsing.
|
||||
*
|
||||
* @param[out] context Optional, stores error information.
|
||||
* @param[in] store nix store reference.
|
||||
* @param[in] json JSON of the derivation as a string.
|
||||
* @return A new derivation, or NULL on error. Free with `nix_derivation_free` when done using the `nix_derivation`.
|
||||
*/
|
||||
nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json);
|
||||
|
||||
|
|
@ -242,6 +249,16 @@ nix_err nix_store_get_fs_closure(
|
|||
void * userdata,
|
||||
void (*callback)(nix_c_context * context, void * userdata, const StorePath * store_path));
|
||||
|
||||
/**
|
||||
* @brief Returns the derivation associated with the store path
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] store The nix store
|
||||
* @param[in] path The nix store path
|
||||
* @return A new derivation, or NULL on error. Free with `nix_derivation_free` when done using the `nix_derivation`.
|
||||
*/
|
||||
nix_derivation * nix_store_drv_from_store_path(nix_c_context * context, Store * store, const StorePath * path);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ extern "C" {
|
|||
/** @brief Nix Derivation */
|
||||
typedef struct nix_derivation nix_derivation;
|
||||
|
||||
/**
|
||||
* @brief Copy a `nix_derivation`
|
||||
*
|
||||
* @param[in] d the derivation to copy
|
||||
* @return a new `nix_derivation`
|
||||
*/
|
||||
nix_derivation * nix_derivation_clone(const nix_derivation * d);
|
||||
|
||||
/**
|
||||
* @brief Deallocate a `nix_derivation`
|
||||
*
|
||||
|
|
@ -28,6 +36,17 @@ typedef struct nix_derivation nix_derivation;
|
|||
*/
|
||||
void nix_derivation_free(nix_derivation * drv);
|
||||
|
||||
/**
|
||||
* @brief Gets the derivation as a JSON string
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] drv The derivation
|
||||
* @param[in] callback Called with the JSON string
|
||||
* @param[in] userdata Arbitrary data passed to the callback
|
||||
*/
|
||||
nix_err nix_derivation_to_json(
|
||||
nix_c_context * context, const nix_derivation * drv, nix_get_string_callback callback, void * userdata);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
* @brief Store path operations
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nix_api_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
@ -44,6 +47,45 @@ void nix_store_path_free(StorePath * p);
|
|||
*/
|
||||
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
/**
|
||||
* @brief A store path hash
|
||||
*
|
||||
* Once decoded from "nix32" encoding, a store path hash is 20 raw bytes.
|
||||
*/
|
||||
typedef struct nix_store_path_hash_part
|
||||
{
|
||||
uint8_t bytes[20];
|
||||
} nix_store_path_hash_part;
|
||||
|
||||
/**
|
||||
* @brief Get the path hash (e.g. "<hash>" in /nix/store/<hash>-<name>)
|
||||
*
|
||||
* The hash is returned as raw bytes, decoded from "nix32" encoding.
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] store_path the path to get the hash from
|
||||
* @param[out] hash_part_out the decoded hash as 20 raw bytes
|
||||
* @return NIX_OK on success, error code on failure
|
||||
*/
|
||||
nix_err
|
||||
nix_store_path_hash(nix_c_context * context, const StorePath * store_path, nix_store_path_hash_part * hash_part_out);
|
||||
|
||||
/**
|
||||
* @brief Create a StorePath from its constituent parts (hash and name)
|
||||
*
|
||||
* This function constructs a store path from a hash and name, without needing
|
||||
* a Store reference or the store directory prefix.
|
||||
*
|
||||
* @note Don't forget to free this path using nix_store_path_free()!
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] hash The store path hash (20 raw bytes)
|
||||
* @param[in] name The store path name (the part after the hash)
|
||||
* @param[in] name_len Length of the name string
|
||||
* @return owned store path, NULL on error
|
||||
*/
|
||||
StorePath * nix_store_create_from_parts(
|
||||
nix_c_context * context, const nix_store_path_hash_part * hash, const char name[/*name_len*/], size_t name_len);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include <fstream>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_store.h"
|
||||
|
||||
|
|
@ -92,6 +94,70 @@ TEST_F(nix_api_store_test, DoesNotCrashWhenContextIsNull)
|
|||
nix_store_path_free(path);
|
||||
}
|
||||
|
||||
// Verify it's 20 bytes
|
||||
static_assert(sizeof(nix_store_path_hash_part::bytes) == 20);
|
||||
static_assert(sizeof(nix_store_path_hash_part::bytes) == sizeof(nix_store_path_hash_part));
|
||||
|
||||
TEST_F(nix_api_store_test, nix_store_path_hash)
|
||||
{
|
||||
StorePath * path = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str());
|
||||
ASSERT_NE(path, nullptr);
|
||||
|
||||
nix_store_path_hash_part hash;
|
||||
auto ret = nix_store_path_hash(ctx, path, &hash);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(ret, NIX_OK);
|
||||
|
||||
// The hash should be non-zero
|
||||
bool allZero = true;
|
||||
for (size_t i = 0; i < sizeof(hash.bytes); i++) {
|
||||
if (hash.bytes[i] != 0) {
|
||||
allZero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(allZero);
|
||||
|
||||
nix_store_path_free(path);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_store_create_from_parts_roundtrip)
|
||||
{
|
||||
// Parse a path
|
||||
StorePath * original = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str());
|
||||
EXPECT_NE(original, nullptr);
|
||||
|
||||
// Get its hash
|
||||
nix_store_path_hash_part hash;
|
||||
auto ret = nix_store_path_hash(ctx, original, &hash);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(ret, NIX_OK);
|
||||
|
||||
// Get its name
|
||||
std::string name;
|
||||
nix_store_path_name(original, OBSERVE_STRING(name));
|
||||
|
||||
// Reconstruct from parts
|
||||
StorePath * reconstructed = nix_store_create_from_parts(ctx, &hash, name.c_str(), name.size());
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(reconstructed, nullptr);
|
||||
|
||||
// Should be equal
|
||||
EXPECT_EQ(original->path, reconstructed->path);
|
||||
|
||||
nix_store_path_free(original);
|
||||
nix_store_path_free(reconstructed);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_store_create_from_parts_invalid_name)
|
||||
{
|
||||
nix_store_path_hash_part hash = {};
|
||||
// Invalid name with spaces
|
||||
StorePath * path = nix_store_create_from_parts(ctx, &hash, "invalid name", 12);
|
||||
ASSERT_EQ(path, nullptr);
|
||||
ASSERT_EQ(nix_err_code(ctx), NIX_ERR_NIX_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, get_version)
|
||||
{
|
||||
std::string str;
|
||||
|
|
@ -795,4 +861,97 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_error_propagati
|
|||
ASSERT_EQ(call_count, 1); // Should have been called exactly once, then aborted
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to load JSON from a test data file
|
||||
*
|
||||
* @param filename Relative path from _NIX_TEST_UNIT_DATA
|
||||
* @return JSON string contents of the file
|
||||
*/
|
||||
static std::string load_json_from_test_data(const char * filename)
|
||||
{
|
||||
std::filesystem::path unitTestData{getenv("_NIX_TEST_UNIT_DATA")};
|
||||
std::ifstream t{unitTestData / filename};
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_derivation_to_json_roundtrip)
|
||||
{
|
||||
// Load JSON from test data
|
||||
auto originalJson = load_json_from_test_data("derivation/invariants/filled-in-deferred-empty-env-var-pre.json");
|
||||
|
||||
// Parse to derivation
|
||||
auto * drv = nix_derivation_from_json(ctx, store, originalJson.c_str());
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(drv, nullptr);
|
||||
|
||||
// Convert back to JSON
|
||||
std::string convertedJson;
|
||||
auto ret = nix_derivation_to_json(ctx, drv, OBSERVE_STRING(convertedJson));
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(ret, NIX_OK);
|
||||
ASSERT_FALSE(convertedJson.empty());
|
||||
|
||||
// Parse both JSON strings to compare (ignoring whitespace differences)
|
||||
auto originalParsed = nlohmann::json::parse(originalJson);
|
||||
auto convertedParsed = nlohmann::json::parse(convertedJson);
|
||||
|
||||
// Remove parts that will be different due to filling-in.
|
||||
originalParsed.at("outputs").erase("out");
|
||||
originalParsed.at("env").erase("out");
|
||||
convertedParsed.at("outputs").erase("out");
|
||||
convertedParsed.at("env").erase("out");
|
||||
|
||||
// They should be equivalent
|
||||
ASSERT_EQ(originalParsed, convertedParsed);
|
||||
|
||||
nix_derivation_free(drv);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_derivation_store_round_trip)
|
||||
{
|
||||
// Load a derivation from JSON
|
||||
auto json = load_json_from_test_data("derivation/invariants/filled-in-deferred-empty-env-var-pre.json");
|
||||
auto * drv = nix_derivation_from_json(ctx, store, json.c_str());
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(drv, nullptr);
|
||||
|
||||
// Add to store
|
||||
auto * drvPath = nix_add_derivation(ctx, store, drv);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(drvPath, nullptr);
|
||||
|
||||
// Retrieve from store
|
||||
auto * drv2 = nix_store_drv_from_store_path(ctx, store, drvPath);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(drv2, nullptr);
|
||||
|
||||
// The round trip should make the same derivation
|
||||
ASSERT_EQ(drv->drv, drv2->drv);
|
||||
|
||||
nix_store_path_free(drvPath);
|
||||
nix_derivation_free(drv);
|
||||
nix_derivation_free(drv2);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_store_test, nix_derivation_clone)
|
||||
{
|
||||
// Load a derivation from JSON
|
||||
auto json = load_json_from_test_data("derivation/invariants/filled-in-deferred-empty-env-var-pre.json");
|
||||
auto * drv = nix_derivation_from_json(ctx, store, json.c_str());
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(drv, nullptr);
|
||||
|
||||
// Clone the derivation
|
||||
auto * drv2 = nix_derivation_clone(drv);
|
||||
ASSERT_NE(drv2, nullptr);
|
||||
|
||||
// The clone should be equal
|
||||
ASSERT_EQ(drv->drv, drv2->drv);
|
||||
|
||||
nix_derivation_free(drv);
|
||||
nix_derivation_free(drv2);
|
||||
}
|
||||
|
||||
} // namespace nixC
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ extern "C" {
|
|||
|
||||
nix_c_context * nix_c_context_create()
|
||||
{
|
||||
return new nix_c_context();
|
||||
try {
|
||||
return new nix_c_context();
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void nix_c_context_free(nix_c_context * context)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue