1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-28 21:21:00 +01:00

libstore-c: Add new derivation and store path functions

Add several new functions to the C API:

StorePath operations:
- nix_store_path_hash: Extract the hash part from a store path
- nix_store_create_from_parts: Construct a store path from hash and name

Derivation operations:
- nix_derivation_clone: Clone a derivation
- nix_derivation_to_json: Serialize a derivation to JSON

Store operations:
- nix_store_drv_from_store_path: Load a derivation from a store path

Test the new functions, and improve documentation of some existing
functions to better distinguish them, also.

Co-authored-by: Tristan Ross <tristan.ross@determinate.systems>
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
This commit is contained in:
John Ericson 2025-11-17 12:14:39 -05:00
parent 6f33f64ce5
commit 1c10ce6047
5 changed files with 320 additions and 1 deletions

View file

@ -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