mirror of
https://github.com/NixOS/nix.git
synced 2025-11-09 20:16:03 +01:00
The short answer for why we need to do this is so we can consistently do
`#include "nix/..."`. Without this change, there are ways to still make
that work, but they are hacky, and they have downsides such as making it
harder to make sure headers from the wrong Nix library (e..g.
`libnixexpr` headers in `libnixutil`) aren't being used.
The C API alraedy used `nix_api_*`, so its headers are *not* put in
subdirectories accordingly.
Progress on #7876
We resisted doing this for a while because it would be annoying to not
have the header source file pairs close by / easy to change file
path/name from one to the other. But I am ameliorating that with
symlinks in the next commit.
(cherry picked from commit f3e1c47f47)
441 lines
15 KiB
C++
441 lines
15 KiB
C++
#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/tests/nix_api_expr.hh"
|
|
#include "nix/tests/string_callback.hh"
|
|
#include "nix/file-system.hh"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
namespace nixC {
|
|
|
|
TEST_F(nix_api_store_test, nix_eval_state_lookup_path)
|
|
{
|
|
auto tmpDir = nix::createTempDir();
|
|
auto delTmpDir = std::make_unique<nix::AutoDelete>(tmpDir, true);
|
|
auto nixpkgs = tmpDir + "/pkgs";
|
|
auto nixos = tmpDir + "/cfg";
|
|
std::filesystem::create_directories(nixpkgs);
|
|
std::filesystem::create_directories(nixos);
|
|
|
|
std::string nixpkgsEntry = "nixpkgs=" + nixpkgs;
|
|
std::string nixosEntry = "nixos-config=" + nixos;
|
|
const char * lookupPath[] = {nixpkgsEntry.c_str(), nixosEntry.c_str(), nullptr};
|
|
|
|
auto builder = nix_eval_state_builder_new(ctx, store);
|
|
assert_ctx_ok();
|
|
|
|
ASSERT_EQ(NIX_OK, nix_eval_state_builder_set_lookup_path(ctx, builder, lookupPath));
|
|
assert_ctx_ok();
|
|
|
|
auto state = nix_eval_state_build(ctx, builder);
|
|
assert_ctx_ok();
|
|
|
|
nix_eval_state_builder_free(builder);
|
|
|
|
Value * value = nix_alloc_value(ctx, state);
|
|
nix_expr_eval_from_string(ctx, state, "builtins.seq <nixos-config> <nixpkgs>", ".", value);
|
|
assert_ctx_ok();
|
|
|
|
ASSERT_EQ(nix_get_type(ctx, value), NIX_TYPE_PATH);
|
|
assert_ctx_ok();
|
|
|
|
auto pathStr = nix_get_path_string(ctx, value);
|
|
assert_ctx_ok();
|
|
ASSERT_EQ(0, strcmp(pathStr, nixpkgs.c_str()));
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
|
|
{
|
|
nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value);
|
|
nix_value_force(nullptr, state, value);
|
|
std::string result;
|
|
nix_get_string(nullptr, value, OBSERVE_STRING(result));
|
|
|
|
ASSERT_STREQ(PACKAGE_VERSION, result.c_str());
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_eval_add_numbers)
|
|
{
|
|
nix_expr_eval_from_string(nullptr, state, "1 + 1", ".", value);
|
|
nix_value_force(nullptr, state, value);
|
|
auto result = nix_get_int(nullptr, value);
|
|
|
|
ASSERT_EQ(2, result);
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_eval_drv)
|
|
{
|
|
auto expr = R"(derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; })";
|
|
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
|
|
ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(nullptr, value));
|
|
|
|
EvalState * stateFn = nix_state_create(nullptr, nullptr, store);
|
|
nix_value * valueFn = nix_alloc_value(nullptr, state);
|
|
nix_expr_eval_from_string(nullptr, stateFn, "builtins.toString", ".", valueFn);
|
|
ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(nullptr, valueFn));
|
|
|
|
EvalState * stateResult = nix_state_create(nullptr, nullptr, store);
|
|
nix_value * valueResult = nix_alloc_value(nullptr, stateResult);
|
|
nix_value_call(ctx, stateResult, valueFn, value, valueResult);
|
|
ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(nullptr, valueResult));
|
|
|
|
std::string p;
|
|
nix_get_string(nullptr, valueResult, OBSERVE_STRING(p));
|
|
std::string pEnd = "-myname";
|
|
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
|
|
|
// Clean up
|
|
nix_gc_decref(nullptr, valueFn);
|
|
nix_state_free(stateFn);
|
|
|
|
nix_gc_decref(nullptr, valueResult);
|
|
nix_state_free(stateResult);
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_build_drv)
|
|
{
|
|
auto expr = R"(derivation { name = "myname";
|
|
system = builtins.currentSystem;
|
|
builder = "/bin/sh";
|
|
args = [ "-c" "echo foo > $out" ];
|
|
})";
|
|
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
|
|
|
|
nix_value * drvPathValue = nix_get_attr_byname(nullptr, value, state, "drvPath");
|
|
std::string drvPath;
|
|
nix_get_string(nullptr, drvPathValue, OBSERVE_STRING(drvPath));
|
|
|
|
std::string p = drvPath;
|
|
std::string pEnd = "-myname.drv";
|
|
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
|
|
|
// NOTE: .drvPath should be usually be ignored. Output paths are more versatile.
|
|
// See https://github.com/NixOS/nix/issues/6507
|
|
// Use e.g. nix_string_realise to realise the output.
|
|
StorePath * drvStorePath = nix_store_parse_path(ctx, store, drvPath.c_str());
|
|
ASSERT_EQ(true, nix_store_is_valid_path(ctx, store, drvStorePath));
|
|
|
|
nix_value * outPathValue = nix_get_attr_byname(ctx, value, state, "outPath");
|
|
std::string outPath;
|
|
nix_get_string(ctx, outPathValue, OBSERVE_STRING(outPath));
|
|
|
|
p = outPath;
|
|
pEnd = "-myname";
|
|
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
|
ASSERT_EQ(true, drvStorePath->path.isDerivation());
|
|
|
|
StorePath * outStorePath = nix_store_parse_path(ctx, store, outPath.c_str());
|
|
ASSERT_EQ(false, nix_store_is_valid_path(ctx, store, outStorePath));
|
|
|
|
nix_store_realise(ctx, store, drvStorePath, nullptr, nullptr);
|
|
auto is_valid_path = nix_store_is_valid_path(ctx, store, outStorePath);
|
|
ASSERT_EQ(true, is_valid_path);
|
|
|
|
// Clean up
|
|
nix_store_path_free(drvStorePath);
|
|
nix_store_path_free(outStorePath);
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_value)
|
|
{
|
|
auto expr = "true";
|
|
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
|
assert_ctx_ok();
|
|
auto r = nix_string_realise(ctx, state, value, false);
|
|
ASSERT_EQ(nullptr, r);
|
|
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
|
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("cannot coerce")));
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_build)
|
|
{
|
|
auto expr = R"(
|
|
derivation { name = "letsbuild";
|
|
system = builtins.currentSystem;
|
|
builder = "/bin/sh";
|
|
args = [ "-c" "echo failing a build for testing purposes; exit 1;" ];
|
|
}
|
|
)";
|
|
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
|
assert_ctx_ok();
|
|
auto r = nix_string_realise(ctx, state, value, false);
|
|
ASSERT_EQ(nullptr, r);
|
|
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
|
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("failed with exit code 1")));
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_realise_context)
|
|
{
|
|
// TODO (ca-derivations): add a content-addressing derivation output, which produces a placeholder
|
|
auto expr = R"(
|
|
''
|
|
a derivation output: ${
|
|
derivation { name = "letsbuild";
|
|
system = builtins.currentSystem;
|
|
builder = "/bin/sh";
|
|
args = [ "-c" "echo foo > $out" ];
|
|
}}
|
|
a path: ${builtins.toFile "just-a-file" "ooh file good"}
|
|
a derivation path by itself: ${
|
|
builtins.unsafeDiscardOutputDependency
|
|
(derivation {
|
|
name = "not-actually-built-yet";
|
|
system = builtins.currentSystem;
|
|
builder = "/bin/sh";
|
|
args = [ "-c" "echo foo > $out" ];
|
|
}).drvPath}
|
|
''
|
|
)";
|
|
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
|
assert_ctx_ok();
|
|
auto r = nix_string_realise(ctx, state, value, false);
|
|
assert_ctx_ok();
|
|
ASSERT_NE(nullptr, r);
|
|
|
|
auto s = std::string(nix_realised_string_get_buffer_start(r), nix_realised_string_get_buffer_size(r));
|
|
|
|
EXPECT_THAT(s, testing::StartsWith("a derivation output:"));
|
|
EXPECT_THAT(s, testing::HasSubstr("-letsbuild\n"));
|
|
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-letsbuild.drv")));
|
|
EXPECT_THAT(s, testing::HasSubstr("a path:"));
|
|
EXPECT_THAT(s, testing::HasSubstr("-just-a-file"));
|
|
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-just-a-file.drv")));
|
|
EXPECT_THAT(s, testing::Not(testing::HasSubstr("ooh file good")));
|
|
EXPECT_THAT(s, testing::HasSubstr("a derivation path by itself:"));
|
|
EXPECT_THAT(s, testing::EndsWith("-not-actually-built-yet.drv\n"));
|
|
|
|
std::vector<std::string> names;
|
|
size_t n = nix_realised_string_get_store_path_count(r);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
const StorePath * p = nix_realised_string_get_store_path(r, i);
|
|
ASSERT_NE(nullptr, p);
|
|
std::string name;
|
|
nix_store_path_name(p, OBSERVE_STRING(name));
|
|
names.push_back(name);
|
|
}
|
|
std::sort(names.begin(), names.end());
|
|
ASSERT_EQ(3, names.size());
|
|
EXPECT_THAT(names[0], testing::StrEq("just-a-file"));
|
|
EXPECT_THAT(names[1], testing::StrEq("letsbuild"));
|
|
EXPECT_THAT(names[2], testing::StrEq("not-actually-built-yet.drv"));
|
|
|
|
nix_realised_string_free(r);
|
|
}
|
|
|
|
const char * SAMPLE_USER_DATA = "whatever";
|
|
|
|
static void
|
|
primop_square(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
|
{
|
|
assert(context);
|
|
assert(state);
|
|
assert(user_data == SAMPLE_USER_DATA);
|
|
auto i = nix_get_int(context, args[0]);
|
|
nix_init_int(context, ret, i * i);
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_primop)
|
|
{
|
|
PrimOp * primop =
|
|
nix_alloc_primop(ctx, primop_square, 1, "square", nullptr, "square an integer", (void *) SAMPLE_USER_DATA);
|
|
assert_ctx_ok();
|
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_primop(ctx, primopValue, primop);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * three = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_int(ctx, three, 3);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * result = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_value_call(ctx, state, primopValue, three, result);
|
|
assert_ctx_ok();
|
|
|
|
auto r = nix_get_int(ctx, result);
|
|
ASSERT_EQ(9, r);
|
|
}
|
|
|
|
static void
|
|
primop_repeat(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
|
{
|
|
assert(context);
|
|
assert(state);
|
|
assert(user_data == SAMPLE_USER_DATA);
|
|
|
|
// Get the string to repeat
|
|
std::string s;
|
|
if (nix_get_string(context, args[0], OBSERVE_STRING(s)) != NIX_OK)
|
|
return;
|
|
|
|
// Get the number of times to repeat
|
|
auto n = nix_get_int(context, args[1]);
|
|
if (nix_err_code(context) != NIX_OK)
|
|
return;
|
|
|
|
// Repeat the string
|
|
std::string result;
|
|
for (int i = 0; i < n; ++i)
|
|
result += s;
|
|
|
|
nix_init_string(context, ret, result.c_str());
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_multiple_calls)
|
|
{
|
|
PrimOp * primop =
|
|
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
|
|
assert_ctx_ok();
|
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_primop(ctx, primopValue, primop);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * hello = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_string(ctx, hello, "hello");
|
|
assert_ctx_ok();
|
|
|
|
nix_value * three = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_int(ctx, three, 3);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * partial = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_value_call(ctx, state, primopValue, hello, partial);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * result = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_value_call(ctx, state, partial, three, result);
|
|
assert_ctx_ok();
|
|
|
|
std::string r;
|
|
nix_get_string(ctx, result, OBSERVE_STRING(r));
|
|
ASSERT_STREQ("hellohellohello", r.c_str());
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_single_call)
|
|
{
|
|
PrimOp * primop =
|
|
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
|
|
assert_ctx_ok();
|
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_primop(ctx, primopValue, primop);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * hello = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_string(ctx, hello, "hello");
|
|
assert_ctx_ok();
|
|
|
|
nix_value * three = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_int(ctx, three, 3);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * result = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
NIX_VALUE_CALL(ctx, state, result, primopValue, hello, three);
|
|
assert_ctx_ok();
|
|
|
|
std::string r;
|
|
nix_get_string(ctx, result, OBSERVE_STRING(r));
|
|
assert_ctx_ok();
|
|
|
|
ASSERT_STREQ("hellohellohello", r.c_str());
|
|
}
|
|
|
|
static void
|
|
primop_bad_no_return(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
|
{
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_expr_primop_bad_no_return)
|
|
{
|
|
PrimOp * primop =
|
|
nix_alloc_primop(ctx, primop_bad_no_return, 1, "badNoReturn", nullptr, "a broken primop", nullptr);
|
|
assert_ctx_ok();
|
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_primop(ctx, primopValue, primop);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * three = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_int(ctx, three, 3);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * result = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_value_call(ctx, state, primopValue, three, result);
|
|
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
|
ASSERT_THAT(
|
|
ctx->last_err,
|
|
testing::Optional(
|
|
testing::HasSubstr("Implementation error in custom function: return value was not initialized")));
|
|
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badNoReturn")));
|
|
}
|
|
|
|
static void primop_bad_return_thunk(
|
|
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
|
{
|
|
nix_init_apply(context, ret, args[0], args[1]);
|
|
}
|
|
TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
|
|
{
|
|
PrimOp * primop =
|
|
nix_alloc_primop(ctx, primop_bad_return_thunk, 2, "badReturnThunk", nullptr, "a broken primop", nullptr);
|
|
assert_ctx_ok();
|
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_primop(ctx, primopValue, primop);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * toString = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_expr_eval_from_string(ctx, state, "builtins.toString", ".", toString);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * four = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
nix_init_int(ctx, four, 4);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * result = nix_alloc_value(ctx, state);
|
|
assert_ctx_ok();
|
|
NIX_VALUE_CALL(ctx, state, result, primopValue, toString, four);
|
|
|
|
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
|
ASSERT_THAT(
|
|
ctx->last_err,
|
|
testing::Optional(
|
|
testing::HasSubstr("Implementation error in custom function: return value must not be a thunk")));
|
|
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badReturnThunk")));
|
|
}
|
|
|
|
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
|
{
|
|
nix_value * n = nix_alloc_value(ctx, state);
|
|
nix_init_int(ctx, n, 3);
|
|
assert_ctx_ok();
|
|
|
|
nix_value * r = nix_alloc_value(ctx, state);
|
|
nix_value_call_multi(ctx, state, n, 0, nullptr, r);
|
|
assert_ctx_ok();
|
|
|
|
auto rInt = nix_get_int(ctx, r);
|
|
assert_ctx_ok();
|
|
ASSERT_EQ(3, rInt);
|
|
}
|
|
} // namespace nixC
|