mirror of
https://github.com/NixOS/nix.git
synced 2025-11-28 05:00:58 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
e8a9a1b305
279 changed files with 4694 additions and 1687 deletions
|
|
@ -83,12 +83,22 @@ nlohmann::json SingleBuiltPath::Built::toJSON(const StoreDirConfig & store) cons
|
|||
|
||||
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
|
||||
{
|
||||
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[&](const SingleBuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
|
||||
[&](const SingleBuiltPath::Built & b) { return b.toJSON(store); },
|
||||
},
|
||||
raw());
|
||||
}
|
||||
|
||||
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
|
||||
{
|
||||
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[&](const BuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
|
||||
[&](const BuiltPath::Built & b) { return b.toJSON(store); },
|
||||
},
|
||||
raw());
|
||||
}
|
||||
|
||||
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ config_priv_h = configure_file(
|
|||
)
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'built-path.cc',
|
||||
|
|
|
|||
|
|
@ -760,7 +760,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
|||
|
||||
void NixRepl::initEnv()
|
||||
{
|
||||
env = &state->allocEnv(envSize);
|
||||
env = &state->mem.allocEnv(envSize);
|
||||
env->up = &state->baseEnv;
|
||||
displ = 0;
|
||||
staticEnv->vars.clear();
|
||||
|
|
@ -869,14 +869,8 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
|
|||
|
||||
Expr * NixRepl::parseString(std::string s)
|
||||
{
|
||||
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
|
||||
}
|
||||
|
||||
void NixRepl::evalString(std::string s, Value & v)
|
||||
{
|
||||
Expr * e;
|
||||
try {
|
||||
e = parseString(s);
|
||||
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
|
||||
} catch (ParseError & e) {
|
||||
if (e.msg().find("unexpected end of file") != std::string::npos)
|
||||
// For parse errors on incomplete input, we continue waiting for the next line of
|
||||
|
|
@ -885,6 +879,11 @@ void NixRepl::evalString(std::string s, Value & v)
|
|||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void NixRepl::evalString(std::string s, Value & v)
|
||||
{
|
||||
Expr * e = parseString(s);
|
||||
e->eval(*state, *env, v);
|
||||
state->forceValue(v, v.determinePos(noPos));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ deps_public_maybe_subproject = [
|
|||
subdir('nix-meson-build-support/subprojects')
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'nix_api_expr.cc',
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Sto
|
|||
|
||||
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
|
||||
{
|
||||
delete builder;
|
||||
operator delete(builder, static_cast<std::align_val_t>(alignof(nix_eval_state_builder)));
|
||||
}
|
||||
|
||||
nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder)
|
||||
|
|
@ -203,7 +203,7 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
|
|||
|
||||
void nix_state_free(EvalState * state)
|
||||
{
|
||||
delete state;
|
||||
operator delete(state, static_cast<std::align_val_t>(alignof(EvalState)));
|
||||
}
|
||||
|
||||
#if NIX_USE_BOEHMGC
|
||||
|
|
|
|||
|
|
@ -326,6 +326,10 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
|||
try {
|
||||
auto & v = check_value_in(value);
|
||||
assert(v.type() == nix::nList);
|
||||
if (ix >= v.listSize()) {
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
|
||||
return nullptr;
|
||||
}
|
||||
auto * p = v.listView()[ix];
|
||||
nix_gc_incref(nullptr, p);
|
||||
if (p != nullptr)
|
||||
|
|
@ -335,6 +339,26 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
|||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
nix_value *
|
||||
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_in(value);
|
||||
assert(v.type() == nix::nList);
|
||||
if (ix >= v.listSize()) {
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
|
||||
return nullptr;
|
||||
}
|
||||
auto * p = v.listView()[ix];
|
||||
nix_gc_incref(nullptr, p);
|
||||
// Note: intentionally NOT calling forceValue() to keep the element lazy
|
||||
return as_nix_value_ptr(p);
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||
{
|
||||
if (context)
|
||||
|
|
@ -355,6 +379,27 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
|
|||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
nix_value *
|
||||
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_in(value);
|
||||
assert(v.type() == nix::nAttrs);
|
||||
nix::Symbol s = state->state.symbols.create(name);
|
||||
auto attr = v.attrs()->get(s);
|
||||
if (attr) {
|
||||
nix_gc_incref(nullptr, attr->value);
|
||||
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
|
||||
return as_nix_value_ptr(attr->value);
|
||||
}
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
|
||||
return nullptr;
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||
{
|
||||
if (context)
|
||||
|
|
@ -371,13 +416,28 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
|
|||
NIXC_CATCH_ERRS_RES(false);
|
||||
}
|
||||
|
||||
nix_value * nix_get_attr_byidx(
|
||||
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
||||
static void collapse_attrset_layer_chain_if_needed(nix::Value & v, EvalState * state)
|
||||
{
|
||||
auto & attrs = *v.attrs();
|
||||
if (attrs.isLayered()) {
|
||||
auto bindings = state->state.buildBindings(attrs.size());
|
||||
std::ranges::copy(attrs, std::back_inserter(bindings));
|
||||
v.mkAttrs(bindings);
|
||||
}
|
||||
}
|
||||
|
||||
nix_value *
|
||||
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_in(value);
|
||||
collapse_attrset_layer_chain_if_needed(v, state);
|
||||
if (i >= v.attrs()->size()) {
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds");
|
||||
return nullptr;
|
||||
}
|
||||
const nix::Attr & a = (*v.attrs())[i];
|
||||
*name = state->state.symbols[a.name].c_str();
|
||||
nix_gc_incref(nullptr, a.value);
|
||||
|
|
@ -387,13 +447,38 @@ nix_value * nix_get_attr_byidx(
|
|||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
const char *
|
||||
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i)
|
||||
nix_value * nix_get_attr_byidx_lazy(
|
||||
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_in(value);
|
||||
collapse_attrset_layer_chain_if_needed(v, state);
|
||||
if (i >= v.attrs()->size()) {
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
|
||||
return nullptr;
|
||||
}
|
||||
const nix::Attr & a = (*v.attrs())[i];
|
||||
*name = state->state.symbols[a.name].c_str();
|
||||
nix_gc_incref(nullptr, a.value);
|
||||
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
|
||||
return as_nix_value_ptr(a.value);
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_in(value);
|
||||
collapse_attrset_layer_chain_if_needed(v, state);
|
||||
if (i >= v.attrs()->size()) {
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
|
||||
return nullptr;
|
||||
}
|
||||
const nix::Attr & a = (*v.attrs())[i];
|
||||
return state->state.symbols[a.name].c_str();
|
||||
}
|
||||
|
|
@ -594,7 +679,7 @@ nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * b
|
|||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto & v = check_value_not_null(value);
|
||||
nix::Symbol s = bb->builder.state.get().symbols.create(name);
|
||||
nix::Symbol s = bb->builder.symbols.get().create(name);
|
||||
bb->builder.insert(s, &v);
|
||||
}
|
||||
NIXC_CATCH_ERRS
|
||||
|
|
|
|||
|
|
@ -265,10 +265,25 @@ ExternalValue * nix_get_external(nix_c_context * context, nix_value * value);
|
|||
*/
|
||||
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
|
||||
|
||||
/** @brief Get an attr by name
|
||||
/** @brief Get the ix'th element of a list without forcing evaluation of the element
|
||||
*
|
||||
* Returns the list element without forcing its evaluation, allowing access to lazy values.
|
||||
* The list value itself must already be evaluated.
|
||||
*
|
||||
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect (must be an evaluated list)
|
||||
* @param[in] state nix evaluator state
|
||||
* @param[in] ix list element to get
|
||||
* @return value, NULL in case of errors
|
||||
*/
|
||||
nix_value *
|
||||
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
|
||||
|
||||
/** @brief Get an attr by name
|
||||
*
|
||||
* Use nix_gc_decref when you're done with the pointer
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect
|
||||
* @param[in] state nix evaluator state
|
||||
* @param[in] name attribute name
|
||||
|
|
@ -276,6 +291,21 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
|||
*/
|
||||
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||
|
||||
/** @brief Get an attribute value by attribute name, without forcing evaluation of the attribute's value
|
||||
*
|
||||
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
|
||||
* The attribute set value itself must already be evaluated.
|
||||
*
|
||||
* Use nix_gc_decref when you're done with the pointer
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
|
||||
* @param[in] state nix evaluator state
|
||||
* @param[in] name attribute name
|
||||
* @return value, NULL in case of errors
|
||||
*/
|
||||
nix_value *
|
||||
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||
|
||||
/** @brief Check if an attribute name exists on a value
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect
|
||||
|
|
@ -285,11 +315,21 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
|
|||
*/
|
||||
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||
|
||||
/** @brief Get an attribute by index in the sorted bindings
|
||||
/** @brief Get an attribute by index
|
||||
*
|
||||
* Also gives you the name.
|
||||
*
|
||||
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
|
||||
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||
* ensure deterministic behavior in your application.
|
||||
*
|
||||
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||
* applying this same ordering for consistency.
|
||||
*
|
||||
* Use nix_gc_decref when you're done with the pointer
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect
|
||||
* @param[in] state nix evaluator state
|
||||
|
|
@ -297,12 +337,50 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
|
|||
* @param[out] name will store a pointer to the attribute name
|
||||
* @return value, NULL in case of errors
|
||||
*/
|
||||
nix_value * nix_get_attr_byidx(
|
||||
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
||||
nix_value *
|
||||
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
||||
|
||||
/** @brief Get an attribute name by index in the sorted bindings
|
||||
/** @brief Get an attribute by index, without forcing evaluation of the attribute's value
|
||||
*
|
||||
* Useful when you want the name but want to avoid evaluation.
|
||||
* Also gives you the name.
|
||||
*
|
||||
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
|
||||
* The attribute set value itself must already have been evaluated.
|
||||
*
|
||||
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||
* ensure deterministic behavior in your application.
|
||||
*
|
||||
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||
* applying this same ordering for consistency.
|
||||
*
|
||||
* Use nix_gc_decref when you're done with the pointer
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
|
||||
* @param[in] state nix evaluator state
|
||||
* @param[in] i attribute index
|
||||
* @param[out] name will store a pointer to the attribute name
|
||||
* @return value, NULL in case of errors
|
||||
*/
|
||||
nix_value * nix_get_attr_byidx_lazy(
|
||||
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
||||
|
||||
/** @brief Get an attribute name by index
|
||||
*
|
||||
* Returns the attribute name without forcing evaluation of the attribute's value.
|
||||
*
|
||||
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||
* ensure deterministic behavior in your application.
|
||||
*
|
||||
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||
* applying this same ordering for consistency.
|
||||
*
|
||||
* Owned by the nix EvalState
|
||||
* @param[out] context Optional, stores error information
|
||||
|
|
@ -311,8 +389,7 @@ nix_value * nix_get_attr_byidx(
|
|||
* @param[in] i attribute index
|
||||
* @return name, NULL in case of errors
|
||||
*/
|
||||
const char *
|
||||
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);
|
||||
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i);
|
||||
|
||||
/**@}*/
|
||||
/** @name Initializers
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ rapidcheck = dependency('rapidcheck')
|
|||
deps_public += rapidcheck
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'tests/value/context.cc',
|
||||
|
|
|
|||
|
|
@ -1,40 +1,15 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/util/logging.hh"
|
||||
|
||||
#include "nix/store/tests/test-main.hh"
|
||||
#include "nix/util/config-global.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
|
||||
auto res = testMainForBuidingPre(argc, argv);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
// For pipe operator tests in trivial.cc
|
||||
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ config_priv_h = configure_file(
|
|||
)
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'derived-path.cc',
|
||||
|
|
@ -82,7 +83,7 @@ this_exe = executable(
|
|||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
env : asan_test_options_env + {
|
||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
|
|
|
|||
|
|
@ -423,6 +423,55 @@ TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
|
|||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("badReturnThunk"));
|
||||
}
|
||||
|
||||
static void primop_with_nix_err_key(
|
||||
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
nix_set_err_msg(context, NIX_ERR_KEY, "Test error from primop");
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop_nix_err_key_conversion)
|
||||
{
|
||||
// Test that NIX_ERR_KEY from a custom primop gets converted to a generic EvalError
|
||||
//
|
||||
// RATIONALE: NIX_ERR_KEY must not be propagated from custom primops because it would
|
||||
// create semantic confusion. NIX_ERR_KEY indicates missing keys/indices in C API functions
|
||||
// (like nix_get_attr_byname, nix_get_list_byidx). If custom primops could return NIX_ERR_KEY,
|
||||
// an evaluation error would be indistinguishable from an actual missing attribute.
|
||||
//
|
||||
// For example, if nix_get_attr_byname returned NIX_ERR_KEY when the attribute is present
|
||||
// but the value evaluation fails, callers expecting NIX_ERR_KEY to mean "missing attribute"
|
||||
// would incorrectly handle evaluation failures as missing attributes. In places where
|
||||
// missing attributes are tolerated (like optional attributes), this would cause the
|
||||
// program to continue after swallowing the error, leading to silent failures.
|
||||
PrimOp * primop = nix_alloc_primop(
|
||||
ctx, primop_with_nix_err_key, 1, "testErrorPrimop", nullptr, "a test primop that sets NIX_ERR_KEY", 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 * arg = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, arg, 42);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_value_call(ctx, state, primopValue, arg, result);
|
||||
|
||||
// Verify that NIX_ERR_KEY gets converted to NIX_ERR_NIX_ERROR (generic evaluation error)
|
||||
ASSERT_EQ(nix_err_code(ctx), NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Error from custom function"));
|
||||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Test error from primop"));
|
||||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("testErrorPrimop"));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, primopValue);
|
||||
nix_gc_decref(ctx, arg);
|
||||
nix_gc_decref(ctx, result);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
||||
{
|
||||
nix_value * n = nix_alloc_value(ctx, state);
|
||||
|
|
@ -437,4 +486,31 @@ TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
|||
assert_ctx_ok();
|
||||
ASSERT_EQ(3, rInt);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_attrset_update)
|
||||
{
|
||||
nix_expr_eval_from_string(ctx, state, "{ a = 0; b = 2; } // { a = 1; b = 3; } // { a = 2; }", ".", value);
|
||||
assert_ctx_ok();
|
||||
|
||||
ASSERT_EQ(nix_get_attrs_size(ctx, value), 2);
|
||||
assert_ctx_ok();
|
||||
std::array<std::pair<std::string_view, nix_value *>, 2> values;
|
||||
for (unsigned int i = 0; i < 2; ++i) {
|
||||
const char * name;
|
||||
values[i].second = nix_get_attr_byidx(ctx, value, state, i, &name);
|
||||
assert_ctx_ok();
|
||||
values[i].first = name;
|
||||
}
|
||||
std::sort(values.begin(), values.end(), [](const auto & lhs, const auto & rhs) { return lhs.first < rhs.first; });
|
||||
|
||||
nix_value * a = values[0].second;
|
||||
ASSERT_EQ("a", values[0].first);
|
||||
ASSERT_EQ(nix_get_int(ctx, a), 2);
|
||||
assert_ctx_ok();
|
||||
nix_value * b = values[1].second;
|
||||
ASSERT_EQ("b", values[1].first);
|
||||
ASSERT_EQ(nix_get_int(ctx, b), 3);
|
||||
assert_ctx_ok();
|
||||
}
|
||||
|
||||
} // namespace nixC
|
||||
|
|
|
|||
|
|
@ -162,6 +162,114 @@ TEST_F(nix_api_expr_test, nix_build_and_init_list)
|
|||
nix_gc_decref(ctx, intValue);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_get_list_byidx_large_indices)
|
||||
{
|
||||
// Create a small list to test extremely large out-of-bounds access
|
||||
ListBuilder * builder = nix_make_list_builder(ctx, state, 2);
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
nix_list_builder_insert(ctx, builder, 0, intValue);
|
||||
nix_list_builder_insert(ctx, builder, 1, intValue);
|
||||
nix_make_list(ctx, builder, value);
|
||||
nix_list_builder_free(builder);
|
||||
|
||||
// Test extremely large indices that would definitely crash without bounds checking
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 1000000));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, intValue);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_get_list_byidx_lazy)
|
||||
{
|
||||
// Create a list with a throwing lazy element, an already-evaluated int, and a lazy function call
|
||||
|
||||
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
_: throw "This should not be evaluated by the lazy accessor"
|
||||
)",
|
||||
"<test>",
|
||||
throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 2. Already evaluated int (not lazy)
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 3. Lazy function application that would compute increment 5 = 6
|
||||
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||
nix_value * argFive = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, argFive, 5);
|
||||
|
||||
// Create a lazy application: (x: x + 1) 5
|
||||
nix_init_apply(ctx, lazyApply, incrementFn, argFive);
|
||||
assert_ctx_ok();
|
||||
|
||||
ListBuilder * builder = nix_make_list_builder(ctx, state, 3);
|
||||
nix_list_builder_insert(ctx, builder, 0, throwingValue);
|
||||
nix_list_builder_insert(ctx, builder, 1, intValue);
|
||||
nix_list_builder_insert(ctx, builder, 2, lazyApply);
|
||||
nix_make_list(ctx, builder, value);
|
||||
nix_list_builder_free(builder);
|
||||
|
||||
// Test 1: Lazy accessor should return the throwing element without forcing evaluation
|
||||
nix_value * lazyThrowingElement = nix_get_list_byidx_lazy(ctx, value, state, 0);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, lazyThrowingElement);
|
||||
|
||||
// Verify the element is still lazy by checking that forcing it throws
|
||||
nix_value_force(ctx, state, lazyThrowingElement);
|
||||
assert_ctx_err();
|
||||
ASSERT_THAT(
|
||||
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||
|
||||
// Test 2: Lazy accessor should return the already-evaluated int
|
||||
nix_value * intElement = nix_get_list_byidx_lazy(ctx, value, state, 1);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, intElement);
|
||||
ASSERT_EQ(42, nix_get_int(ctx, intElement));
|
||||
|
||||
// Test 3: Lazy accessor should return the lazy function application without forcing
|
||||
nix_value * lazyFunctionElement = nix_get_list_byidx_lazy(ctx, value, state, 2);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, lazyFunctionElement);
|
||||
|
||||
// Force the lazy function application - should compute 5 + 1 = 6
|
||||
nix_value_force(ctx, state, lazyFunctionElement);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(6, nix_get_int(ctx, lazyFunctionElement));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, throwingFn);
|
||||
nix_gc_decref(ctx, throwingValue);
|
||||
nix_gc_decref(ctx, intValue);
|
||||
nix_gc_decref(ctx, lazyApply);
|
||||
nix_gc_decref(ctx, incrementFn);
|
||||
nix_gc_decref(ctx, argFive);
|
||||
nix_gc_decref(ctx, lazyThrowingElement);
|
||||
nix_gc_decref(ctx, intElement);
|
||||
nix_gc_decref(ctx, lazyFunctionElement);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
|
||||
{
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
|
||||
|
|
@ -244,6 +352,225 @@ TEST_F(nix_api_expr_test, nix_build_and_init_attr)
|
|||
free(out_name);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_get_attr_byidx_large_indices)
|
||||
{
|
||||
// Create a small attribute set to test extremely large out-of-bounds access
|
||||
const char ** out_name = (const char **) malloc(sizeof(char *));
|
||||
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 2);
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
nix_bindings_builder_insert(ctx, builder, "test", intValue);
|
||||
nix_make_attrs(ctx, value, builder);
|
||||
nix_bindings_builder_free(builder);
|
||||
|
||||
// Test extremely large indices that would definitely crash without bounds checking
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, 1000000, out_name));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2, out_name));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2 + 1000000, out_name));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
|
||||
// Test nix_get_attr_name_byidx with large indices too
|
||||
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, 1000000));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, intValue);
|
||||
free(out_name);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_get_attr_byname_lazy)
|
||||
{
|
||||
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
|
||||
|
||||
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
_: throw "This should not be evaluated by the lazy accessor"
|
||||
)",
|
||||
"<test>",
|
||||
throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 2. Already evaluated int (not lazy)
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 3. Lazy function application that would compute increment 7 = 8
|
||||
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||
nix_value * argSeven = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, argSeven, 7);
|
||||
|
||||
// Create a lazy application: (x: x + 1) 7
|
||||
nix_init_apply(ctx, lazyApply, incrementFn, argSeven);
|
||||
assert_ctx_ok();
|
||||
|
||||
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
|
||||
nix_bindings_builder_insert(ctx, builder, "throwing", throwingValue);
|
||||
nix_bindings_builder_insert(ctx, builder, "normal", intValue);
|
||||
nix_bindings_builder_insert(ctx, builder, "lazy", lazyApply);
|
||||
nix_make_attrs(ctx, value, builder);
|
||||
nix_bindings_builder_free(builder);
|
||||
|
||||
// Test 1: Lazy accessor should return the throwing attribute without forcing evaluation
|
||||
nix_value * lazyThrowingAttr = nix_get_attr_byname_lazy(ctx, value, state, "throwing");
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, lazyThrowingAttr);
|
||||
|
||||
// Verify the attribute is still lazy by checking that forcing it throws
|
||||
nix_value_force(ctx, state, lazyThrowingAttr);
|
||||
assert_ctx_err();
|
||||
ASSERT_THAT(
|
||||
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||
|
||||
// Test 2: Lazy accessor should return the already-evaluated int
|
||||
nix_value * intAttr = nix_get_attr_byname_lazy(ctx, value, state, "normal");
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, intAttr);
|
||||
ASSERT_EQ(42, nix_get_int(ctx, intAttr));
|
||||
|
||||
// Test 3: Lazy accessor should return the lazy function application without forcing
|
||||
nix_value * lazyFunctionAttr = nix_get_attr_byname_lazy(ctx, value, state, "lazy");
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, lazyFunctionAttr);
|
||||
|
||||
// Force the lazy function application - should compute 7 + 1 = 8
|
||||
nix_value_force(ctx, state, lazyFunctionAttr);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(8, nix_get_int(ctx, lazyFunctionAttr));
|
||||
|
||||
// Test 4: Missing attribute should return NULL with NIX_ERR_KEY
|
||||
nix_value * missingAttr = nix_get_attr_byname_lazy(ctx, value, state, "nonexistent");
|
||||
ASSERT_EQ(nullptr, missingAttr);
|
||||
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, throwingFn);
|
||||
nix_gc_decref(ctx, throwingValue);
|
||||
nix_gc_decref(ctx, intValue);
|
||||
nix_gc_decref(ctx, lazyApply);
|
||||
nix_gc_decref(ctx, incrementFn);
|
||||
nix_gc_decref(ctx, argSeven);
|
||||
nix_gc_decref(ctx, lazyThrowingAttr);
|
||||
nix_gc_decref(ctx, intAttr);
|
||||
nix_gc_decref(ctx, lazyFunctionAttr);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_get_attr_byidx_lazy)
|
||||
{
|
||||
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
|
||||
|
||||
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
_: throw "This should not be evaluated by the lazy accessor"
|
||||
)",
|
||||
"<test>",
|
||||
throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 2. Already evaluated int (not lazy)
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 99);
|
||||
assert_ctx_ok();
|
||||
|
||||
// 3. Lazy function application that would compute increment 10 = 11
|
||||
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||
nix_value * argTen = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, argTen, 10);
|
||||
|
||||
// Create a lazy application: (x: x + 1) 10
|
||||
nix_init_apply(ctx, lazyApply, incrementFn, argTen);
|
||||
assert_ctx_ok();
|
||||
|
||||
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
|
||||
nix_bindings_builder_insert(ctx, builder, "a_throwing", throwingValue);
|
||||
nix_bindings_builder_insert(ctx, builder, "b_normal", intValue);
|
||||
nix_bindings_builder_insert(ctx, builder, "c_lazy", lazyApply);
|
||||
nix_make_attrs(ctx, value, builder);
|
||||
nix_bindings_builder_free(builder);
|
||||
|
||||
// Proper usage: first get the size and gather all attributes into a map
|
||||
unsigned int attrCount = nix_get_attrs_size(ctx, value);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(3u, attrCount);
|
||||
|
||||
// Gather all attributes into a map (proper contract usage)
|
||||
std::map<std::string, nix_value *> attrMap;
|
||||
const char * name;
|
||||
|
||||
for (unsigned int i = 0; i < attrCount; i++) {
|
||||
nix_value * attr = nix_get_attr_byidx_lazy(ctx, value, state, i, &name);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, attr);
|
||||
attrMap[std::string(name)] = attr;
|
||||
}
|
||||
|
||||
// Now test the gathered attributes
|
||||
ASSERT_EQ(3u, attrMap.size());
|
||||
ASSERT_TRUE(attrMap.count("a_throwing"));
|
||||
ASSERT_TRUE(attrMap.count("b_normal"));
|
||||
ASSERT_TRUE(attrMap.count("c_lazy"));
|
||||
|
||||
// Test 1: Throwing attribute should be lazy
|
||||
nix_value * throwingAttr = attrMap["a_throwing"];
|
||||
nix_value_force(ctx, state, throwingAttr);
|
||||
assert_ctx_err();
|
||||
ASSERT_THAT(
|
||||
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||
|
||||
// Test 2: Normal attribute should be already evaluated
|
||||
nix_value * normalAttr = attrMap["b_normal"];
|
||||
ASSERT_EQ(99, nix_get_int(ctx, normalAttr));
|
||||
|
||||
// Test 3: Lazy function should compute when forced
|
||||
nix_value * lazyAttr = attrMap["c_lazy"];
|
||||
nix_value_force(ctx, state, lazyAttr);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(11, nix_get_int(ctx, lazyAttr));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, throwingFn);
|
||||
nix_gc_decref(ctx, throwingValue);
|
||||
nix_gc_decref(ctx, intValue);
|
||||
nix_gc_decref(ctx, lazyApply);
|
||||
nix_gc_decref(ctx, incrementFn);
|
||||
nix_gc_decref(ctx, argTen);
|
||||
for (auto & pair : attrMap) {
|
||||
nix_gc_decref(ctx, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_init)
|
||||
{
|
||||
// Setup
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ mkMesonExecutable (finalAttrs: {
|
|||
mkdir -p "$HOME"
|
||||
''
|
||||
+ ''
|
||||
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
|
|
|
|||
|
|
@ -642,7 +642,7 @@ class ToStringPrimOpTest : public PrimOpTest,
|
|||
|
||||
TEST_P(ToStringPrimOpTest, toString)
|
||||
{
|
||||
const auto [input, output] = GetParam();
|
||||
const auto & [input, output] = GetParam();
|
||||
auto v = eval(input);
|
||||
ASSERT_THAT(v, IsStringEq(output));
|
||||
}
|
||||
|
|
@ -798,7 +798,7 @@ class CompareVersionsPrimOpTest : public PrimOpTest,
|
|||
|
||||
TEST_P(CompareVersionsPrimOpTest, compareVersions)
|
||||
{
|
||||
auto [expression, expectation] = GetParam();
|
||||
const auto & [expression, expectation] = GetParam();
|
||||
auto v = eval(expression);
|
||||
ASSERT_THAT(v, IsIntEq(expectation));
|
||||
}
|
||||
|
|
@ -834,7 +834,7 @@ class ParseDrvNamePrimOpTest
|
|||
|
||||
TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
|
||||
{
|
||||
auto [input, expectedName, expectedVersion] = GetParam();
|
||||
const auto & [input, expectedName, expectedVersion] = GetParam();
|
||||
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
|
||||
auto v = eval(expr);
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
|
|
|
|||
|
|
@ -10,32 +10,32 @@ Bindings Bindings::emptyBindings;
|
|||
/* Allocate a new array of attributes for an attribute set with a specific
|
||||
capacity. The space is implicitly reserved after the Bindings
|
||||
structure. */
|
||||
Bindings * EvalState::allocBindings(size_t capacity)
|
||||
Bindings * EvalMemory::allocBindings(size_t capacity)
|
||||
{
|
||||
if (capacity == 0)
|
||||
return &Bindings::emptyBindings;
|
||||
if (capacity > std::numeric_limits<Bindings::size_t>::max())
|
||||
if (capacity > std::numeric_limits<Bindings::size_type>::max())
|
||||
throw Error("attribute set of size %d is too big", capacity);
|
||||
nrAttrsets++;
|
||||
nrAttrsInAttrsets += capacity;
|
||||
stats.nrAttrsets++;
|
||||
stats.nrAttrsInAttrsets += capacity;
|
||||
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
|
||||
}
|
||||
|
||||
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
|
||||
{
|
||||
auto value = state.get().allocValue();
|
||||
auto value = mem.get().allocValue();
|
||||
bindings->push_back(Attr(name, value, pos));
|
||||
return *value;
|
||||
}
|
||||
|
||||
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
|
||||
{
|
||||
return alloc(state.get().symbols.create(name), pos);
|
||||
return alloc(symbols.get().create(name), pos);
|
||||
}
|
||||
|
||||
void Bindings::sort()
|
||||
{
|
||||
std::sort(attrs, attrs + size_);
|
||||
std::sort(attrs, attrs + numAttrs);
|
||||
}
|
||||
|
||||
Value & Value::mkAttrs(BindingsBuilder & bindings)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "nix/expr/print.hh"
|
||||
#include "nix/fetchers/filtering-source-accessor.hh"
|
||||
#include "nix/util/memory-source-accessor.hh"
|
||||
#include "nix/util/mounted-source-accessor.hh"
|
||||
#include "nix/expr/gc-small-vector.hh"
|
||||
#include "nix/util/url.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
|
|
@ -193,6 +194,15 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
|
|||
|
||||
static constexpr size_t BASE_ENV_SIZE = 128;
|
||||
|
||||
EvalMemory::EvalMemory()
|
||||
#if NIX_USE_BOEHMGC
|
||||
: valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
#endif
|
||||
{
|
||||
assertGCInitialized();
|
||||
}
|
||||
|
||||
EvalState::EvalState(
|
||||
const LookupPath & lookupPathFromArguments,
|
||||
ref<Store> store,
|
||||
|
|
@ -225,22 +235,25 @@ EvalState::EvalState(
|
|||
*/
|
||||
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
|
||||
}))
|
||||
, rootFS(({
|
||||
/* In pure eval mode, we provide a filesystem that only
|
||||
contains the Nix store.
|
||||
, rootFS([&] {
|
||||
auto accessor = [&]() -> decltype(rootFS) {
|
||||
/* In pure eval mode, we provide a filesystem that only
|
||||
contains the Nix store. */
|
||||
if (settings.pureEval)
|
||||
return storeFS;
|
||||
|
||||
If we have a chroot store and pure eval is not enabled,
|
||||
use a union accessor to make the chroot store available
|
||||
at its logical location while still having the
|
||||
underlying directory available. This is necessary for
|
||||
instance if we're evaluating a file from the physical
|
||||
/nix/store while using a chroot store. */
|
||||
auto accessor = getFSSourceAccessor();
|
||||
/* If we have a chroot store and pure eval is not enabled,
|
||||
use a union accessor to make the chroot store available
|
||||
at its logical location while still having the underlying
|
||||
directory available. This is necessary for instance if
|
||||
we're evaluating a file from the physical /nix/store
|
||||
while using a chroot store. */
|
||||
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
|
||||
if (store->storeDir != realStoreDir)
|
||||
return makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
|
||||
|
||||
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
|
||||
if (settings.pureEval || store->storeDir != realStoreDir) {
|
||||
accessor = settings.pureEval ? storeFS : makeUnionSourceAccessor({accessor, storeFS});
|
||||
}
|
||||
return getFSSourceAccessor();
|
||||
}();
|
||||
|
||||
/* Apply access control if needed. */
|
||||
if (settings.restrictEval || settings.pureEval)
|
||||
|
|
@ -251,8 +264,8 @@ EvalState::EvalState(
|
|||
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
|
||||
});
|
||||
|
||||
accessor;
|
||||
}))
|
||||
return accessor;
|
||||
}())
|
||||
, corepkgsFS(make_ref<MemorySourceAccessor>())
|
||||
, internalFS(make_ref<MemorySourceAccessor>())
|
||||
, derivationInternal{corepkgsFS->addFile(
|
||||
|
|
@ -270,12 +283,10 @@ EvalState::EvalState(
|
|||
, fileEvalCache(make_ref<decltype(fileEvalCache)::element_type>())
|
||||
, regexCache(makeRegexCache())
|
||||
#if NIX_USE_BOEHMGC
|
||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &allocEnv(BASE_ENV_SIZE)))
|
||||
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &mem.allocEnv(BASE_ENV_SIZE)))
|
||||
, baseEnv(**baseEnvP)
|
||||
#else
|
||||
, baseEnv(allocEnv(BASE_ENV_SIZE))
|
||||
, baseEnv(mem.allocEnv(BASE_ENV_SIZE))
|
||||
#endif
|
||||
, staticBaseEnv{std::make_shared<StaticEnv>(nullptr, nullptr)}
|
||||
{
|
||||
|
|
@ -284,9 +295,8 @@ EvalState::EvalState(
|
|||
|
||||
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
|
||||
|
||||
assertGCInitialized();
|
||||
|
||||
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
|
||||
static_assert(sizeof(Counter) == 64, "counters must be 64 bytes");
|
||||
|
||||
/* Construct the Nix expression search path. */
|
||||
assert(lookupPath.elements.empty());
|
||||
|
|
@ -333,7 +343,7 @@ EvalState::EvalState(
|
|||
|
||||
EvalState::~EvalState() {}
|
||||
|
||||
void EvalState::allowPath(const Path & path)
|
||||
void EvalState::allowPathLegacy(const Path & path)
|
||||
{
|
||||
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
|
||||
rootFS2->allowPrefix(CanonPath(path));
|
||||
|
|
@ -880,11 +890,10 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
|||
}
|
||||
}
|
||||
|
||||
ListBuilder::ListBuilder(EvalState & state, size_t size)
|
||||
ListBuilder::ListBuilder(size_t size)
|
||||
: size(size)
|
||||
, elems(size <= 2 ? inlineElems : (Value **) allocBytes(size * sizeof(Value *)))
|
||||
{
|
||||
state.nrListElems += size;
|
||||
}
|
||||
|
||||
Value * EvalState::getBool(bool b)
|
||||
|
|
@ -892,7 +901,7 @@ Value * EvalState::getBool(bool b)
|
|||
return b ? &Value::vTrue : &Value::vFalse;
|
||||
}
|
||||
|
||||
unsigned long nrThunks = 0;
|
||||
static Counter nrThunks;
|
||||
|
||||
static inline void mkThunk(Value & v, Env & env, Expr * expr)
|
||||
{
|
||||
|
|
@ -983,10 +992,6 @@ void EvalState::mkSingleDerivedPathString(const SingleDerivedPath & p, Value & v
|
|||
});
|
||||
}
|
||||
|
||||
/* Create a thunk for the delayed computation of the given expression
|
||||
in the given environment. But if the expression is a variable,
|
||||
then look it up right away. This significantly reduces the number
|
||||
of thunks allocated. */
|
||||
Value * Expr::maybeThunk(EvalState & state, Env & env)
|
||||
{
|
||||
Value * v = state.allocValue();
|
||||
|
|
@ -1035,9 +1040,10 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
|
|||
* from a thunk, ensuring that every file is parsed/evaluated only
|
||||
* once (via the thunk stored in `EvalState::fileEvalCache`).
|
||||
*/
|
||||
struct ExprParseFile : Expr
|
||||
struct ExprParseFile : Expr, gc
|
||||
{
|
||||
SourcePath & path;
|
||||
// FIXME: make this a reference (see below).
|
||||
SourcePath path;
|
||||
bool mustBeTrivial;
|
||||
|
||||
ExprParseFile(SourcePath & path, bool mustBeTrivial)
|
||||
|
|
@ -1088,14 +1094,18 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
|
|||
}
|
||||
|
||||
Value * vExpr;
|
||||
ExprParseFile expr{*resolvedPath, mustBeTrivial};
|
||||
// FIXME: put ExprParseFile on the stack instead of the heap once
|
||||
// https://github.com/NixOS/nix/pull/13930 is merged. That will ensure
|
||||
// the post-condition that `expr` is unreachable after
|
||||
// `forceValue()` returns.
|
||||
auto expr = new ExprParseFile{*resolvedPath, mustBeTrivial};
|
||||
|
||||
fileEvalCache->try_emplace_and_cvisit(
|
||||
*resolvedPath,
|
||||
nullptr,
|
||||
[&](auto & i) {
|
||||
vExpr = allocValue();
|
||||
vExpr->mkThunk(&baseEnv, &expr);
|
||||
vExpr->mkThunk(&baseEnv, expr);
|
||||
i.second = vExpr;
|
||||
},
|
||||
[&](auto & i) { vExpr = i.second; });
|
||||
|
|
@ -1177,7 +1187,7 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v)
|
|||
|
||||
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
||||
{
|
||||
Env & inheritEnv = state.allocEnv(inheritFromExprs->size());
|
||||
Env & inheritEnv = state.mem.allocEnv(inheritFromExprs->size());
|
||||
inheritEnv.up = &up;
|
||||
|
||||
Displacement displ = 0;
|
||||
|
|
@ -1196,7 +1206,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
|||
if (recursive) {
|
||||
/* Create a new environment that contains the attributes in
|
||||
this `rec'. */
|
||||
Env & env2(state.allocEnv(attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs.size()));
|
||||
env2.up = &env;
|
||||
dynamicEnv = &env2;
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
|
@ -1288,7 +1298,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
|||
{
|
||||
/* Create a new environment that contains the attributes in this
|
||||
`let'. */
|
||||
Env & env2(state.allocEnv(attrs->attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs->attrs.size()));
|
||||
env2.up = &env;
|
||||
|
||||
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
|
@ -1494,7 +1504,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
|
|||
ExprLambda & lambda(*vCur.lambda().fun);
|
||||
|
||||
auto size = (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
||||
Env & env2(allocEnv(size));
|
||||
Env & env2(mem.allocEnv(size));
|
||||
env2.up = vCur.lambda().env;
|
||||
|
||||
Displacement displ = 0;
|
||||
|
|
@ -1783,7 +1793,7 @@ https://nix.dev/manual/nix/stable/language/syntax.html#functions.)",
|
|||
|
||||
void ExprWith::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
Env & env2(state.allocEnv(1));
|
||||
Env & env2(state.mem.allocEnv(1));
|
||||
env2.up = &env;
|
||||
env2.values[0] = attrs->maybeThunk(state, env);
|
||||
|
||||
|
|
@ -1865,51 +1875,113 @@ void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
|
|||
|| state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
|
||||
}
|
||||
|
||||
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||
void ExprOpUpdate::eval(EvalState & state, Value & v, Value & v1, Value & v2)
|
||||
{
|
||||
Value v1, v2;
|
||||
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
|
||||
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
|
||||
|
||||
state.nrOpUpdates++;
|
||||
|
||||
if (v1.attrs()->size() == 0) {
|
||||
const Bindings & bindings1 = *v1.attrs();
|
||||
if (bindings1.empty()) {
|
||||
v = v2;
|
||||
return;
|
||||
}
|
||||
if (v2.attrs()->size() == 0) {
|
||||
|
||||
const Bindings & bindings2 = *v2.attrs();
|
||||
if (bindings2.empty()) {
|
||||
v = v1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(v1.attrs()->size() + v2.attrs()->size());
|
||||
/* Simple heuristic for determining whether attrs2 should be "layered" on top of
|
||||
attrs1 instead of copying to a new Bindings. */
|
||||
bool shouldLayer = [&]() -> bool {
|
||||
if (bindings1.isLayerListFull())
|
||||
return false;
|
||||
|
||||
if (bindings2.size() > state.settings.bindingsUpdateLayerRhsSizeThreshold)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (shouldLayer) {
|
||||
auto attrs = state.buildBindings(bindings2.size());
|
||||
attrs.layerOnTopOf(bindings1);
|
||||
|
||||
std::ranges::copy(bindings2, std::back_inserter(attrs));
|
||||
v.mkAttrs(attrs.alreadySorted());
|
||||
|
||||
state.nrOpUpdateValuesCopied += bindings2.size();
|
||||
return;
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(bindings1.size() + bindings2.size());
|
||||
|
||||
/* Merge the sets, preferring values from the second set. Make
|
||||
sure to keep the resulting vector in sorted order. */
|
||||
auto i = v1.attrs()->begin();
|
||||
auto j = v2.attrs()->begin();
|
||||
auto i = bindings1.begin();
|
||||
auto j = bindings2.begin();
|
||||
|
||||
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
|
||||
while (i != bindings1.end() && j != bindings2.end()) {
|
||||
if (i->name == j->name) {
|
||||
attrs.insert(*j);
|
||||
++i;
|
||||
++j;
|
||||
} else if (i->name < j->name)
|
||||
attrs.insert(*i++);
|
||||
else
|
||||
attrs.insert(*j++);
|
||||
} else if (i->name < j->name) {
|
||||
attrs.insert(*i);
|
||||
++i;
|
||||
} else {
|
||||
attrs.insert(*j);
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
while (i != v1.attrs()->end())
|
||||
attrs.insert(*i++);
|
||||
while (j != v2.attrs()->end())
|
||||
attrs.insert(*j++);
|
||||
while (i != bindings1.end()) {
|
||||
attrs.insert(*i);
|
||||
++i;
|
||||
}
|
||||
|
||||
while (j != bindings2.end()) {
|
||||
attrs.insert(*j);
|
||||
++j;
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs.alreadySorted());
|
||||
|
||||
state.nrOpUpdateValuesCopied += v.attrs()->size();
|
||||
}
|
||||
|
||||
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
UpdateQueue q;
|
||||
evalForUpdate(state, env, q);
|
||||
|
||||
v.mkAttrs(&Bindings::emptyBindings);
|
||||
for (auto & rhs : std::views::reverse(q)) {
|
||||
/* Remember that queue is sorted rightmost attrset first. */
|
||||
eval(state, /*v=*/v, /*v1=*/v, /*v2=*/rhs);
|
||||
}
|
||||
}
|
||||
|
||||
void Expr::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||
{
|
||||
Value v;
|
||||
state.evalAttrs(env, this, v, getPos(), errorCtx);
|
||||
q.push_back(v);
|
||||
}
|
||||
|
||||
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q)
|
||||
{
|
||||
/* Output rightmost attrset first to the merge queue as the one
|
||||
with the most priority. */
|
||||
e2->evalForUpdate(state, env, q, "in the right operand of the update (//) operator");
|
||||
e1->evalForUpdate(state, env, q, "in the left operand of the update (//) operator");
|
||||
}
|
||||
|
||||
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||
{
|
||||
evalForUpdate(state, env, q);
|
||||
}
|
||||
|
||||
void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
Value v1;
|
||||
|
|
@ -2828,11 +2900,11 @@ bool EvalState::fullGC()
|
|||
#endif
|
||||
}
|
||||
|
||||
bool Counter::enabled = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
|
||||
|
||||
void EvalState::maybePrintStats()
|
||||
{
|
||||
bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
|
||||
|
||||
if (showStats) {
|
||||
if (Counter::enabled) {
|
||||
// Make the final heap size more deterministic.
|
||||
#if NIX_USE_BOEHMGC
|
||||
if (!fullGC()) {
|
||||
|
|
@ -2848,10 +2920,12 @@ void EvalState::printStatistics()
|
|||
std::chrono::microseconds cpuTimeDuration = getCpuUserTime();
|
||||
float cpuTime = std::chrono::duration_cast<std::chrono::duration<float>>(cpuTimeDuration).count();
|
||||
|
||||
uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *);
|
||||
uint64_t bLists = nrListElems * sizeof(Value *);
|
||||
uint64_t bValues = nrValues * sizeof(Value);
|
||||
uint64_t bAttrsets = nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr);
|
||||
auto & memstats = mem.getStats();
|
||||
|
||||
uint64_t bEnvs = memstats.nrEnvs * sizeof(Env) + memstats.nrValuesInEnvs * sizeof(Value *);
|
||||
uint64_t bLists = memstats.nrListElems * sizeof(Value *);
|
||||
uint64_t bValues = memstats.nrValues * sizeof(Value);
|
||||
uint64_t bAttrsets = memstats.nrAttrsets * sizeof(Bindings) + memstats.nrAttrsInAttrsets * sizeof(Attr);
|
||||
|
||||
#if NIX_USE_BOEHMGC
|
||||
GC_word heapSize, totalBytes;
|
||||
|
|
@ -2877,18 +2951,18 @@ void EvalState::printStatistics()
|
|||
#endif
|
||||
};
|
||||
topObj["envs"] = {
|
||||
{"number", nrEnvs},
|
||||
{"elements", nrValuesInEnvs},
|
||||
{"number", memstats.nrEnvs.load()},
|
||||
{"elements", memstats.nrValuesInEnvs.load()},
|
||||
{"bytes", bEnvs},
|
||||
};
|
||||
topObj["nrExprs"] = Expr::nrExprs;
|
||||
topObj["nrExprs"] = Expr::nrExprs.load();
|
||||
topObj["list"] = {
|
||||
{"elements", nrListElems},
|
||||
{"elements", memstats.nrListElems.load()},
|
||||
{"bytes", bLists},
|
||||
{"concats", nrListConcats},
|
||||
{"concats", nrListConcats.load()},
|
||||
};
|
||||
topObj["values"] = {
|
||||
{"number", nrValues},
|
||||
{"number", memstats.nrValues.load()},
|
||||
{"bytes", bValues},
|
||||
};
|
||||
topObj["symbols"] = {
|
||||
|
|
@ -2896,9 +2970,9 @@ void EvalState::printStatistics()
|
|||
{"bytes", symbols.totalSize()},
|
||||
};
|
||||
topObj["sets"] = {
|
||||
{"number", nrAttrsets},
|
||||
{"number", memstats.nrAttrsets.load()},
|
||||
{"bytes", bAttrsets},
|
||||
{"elements", nrAttrsInAttrsets},
|
||||
{"elements", memstats.nrAttrsInAttrsets.load()},
|
||||
};
|
||||
topObj["sizes"] = {
|
||||
{"Env", sizeof(Env)},
|
||||
|
|
@ -2906,13 +2980,13 @@ void EvalState::printStatistics()
|
|||
{"Bindings", sizeof(Bindings)},
|
||||
{"Attr", sizeof(Attr)},
|
||||
};
|
||||
topObj["nrOpUpdates"] = nrOpUpdates;
|
||||
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
|
||||
topObj["nrThunks"] = nrThunks;
|
||||
topObj["nrAvoided"] = nrAvoided;
|
||||
topObj["nrLookups"] = nrLookups;
|
||||
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
|
||||
topObj["nrFunctionCalls"] = nrFunctionCalls;
|
||||
topObj["nrOpUpdates"] = nrOpUpdates.load();
|
||||
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied.load();
|
||||
topObj["nrThunks"] = nrThunks.load();
|
||||
topObj["nrAvoided"] = nrAvoided.load();
|
||||
topObj["nrLookups"] = nrLookups.load();
|
||||
topObj["nrPrimOpCalls"] = nrPrimOpCalls.load();
|
||||
topObj["nrFunctionCalls"] = nrFunctionCalls.load();
|
||||
#if NIX_USE_BOEHMGC
|
||||
topObj["gc"] = {
|
||||
{"heapSize", heapSize},
|
||||
|
|
@ -3113,7 +3187,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
|||
|
||||
/* Allow access to paths in the search path. */
|
||||
if (initAccessControl) {
|
||||
allowPath(path.path.abs());
|
||||
allowPathLegacy(path.path.abs());
|
||||
if (store->isInStore(path.path.abs())) {
|
||||
try {
|
||||
allowClosure(store->toStorePath(path.path.abs()).first);
|
||||
|
|
@ -3143,7 +3217,8 @@ Expr * EvalState::parse(
|
|||
docComments = &it->second;
|
||||
}
|
||||
|
||||
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, *docComments, rootFS);
|
||||
auto result = parseExprFromBuf(
|
||||
text, length, origin, basePath, mem.exprs.alloc, symbols, settings, positions, *docComments, rootFS);
|
||||
|
||||
result->bindVars(*this, staticEnv);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
#include "nix/expr/nixexpr.hh"
|
||||
#include "nix/expr/symbol-table.hh"
|
||||
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <concepts>
|
||||
#include <ranges>
|
||||
#include <optional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
class EvalState;
|
||||
class EvalMemory;
|
||||
struct Value;
|
||||
|
||||
/**
|
||||
|
|
@ -48,11 +51,18 @@ static_assert(
|
|||
* by its size and its capacity, the capacity being the number of Attr
|
||||
* elements allocated after this structure, while the size corresponds to
|
||||
* the number of elements already inserted in this structure.
|
||||
*
|
||||
* Bindings can be efficiently `//`-composed into an intrusive linked list of "layers"
|
||||
* that saves on copies and allocations. Each lookup (@see Bindings::get) traverses
|
||||
* this linked list until a matching attribute is found (thus overlays earlier in
|
||||
* the list take precedence). For iteration over the whole Bindings, an on-the-fly
|
||||
* k-way merge is performed by Bindings::iterator class.
|
||||
*/
|
||||
class Bindings
|
||||
{
|
||||
public:
|
||||
typedef uint32_t size_t;
|
||||
using size_type = uint32_t;
|
||||
|
||||
PosIdx pos;
|
||||
|
||||
/**
|
||||
|
|
@ -62,7 +72,32 @@ public:
|
|||
static Bindings emptyBindings;
|
||||
|
||||
private:
|
||||
size_t size_ = 0;
|
||||
/**
|
||||
* Number of attributes in the attrs FAM (Flexible Array Member).
|
||||
*/
|
||||
size_type numAttrs = 0;
|
||||
|
||||
/**
|
||||
* Number of attributes with unique names in the layer chain.
|
||||
*
|
||||
* This is the *real* user-facing size of bindings, whereas @ref numAttrs is
|
||||
* an implementation detail of the data structure.
|
||||
*/
|
||||
size_type numAttrsInChain = 0;
|
||||
|
||||
/**
|
||||
* Length of the layers list.
|
||||
*/
|
||||
uint32_t numLayers = 1;
|
||||
|
||||
/**
|
||||
* Bindings that this attrset is "layered" on top of.
|
||||
*/
|
||||
const Bindings * baseLayer = nullptr;
|
||||
|
||||
/**
|
||||
* Flexible array member of attributes.
|
||||
*/
|
||||
Attr attrs[0];
|
||||
|
||||
Bindings() = default;
|
||||
|
|
@ -71,15 +106,22 @@ private:
|
|||
Bindings & operator=(const Bindings &) = delete;
|
||||
Bindings & operator=(Bindings &&) = delete;
|
||||
|
||||
friend class BindingsBuilder;
|
||||
|
||||
/**
|
||||
* Maximum length of the Bindings layer chains.
|
||||
*/
|
||||
static constexpr unsigned maxLayers = 8;
|
||||
|
||||
public:
|
||||
size_t size() const
|
||||
size_type size() const
|
||||
{
|
||||
return size_;
|
||||
return numAttrsInChain;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return !size_;
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
class iterator
|
||||
|
|
@ -94,77 +136,276 @@ public:
|
|||
friend class Bindings;
|
||||
|
||||
private:
|
||||
pointer ptr = nullptr;
|
||||
|
||||
explicit iterator(pointer ptr)
|
||||
: ptr(ptr)
|
||||
struct BindingsCursor
|
||||
{
|
||||
/**
|
||||
* Attr that the cursor currently points to.
|
||||
*/
|
||||
pointer current;
|
||||
|
||||
/**
|
||||
* One past the end pointer to the contiguous buffer of Attrs.
|
||||
*/
|
||||
pointer end;
|
||||
|
||||
/**
|
||||
* Priority of the value. Lesser values have more priority (i.e. they override
|
||||
* attributes that appear later in the linked list of Bindings).
|
||||
*/
|
||||
uint32_t priority;
|
||||
|
||||
pointer operator->() const noexcept
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
reference get() const noexcept
|
||||
{
|
||||
return *current;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return current == end;
|
||||
}
|
||||
|
||||
void increment() noexcept
|
||||
{
|
||||
++current;
|
||||
}
|
||||
|
||||
void consume(Symbol name) noexcept
|
||||
{
|
||||
while (!empty() && current->name <= name)
|
||||
++current;
|
||||
}
|
||||
|
||||
GENERATE_CMP(BindingsCursor, me->current->name, me->priority)
|
||||
};
|
||||
|
||||
using QueueStorageType = boost::container::static_vector<BindingsCursor, maxLayers>;
|
||||
|
||||
/**
|
||||
* Comparator implementing the override priority / name ordering
|
||||
* for BindingsCursor.
|
||||
*/
|
||||
static constexpr auto comp = std::greater<BindingsCursor>();
|
||||
|
||||
/**
|
||||
* A priority queue used to implement an on-the-fly k-way merge.
|
||||
*/
|
||||
QueueStorageType cursorHeap;
|
||||
|
||||
/**
|
||||
* The attribute the iterator currently points to.
|
||||
*/
|
||||
pointer current = nullptr;
|
||||
|
||||
/**
|
||||
* Whether iterating over a single attribute and not a merge chain.
|
||||
*/
|
||||
bool doMerge = true;
|
||||
|
||||
void push(BindingsCursor cursor) noexcept
|
||||
{
|
||||
cursorHeap.push_back(cursor);
|
||||
std::ranges::make_heap(cursorHeap, comp);
|
||||
}
|
||||
|
||||
[[nodiscard]] BindingsCursor pop() noexcept
|
||||
{
|
||||
std::ranges::pop_heap(cursorHeap, comp);
|
||||
auto cursor = cursorHeap.back();
|
||||
cursorHeap.pop_back();
|
||||
return cursor;
|
||||
}
|
||||
|
||||
iterator & finished() noexcept
|
||||
{
|
||||
current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void next(BindingsCursor cursor) noexcept
|
||||
{
|
||||
current = &cursor.get();
|
||||
cursor.increment();
|
||||
|
||||
if (!cursor.empty())
|
||||
push(cursor);
|
||||
}
|
||||
|
||||
std::optional<BindingsCursor> consumeAllUntilCurrentName() noexcept
|
||||
{
|
||||
auto cursor = pop();
|
||||
Symbol lastHandledName = current->name;
|
||||
|
||||
while (cursor->name <= lastHandledName) {
|
||||
cursor.consume(lastHandledName);
|
||||
if (!cursor.empty())
|
||||
push(cursor);
|
||||
|
||||
if (cursorHeap.empty())
|
||||
return std::nullopt;
|
||||
|
||||
cursor = pop();
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
explicit iterator(const Bindings & attrs) noexcept
|
||||
: doMerge(attrs.baseLayer)
|
||||
{
|
||||
auto pushBindings = [this, priority = unsigned{0}](const Bindings & layer) mutable {
|
||||
auto first = layer.attrs;
|
||||
push(
|
||||
BindingsCursor{
|
||||
.current = first,
|
||||
.end = first + layer.numAttrs,
|
||||
.priority = priority++,
|
||||
});
|
||||
};
|
||||
|
||||
if (!doMerge) {
|
||||
if (attrs.empty())
|
||||
return;
|
||||
|
||||
current = attrs.attrs;
|
||||
pushBindings(attrs);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const Bindings * layer = &attrs;
|
||||
while (layer) {
|
||||
if (layer->numAttrs != 0)
|
||||
pushBindings(*layer);
|
||||
layer = layer->baseLayer;
|
||||
}
|
||||
|
||||
if (cursorHeap.empty())
|
||||
return;
|
||||
|
||||
next(pop());
|
||||
}
|
||||
|
||||
public:
|
||||
iterator() = default;
|
||||
|
||||
reference operator*() const
|
||||
reference operator*() const noexcept
|
||||
{
|
||||
return *ptr;
|
||||
return *current;
|
||||
}
|
||||
|
||||
const value_type * operator->() const
|
||||
pointer operator->() const noexcept
|
||||
{
|
||||
return ptr;
|
||||
return current;
|
||||
}
|
||||
|
||||
iterator & operator++()
|
||||
iterator & operator++() noexcept
|
||||
{
|
||||
++ptr;
|
||||
if (!doMerge) {
|
||||
++current;
|
||||
if (current == cursorHeap.front().end)
|
||||
return finished();
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (cursorHeap.empty())
|
||||
return finished();
|
||||
|
||||
auto cursor = consumeAllUntilCurrentName();
|
||||
if (!cursor)
|
||||
return finished();
|
||||
|
||||
next(*cursor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int)
|
||||
iterator operator++(int) noexcept
|
||||
{
|
||||
pointer tmp = ptr;
|
||||
iterator tmp = *this;
|
||||
++*this;
|
||||
return iterator(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator & rhs) const = default;
|
||||
bool operator==(const iterator & rhs) const noexcept
|
||||
{
|
||||
return current == rhs.current;
|
||||
}
|
||||
};
|
||||
|
||||
using const_iterator = iterator;
|
||||
|
||||
void push_back(const Attr & attr)
|
||||
{
|
||||
attrs[size_++] = attr;
|
||||
attrs[numAttrs++] = attr;
|
||||
numAttrsInChain = numAttrs;
|
||||
}
|
||||
|
||||
const Attr * get(Symbol name) const
|
||||
/**
|
||||
* Get attribute by name or nullptr if no such attribute exists.
|
||||
*/
|
||||
const Attr * get(Symbol name) const noexcept
|
||||
{
|
||||
Attr key(name, 0);
|
||||
auto first = attrs;
|
||||
auto last = attrs + size_;
|
||||
const Attr * i = std::lower_bound(first, last, key);
|
||||
if (i != last && i->name == name)
|
||||
return i;
|
||||
auto getInChunk = [key = Attr{name, nullptr}](const Bindings & chunk) -> const Attr * {
|
||||
auto first = chunk.attrs;
|
||||
auto last = first + chunk.numAttrs;
|
||||
const Attr * i = std::lower_bound(first, last, key);
|
||||
if (i != last && i->name == key.name)
|
||||
return i;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const Bindings * currentChunk = this;
|
||||
while (currentChunk) {
|
||||
const Attr * maybeAttr = getInChunk(*currentChunk);
|
||||
if (maybeAttr)
|
||||
return maybeAttr;
|
||||
currentChunk = currentChunk->baseLayer;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the layer chain is full.
|
||||
*/
|
||||
bool isLayerListFull() const noexcept
|
||||
{
|
||||
return numLayers == Bindings::maxLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the length of the linked list of layers is greater than 1.
|
||||
*/
|
||||
bool isLayered() const noexcept
|
||||
{
|
||||
return numLayers > 1;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_iterator(attrs);
|
||||
return const_iterator(*this);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(attrs + size_);
|
||||
return const_iterator();
|
||||
}
|
||||
|
||||
Attr & operator[](size_t pos)
|
||||
Attr & operator[](size_type pos)
|
||||
{
|
||||
if (isLayered()) [[unlikely]]
|
||||
unreachable();
|
||||
return attrs[pos];
|
||||
}
|
||||
|
||||
const Attr & operator[](size_t pos) const
|
||||
const Attr & operator[](size_type pos) const
|
||||
{
|
||||
if (isLayered()) [[unlikely]]
|
||||
unreachable();
|
||||
return attrs[pos];
|
||||
}
|
||||
|
||||
|
|
@ -176,17 +417,16 @@ public:
|
|||
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
|
||||
{
|
||||
std::vector<const Attr *> res;
|
||||
res.reserve(size_);
|
||||
for (size_t n = 0; n < size_; n++)
|
||||
res.emplace_back(&attrs[n]);
|
||||
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
|
||||
res.reserve(size());
|
||||
std::ranges::transform(*this, std::back_inserter(res), [](const Attr & a) { return &a; });
|
||||
std::ranges::sort(res, [&](const Attr * a, const Attr * b) {
|
||||
std::string_view sa = symbols[a->name], sb = symbols[b->name];
|
||||
return sa < sb;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
friend class EvalState;
|
||||
friend class EvalMemory;
|
||||
};
|
||||
|
||||
static_assert(std::forward_iterator<Bindings::iterator>);
|
||||
|
|
@ -202,23 +442,38 @@ class BindingsBuilder final
|
|||
public:
|
||||
// needed by std::back_inserter
|
||||
using value_type = Attr;
|
||||
using size_type = Bindings::size_t;
|
||||
using size_type = Bindings::size_type;
|
||||
|
||||
private:
|
||||
Bindings * bindings;
|
||||
Bindings::size_t capacity_;
|
||||
Bindings::size_type capacity_;
|
||||
|
||||
friend class EvalState;
|
||||
friend class EvalMemory;
|
||||
|
||||
BindingsBuilder(EvalState & state, Bindings * bindings, size_type capacity)
|
||||
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, size_type capacity)
|
||||
: bindings(bindings)
|
||||
, capacity_(capacity)
|
||||
, state(state)
|
||||
, mem(mem)
|
||||
, symbols(symbols)
|
||||
{
|
||||
}
|
||||
|
||||
bool hasBaseLayer() const noexcept
|
||||
{
|
||||
return bindings->baseLayer;
|
||||
}
|
||||
|
||||
void finishSizeIfNecessary()
|
||||
{
|
||||
if (hasBaseLayer())
|
||||
/* NOTE: Do not use std::ranges::distance, since Bindings is a sized
|
||||
range, but we are calculating this size here. */
|
||||
bindings->numAttrsInChain = std::distance(bindings->begin(), bindings->end());
|
||||
}
|
||||
|
||||
public:
|
||||
std::reference_wrapper<EvalState> state;
|
||||
std::reference_wrapper<EvalMemory> mem;
|
||||
std::reference_wrapper<SymbolTable> symbols;
|
||||
|
||||
void insert(Symbol name, Value * value, PosIdx pos = noPos)
|
||||
{
|
||||
|
|
@ -232,10 +487,26 @@ public:
|
|||
|
||||
void push_back(const Attr & attr)
|
||||
{
|
||||
assert(bindings->size() < capacity_);
|
||||
assert(bindings->numAttrs < capacity_);
|
||||
bindings->push_back(attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Layer" the newly constructured Bindings on top of another attribute set.
|
||||
*
|
||||
* This effectively performs an attribute set merge, while giving preference
|
||||
* to attributes from the newly constructed Bindings in case of duplicate attribute
|
||||
* names.
|
||||
*
|
||||
* This operation amortizes the need to copy over all attributes and allows
|
||||
* for efficient implementation of attribute set merges (ExprOpUpdate::eval).
|
||||
*/
|
||||
void layerOnTopOf(const Bindings & base) noexcept
|
||||
{
|
||||
bindings->baseLayer = &base;
|
||||
bindings->numLayers = base.numLayers + 1;
|
||||
}
|
||||
|
||||
Value & alloc(Symbol name, PosIdx pos = noPos);
|
||||
|
||||
Value & alloc(std::string_view name, PosIdx pos = noPos);
|
||||
|
|
@ -243,11 +514,13 @@ public:
|
|||
Bindings * finish()
|
||||
{
|
||||
bindings->sort();
|
||||
finishSizeIfNecessary();
|
||||
return bindings;
|
||||
}
|
||||
|
||||
Bindings * alreadySorted()
|
||||
{
|
||||
finishSizeIfNecessary();
|
||||
return bindings;
|
||||
}
|
||||
|
||||
|
|
|
|||
70
src/libexpr/include/nix/expr/counter.hh
Normal file
70
src/libexpr/include/nix/expr/counter.hh
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* An atomic counter aligned on a cache line to prevent false sharing.
|
||||
* The counter is only enabled when the `NIX_SHOW_STATS` environment
|
||||
* variable is set. This is to prevent contention on these counters
|
||||
* when multi-threaded evaluation is enabled.
|
||||
*/
|
||||
struct alignas(64) Counter
|
||||
{
|
||||
using value_type = uint64_t;
|
||||
|
||||
std::atomic<value_type> inner{0};
|
||||
|
||||
static bool enabled;
|
||||
|
||||
Counter() {}
|
||||
|
||||
operator value_type() const noexcept
|
||||
{
|
||||
return inner;
|
||||
}
|
||||
|
||||
void operator=(value_type n) noexcept
|
||||
{
|
||||
inner = n;
|
||||
}
|
||||
|
||||
value_type load() const noexcept
|
||||
{
|
||||
return inner;
|
||||
}
|
||||
|
||||
value_type operator++() noexcept
|
||||
{
|
||||
return enabled ? ++inner : 0;
|
||||
}
|
||||
|
||||
value_type operator++(int) noexcept
|
||||
{
|
||||
return enabled ? inner++ : 0;
|
||||
}
|
||||
|
||||
value_type operator--() noexcept
|
||||
{
|
||||
return enabled ? --inner : 0;
|
||||
}
|
||||
|
||||
value_type operator--(int) noexcept
|
||||
{
|
||||
return enabled ? inner-- : 0;
|
||||
}
|
||||
|
||||
value_type operator+=(value_type n) noexcept
|
||||
{
|
||||
return enabled ? inner += n : 0;
|
||||
}
|
||||
|
||||
value_type operator-=(value_type n) noexcept
|
||||
{
|
||||
return enabled ? inner -= n : 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nix
|
||||
|
|
@ -26,7 +26,7 @@ inline void * allocBytes(size_t n)
|
|||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
Value * EvalState::allocValue()
|
||||
Value * EvalMemory::allocValue()
|
||||
{
|
||||
#if NIX_USE_BOEHMGC
|
||||
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
||||
|
|
@ -48,15 +48,15 @@ Value * EvalState::allocValue()
|
|||
void * p = allocBytes(sizeof(Value));
|
||||
#endif
|
||||
|
||||
nrValues++;
|
||||
stats.nrValues++;
|
||||
return (Value *) p;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
Env & EvalState::allocEnv(size_t size)
|
||||
Env & EvalMemory::allocEnv(size_t size)
|
||||
{
|
||||
nrEnvs++;
|
||||
nrValuesInEnvs += size;
|
||||
stats.nrEnvs++;
|
||||
stats.nrValuesInEnvs += size;
|
||||
|
||||
Env * env;
|
||||
|
||||
|
|
|
|||
|
|
@ -342,6 +342,25 @@ struct EvalSettings : Config
|
|||
This is useful for improving code readability and making path literals
|
||||
more explicit.
|
||||
)"};
|
||||
|
||||
Setting<unsigned> bindingsUpdateLayerRhsSizeThreshold{
|
||||
this,
|
||||
sizeof(void *) == 4 ? 8192 : 16,
|
||||
"eval-attrset-update-layer-rhs-threshold",
|
||||
R"(
|
||||
Tunes the maximum size of an attribute set that, when used
|
||||
as a right operand in an [attribute set update expression](@docroot@/language/operators.md#update),
|
||||
uses a more space-efficient linked-list representation of attribute sets.
|
||||
|
||||
Setting this to larger values generally leads to less memory allocations,
|
||||
but may lead to worse evaluation performance.
|
||||
|
||||
A value of `0` disables this optimization completely.
|
||||
|
||||
This is an advanced performance tuning option and typically should not be changed.
|
||||
The default value is chosen to balance performance and memory usage. On 32 bit systems
|
||||
where memory is scarce, the default is a large value to reduce the amount of allocations.
|
||||
)"};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "nix/expr/search-path.hh"
|
||||
#include "nix/expr/repl-exit-status.hh"
|
||||
#include "nix/util/ref.hh"
|
||||
#include "nix/expr/counter.hh"
|
||||
|
||||
// For `NIX_USE_BOEHMGC`, and if that's set, `GC_THREADS`
|
||||
#include "nix/expr/config.hh"
|
||||
|
|
@ -48,6 +49,7 @@ class StorePath;
|
|||
struct SingleDerivedPath;
|
||||
enum RepairFlag : bool;
|
||||
struct MemorySourceAccessor;
|
||||
struct MountedSourceAccessor;
|
||||
|
||||
namespace eval_cache {
|
||||
class EvalCache;
|
||||
|
|
@ -300,6 +302,68 @@ struct StaticEvalSymbols
|
|||
}
|
||||
};
|
||||
|
||||
class EvalMemory
|
||||
{
|
||||
#if NIX_USE_BOEHMGC
|
||||
/**
|
||||
* Allocation cache for GC'd Value objects.
|
||||
*/
|
||||
std::shared_ptr<void *> valueAllocCache;
|
||||
|
||||
/**
|
||||
* Allocation cache for size-1 Env objects.
|
||||
*/
|
||||
std::shared_ptr<void *> env1AllocCache;
|
||||
#endif
|
||||
|
||||
public:
|
||||
struct Statistics
|
||||
{
|
||||
Counter nrEnvs;
|
||||
Counter nrValuesInEnvs;
|
||||
Counter nrValues;
|
||||
Counter nrAttrsets;
|
||||
Counter nrAttrsInAttrsets;
|
||||
Counter nrListElems;
|
||||
};
|
||||
|
||||
EvalMemory();
|
||||
|
||||
EvalMemory(const EvalMemory &) = delete;
|
||||
EvalMemory(EvalMemory &&) = delete;
|
||||
EvalMemory & operator=(const EvalMemory &) = delete;
|
||||
EvalMemory & operator=(EvalMemory &&) = delete;
|
||||
|
||||
inline Value * allocValue();
|
||||
inline Env & allocEnv(size_t size);
|
||||
|
||||
Bindings * allocBindings(size_t capacity);
|
||||
|
||||
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
|
||||
{
|
||||
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
|
||||
}
|
||||
|
||||
ListBuilder buildList(size_t size)
|
||||
{
|
||||
stats.nrListElems += size;
|
||||
return ListBuilder(size);
|
||||
}
|
||||
|
||||
const Statistics & getStats() const &
|
||||
{
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage for the AST nodes
|
||||
*/
|
||||
Exprs exprs;
|
||||
|
||||
private:
|
||||
Statistics stats;
|
||||
};
|
||||
|
||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||
{
|
||||
public:
|
||||
|
|
@ -310,6 +374,8 @@ public:
|
|||
SymbolTable symbols;
|
||||
PosTable positions;
|
||||
|
||||
EvalMemory mem;
|
||||
|
||||
/**
|
||||
* If set, force copying files to the Nix store even if they
|
||||
* already exist there.
|
||||
|
|
@ -319,7 +385,7 @@ public:
|
|||
/**
|
||||
* The accessor corresponding to `store`.
|
||||
*/
|
||||
const ref<SourceAccessor> storeFS;
|
||||
const ref<MountedSourceAccessor> storeFS;
|
||||
|
||||
/**
|
||||
* The accessor for the root filesystem.
|
||||
|
|
@ -439,18 +505,6 @@ private:
|
|||
*/
|
||||
std::shared_ptr<RegexCache> regexCache;
|
||||
|
||||
#if NIX_USE_BOEHMGC
|
||||
/**
|
||||
* Allocation cache for GC'd Value objects.
|
||||
*/
|
||||
std::shared_ptr<void *> valueAllocCache;
|
||||
|
||||
/**
|
||||
* Allocation cache for size-1 Env objects.
|
||||
*/
|
||||
std::shared_ptr<void *> env1AllocCache;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
EvalState(
|
||||
|
|
@ -461,6 +515,15 @@ public:
|
|||
std::shared_ptr<Store> buildStore = nullptr);
|
||||
~EvalState();
|
||||
|
||||
/**
|
||||
* A wrapper around EvalMemory::allocValue() to avoid code churn when it
|
||||
* was introduced.
|
||||
*/
|
||||
inline Value * allocValue()
|
||||
{
|
||||
return mem.allocValue();
|
||||
}
|
||||
|
||||
LookupPath getLookupPath()
|
||||
{
|
||||
return lookupPath;
|
||||
|
|
@ -488,8 +551,11 @@ public:
|
|||
|
||||
/**
|
||||
* Allow access to a path.
|
||||
*
|
||||
* Only for restrict eval: pure eval just whitelist store paths,
|
||||
* never arbitrary paths.
|
||||
*/
|
||||
void allowPath(const Path & path);
|
||||
void allowPathLegacy(const Path & path);
|
||||
|
||||
/**
|
||||
* Allow access to a store path. Note that this gets remapped to
|
||||
|
|
@ -829,22 +895,14 @@ public:
|
|||
*/
|
||||
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/**
|
||||
* Allocation primitives.
|
||||
*/
|
||||
inline Value * allocValue();
|
||||
inline Env & allocEnv(size_t size);
|
||||
|
||||
Bindings * allocBindings(size_t capacity);
|
||||
|
||||
BindingsBuilder buildBindings(size_t capacity)
|
||||
{
|
||||
return BindingsBuilder(*this, allocBindings(capacity), capacity);
|
||||
return mem.buildBindings(symbols, capacity);
|
||||
}
|
||||
|
||||
ListBuilder buildList(size_t size)
|
||||
{
|
||||
return ListBuilder(*this, size);
|
||||
return mem.buildList(size);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -961,19 +1019,13 @@ private:
|
|||
*/
|
||||
std::string mkSingleDerivedPathStringRaw(const SingleDerivedPath & p);
|
||||
|
||||
unsigned long nrEnvs = 0;
|
||||
unsigned long nrValuesInEnvs = 0;
|
||||
unsigned long nrValues = 0;
|
||||
unsigned long nrListElems = 0;
|
||||
unsigned long nrLookups = 0;
|
||||
unsigned long nrAttrsets = 0;
|
||||
unsigned long nrAttrsInAttrsets = 0;
|
||||
unsigned long nrAvoided = 0;
|
||||
unsigned long nrOpUpdates = 0;
|
||||
unsigned long nrOpUpdateValuesCopied = 0;
|
||||
unsigned long nrListConcats = 0;
|
||||
unsigned long nrPrimOpCalls = 0;
|
||||
unsigned long nrFunctionCalls = 0;
|
||||
Counter nrLookups;
|
||||
Counter nrAvoided;
|
||||
Counter nrOpUpdates;
|
||||
Counter nrOpUpdateValuesCopied;
|
||||
Counter nrListConcats;
|
||||
Counter nrPrimOpCalls;
|
||||
Counter nrFunctionCalls;
|
||||
|
||||
bool countCalls;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,4 +26,20 @@ using SmallValueVector = SmallVector<Value *, nItems>;
|
|||
template<size_t nItems>
|
||||
using SmallTemporaryValueVector = SmallVector<Value, nItems>;
|
||||
|
||||
/**
|
||||
* For functions where we do not expect deep recursion, we can use a sizable
|
||||
* part of the stack a free allocation space.
|
||||
*
|
||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||
*/
|
||||
constexpr size_t nonRecursiveStackReservation = 128;
|
||||
|
||||
/**
|
||||
* Functions that maybe applied to self-similar inputs, such as concatMap on a
|
||||
* tree, should reserve a smaller part of the stack for allocation.
|
||||
*
|
||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||
*/
|
||||
constexpr size_t conservativeStackReservation = 16;
|
||||
|
||||
} // namespace nix
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ config_pub_h = configure_file(
|
|||
headers = [ config_pub_h ] + files(
|
||||
'attr-path.hh',
|
||||
'attr-set.hh',
|
||||
'counter.hh',
|
||||
'eval-cache.hh',
|
||||
'eval-error.hh',
|
||||
'eval-gc.hh',
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory_resource>
|
||||
|
||||
#include "nix/expr/gc-small-vector.hh"
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/symbol-table.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/util/pos-idx.hh"
|
||||
#include "nix/expr/counter.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -80,6 +83,15 @@ typedef std::vector<AttrName> AttrPath;
|
|||
|
||||
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
|
||||
|
||||
using UpdateQueue = SmallTemporaryValueVector<conservativeStackReservation>;
|
||||
|
||||
class Exprs
|
||||
{
|
||||
std::pmr::monotonic_buffer_resource buffer;
|
||||
public:
|
||||
std::pmr::polymorphic_allocator<char> alloc{&buffer};
|
||||
};
|
||||
|
||||
/* Abstract syntax of Nix expressions. */
|
||||
|
||||
struct Expr
|
||||
|
|
@ -89,7 +101,7 @@ struct Expr
|
|||
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
|
||||
};
|
||||
|
||||
static unsigned long nrExprs;
|
||||
static Counter nrExprs;
|
||||
|
||||
Expr()
|
||||
{
|
||||
|
|
@ -99,8 +111,25 @@ struct Expr
|
|||
virtual ~Expr() {};
|
||||
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
|
||||
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
|
||||
|
||||
/** Normal evaluation, implemented directly by all subclasses. */
|
||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||
|
||||
/**
|
||||
* Create a thunk for the delayed computation of the given expression
|
||||
* in the given environment. But if the expression is a variable,
|
||||
* then look it up right away. This significantly reduces the number
|
||||
* of thunks allocated.
|
||||
*/
|
||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||
|
||||
/**
|
||||
* Only called when performing an attrset update: `//` or similar.
|
||||
* Instead of writing to a Value &, this function writes to an UpdateQueue.
|
||||
* This allows the expression to perform multiple updates in a delayed manner, gathering up all the updates before
|
||||
* applying them.
|
||||
*/
|
||||
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx);
|
||||
virtual void setName(Symbol name);
|
||||
virtual void setDocComment(DocComment docComment) {};
|
||||
|
||||
|
|
@ -152,13 +181,28 @@ struct ExprFloat : Expr
|
|||
|
||||
struct ExprString : Expr
|
||||
{
|
||||
std::string s;
|
||||
Value v;
|
||||
|
||||
ExprString(std::string && s)
|
||||
: s(std::move(s))
|
||||
/**
|
||||
* This is only for strings already allocated in our polymorphic allocator,
|
||||
* or that live at least that long (e.g. c++ string literals)
|
||||
*/
|
||||
ExprString(const char * s)
|
||||
{
|
||||
v.mkStringNoCopy(this->s.data());
|
||||
v.mkStringNoCopy(s);
|
||||
};
|
||||
|
||||
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
|
||||
{
|
||||
auto len = sv.length();
|
||||
if (len == 0) {
|
||||
v.mkStringNoCopy("");
|
||||
return;
|
||||
}
|
||||
char * s = alloc.allocate(len + 1);
|
||||
sv.copy(s, len);
|
||||
s[len] = '\0';
|
||||
v.mkStringNoCopy(s);
|
||||
};
|
||||
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
|
|
@ -565,36 +609,39 @@ struct ExprOpNot : Expr
|
|||
COMMON_METHODS
|
||||
};
|
||||
|
||||
#define MakeBinOp(name, s) \
|
||||
struct name : Expr \
|
||||
{ \
|
||||
PosIdx pos; \
|
||||
Expr *e1, *e2; \
|
||||
name(Expr * e1, Expr * e2) \
|
||||
: e1(e1) \
|
||||
, e2(e2) {}; \
|
||||
name(const PosIdx & pos, Expr * e1, Expr * e2) \
|
||||
: pos(pos) \
|
||||
, e1(e1) \
|
||||
, e2(e2) {}; \
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const override \
|
||||
{ \
|
||||
str << "("; \
|
||||
e1->show(symbols, str); \
|
||||
str << " " s " "; \
|
||||
e2->show(symbols, str); \
|
||||
str << ")"; \
|
||||
} \
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
|
||||
{ \
|
||||
e1->bindVars(es, env); \
|
||||
e2->bindVars(es, env); \
|
||||
} \
|
||||
void eval(EvalState & state, Env & env, Value & v) override; \
|
||||
PosIdx getPos() const override \
|
||||
{ \
|
||||
return pos; \
|
||||
} \
|
||||
#define MakeBinOpMembers(name, s) \
|
||||
PosIdx pos; \
|
||||
Expr *e1, *e2; \
|
||||
name(Expr * e1, Expr * e2) \
|
||||
: e1(e1) \
|
||||
, e2(e2){}; \
|
||||
name(const PosIdx & pos, Expr * e1, Expr * e2) \
|
||||
: pos(pos) \
|
||||
, e1(e1) \
|
||||
, e2(e2){}; \
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const override \
|
||||
{ \
|
||||
str << "("; \
|
||||
e1->show(symbols, str); \
|
||||
str << " " s " "; \
|
||||
e2->show(symbols, str); \
|
||||
str << ")"; \
|
||||
} \
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
|
||||
{ \
|
||||
e1->bindVars(es, env); \
|
||||
e2->bindVars(es, env); \
|
||||
} \
|
||||
void eval(EvalState & state, Env & env, Value & v) override; \
|
||||
PosIdx getPos() const override \
|
||||
{ \
|
||||
return pos; \
|
||||
}
|
||||
|
||||
#define MakeBinOp(name, s) \
|
||||
struct name : Expr \
|
||||
{ \
|
||||
MakeBinOpMembers(name, s) \
|
||||
}
|
||||
|
||||
MakeBinOp(ExprOpEq, "==");
|
||||
|
|
@ -602,9 +649,20 @@ MakeBinOp(ExprOpNEq, "!=");
|
|||
MakeBinOp(ExprOpAnd, "&&");
|
||||
MakeBinOp(ExprOpOr, "||");
|
||||
MakeBinOp(ExprOpImpl, "->");
|
||||
MakeBinOp(ExprOpUpdate, "//");
|
||||
MakeBinOp(ExprOpConcatLists, "++");
|
||||
|
||||
struct ExprOpUpdate : Expr
|
||||
{
|
||||
private:
|
||||
/** Special case for merging of two attrsets. */
|
||||
void eval(EvalState & state, Value & v, Value & v1, Value & v2);
|
||||
void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q);
|
||||
|
||||
public:
|
||||
MakeBinOpMembers(ExprOpUpdate, "//");
|
||||
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx) override;
|
||||
};
|
||||
|
||||
struct ExprConcatStrings : Expr
|
||||
{
|
||||
PosIdx pos;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ struct StringToken
|
|||
}
|
||||
};
|
||||
|
||||
// This type must be trivially copyable; see YYLTYPE_IS_TRIVIAL in parser.y.
|
||||
struct ParserLocation
|
||||
{
|
||||
int beginOffset;
|
||||
|
|
@ -44,9 +43,6 @@ struct ParserLocation
|
|||
beginOffset = stashedBeginOffset;
|
||||
endOffset = stashedEndOffset;
|
||||
}
|
||||
|
||||
/** Latest doc comment position, or 0. */
|
||||
int doc_comment_first_column, doc_comment_last_column;
|
||||
};
|
||||
|
||||
struct LexerState
|
||||
|
|
@ -82,6 +78,7 @@ struct LexerState
|
|||
struct ParserState
|
||||
{
|
||||
const LexerState & lexerState;
|
||||
std::pmr::polymorphic_allocator<char> & alloc;
|
||||
SymbolTable & symbols;
|
||||
PosTable & positions;
|
||||
Expr * result;
|
||||
|
|
@ -327,7 +324,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
|
|||
|
||||
// Ignore empty strings for a minor optimisation and AST simplification
|
||||
if (s2 != "") {
|
||||
es2->emplace_back(i->first, new ExprString(std::move(s2)));
|
||||
es2->emplace_back(i->first, new ExprString(alloc, s2));
|
||||
}
|
||||
};
|
||||
for (; i != es.end(); ++i, --n) {
|
||||
|
|
|
|||
|
|
@ -8,22 +8,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* For functions where we do not expect deep recursion, we can use a sizable
|
||||
* part of the stack a free allocation space.
|
||||
*
|
||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||
*/
|
||||
constexpr size_t nonRecursiveStackReservation = 128;
|
||||
|
||||
/**
|
||||
* Functions that maybe applied to self-similar inputs, such as concatMap on a
|
||||
* tree, should reserve a smaller part of the stack for allocation.
|
||||
*
|
||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||
*/
|
||||
constexpr size_t conservativeStackReservation = 16;
|
||||
|
||||
struct RegisterPrimOp
|
||||
{
|
||||
typedef std::vector<PrimOp> PrimOps;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class ListBuilder
|
|||
Value * inlineElems[2] = {nullptr, nullptr};
|
||||
public:
|
||||
Value ** elems;
|
||||
ListBuilder(EvalState & state, size_t size);
|
||||
ListBuilder(size_t size);
|
||||
|
||||
// NOTE: Can be noexcept because we are just copying integral values and
|
||||
// raw pointers.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#include "lexer-helpers.hh"
|
||||
|
||||
void nix::lexer::internal::initLoc(YYLTYPE * loc)
|
||||
void nix::lexer::internal::initLoc(Parser::location_type * loc)
|
||||
{
|
||||
loc->beginOffset = loc->endOffset = 0;
|
||||
}
|
||||
|
||||
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len)
|
||||
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len)
|
||||
{
|
||||
loc->stash();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,12 @@
|
|||
|
||||
#include <cstddef>
|
||||
|
||||
// including the generated headers twice leads to errors
|
||||
#ifndef BISON_HEADER
|
||||
# include "lexer-tab.hh"
|
||||
# include "parser-tab.hh"
|
||||
#endif
|
||||
#include "parser-scanner-decls.hh"
|
||||
|
||||
namespace nix::lexer::internal {
|
||||
|
||||
void initLoc(YYLTYPE * loc);
|
||||
void initLoc(Parser::location_type * loc);
|
||||
|
||||
void adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len);
|
||||
void adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len);
|
||||
|
||||
} // namespace nix::lexer::internal
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ static void requireExperimentalFeature(const ExperimentalFeature & feature, cons
|
|||
|
||||
}
|
||||
|
||||
using enum nix::Parser::token::token_kind_type;
|
||||
using YYSTYPE = nix::Parser::value_type;
|
||||
using YYLTYPE = nix::Parser::location_type;
|
||||
|
||||
// yacc generates code that uses unannotated fallthrough.
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@ deps_other += boost
|
|||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
bdw_gc = dependency('bdw-gc', required : get_option('gc'))
|
||||
bdw_gc_required = get_option('gc').disable_if(
|
||||
'address' in get_option('b_sanitize'),
|
||||
error_message : 'Building with Boehm GC and ASAN is not supported',
|
||||
)
|
||||
|
||||
bdw_gc = dependency('bdw-gc', required : bdw_gc_required)
|
||||
if bdw_gc.found()
|
||||
deps_public += bdw_gc
|
||||
foreach funcspec : [
|
||||
|
|
@ -64,6 +69,10 @@ if bdw_gc.found()
|
|||
define_value = cxx.has_function(funcspec).to_int()
|
||||
configdata_priv.set(define_name, define_value)
|
||||
endforeach
|
||||
if host_machine.system() == 'cygwin'
|
||||
# undefined reference to `__wrap__Znwm'
|
||||
configdata_pub.set('GC_NO_INLINE_STD_NEW', 1)
|
||||
endif
|
||||
endif
|
||||
# Used in public header. Affects ABI!
|
||||
configdata_pub.set('NIX_USE_BOEHMGC', bdw_gc.found().to_int())
|
||||
|
|
@ -88,6 +97,7 @@ config_priv_h = configure_file(
|
|||
)
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
parser_tab = custom_target(
|
||||
input : 'parser.y',
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
unsigned long Expr::nrExprs = 0;
|
||||
Counter Expr::nrExprs;
|
||||
|
||||
ExprBlackHole eBlackHole;
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
|
|||
|
||||
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
||||
{
|
||||
printLiteralString(str, s);
|
||||
printLiteralString(str, v.string_view());
|
||||
}
|
||||
|
||||
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
||||
|
|
|
|||
17
src/libexpr/parser-scanner-decls.hh
Normal file
17
src/libexpr/parser-scanner-decls.hh
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef BISON_HEADER
|
||||
# include "parser-tab.hh"
|
||||
using YYSTYPE = nix::parser::BisonParser::value_type;
|
||||
using YYLTYPE = nix::parser::BisonParser::location_type;
|
||||
# include "lexer-tab.hh" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Parser : public parser::BisonParser
|
||||
{
|
||||
using BisonParser::BisonParser;
|
||||
};
|
||||
|
||||
} // namespace nix
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
%skeleton "lalr1.cc"
|
||||
%define api.location.type { ::nix::ParserLocation }
|
||||
%define api.pure
|
||||
%define api.namespace { ::nix::parser }
|
||||
%define api.parser.class { BisonParser }
|
||||
%locations
|
||||
%define parse.error verbose
|
||||
%defines
|
||||
|
|
@ -26,19 +28,12 @@
|
|||
#include "nix/expr/eval-settings.hh"
|
||||
#include "nix/expr/parser-state.hh"
|
||||
|
||||
// Bison seems to have difficulty growing the parser stack when using C++ with
|
||||
// a custom location type. This undocumented macro tells Bison that our
|
||||
// location type is "trivially copyable" in C++-ese, so it is safe to use the
|
||||
// same memcpy macro it uses to grow the stack that it uses with its own
|
||||
// default location type. Without this, we get "error: memory exhausted" when
|
||||
// parsing some large Nix files. Our other options are to increase the initial
|
||||
// stack size (200 by default) to be as large as we ever want to support (so
|
||||
// that growing the stack is unnecessary), or redefine the stack-relocation
|
||||
// macro ourselves (which is also undocumented).
|
||||
#define YYLTYPE_IS_TRIVIAL 1
|
||||
|
||||
#define YY_DECL int yylex \
|
||||
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
|
||||
#define YY_DECL \
|
||||
int yylex( \
|
||||
nix::Parser::value_type * yylval_param, \
|
||||
nix::Parser::location_type * yylloc_param, \
|
||||
yyscan_t yyscanner, \
|
||||
nix::ParserState * state)
|
||||
|
||||
// For efficiency, we only track offsets; not line,column coordinates
|
||||
# define YYLLOC_DEFAULT(Current, Rhs, N) \
|
||||
|
|
@ -64,6 +59,7 @@ Expr * parseExprFromBuf(
|
|||
size_t length,
|
||||
Pos::Origin origin,
|
||||
const SourcePath & basePath,
|
||||
std::pmr::polymorphic_allocator<char> & alloc,
|
||||
SymbolTable & symbols,
|
||||
const EvalSettings & settings,
|
||||
PosTable & positions,
|
||||
|
|
@ -78,24 +74,30 @@ Expr * parseExprFromBuf(
|
|||
|
||||
%{
|
||||
|
||||
#include "parser-tab.hh"
|
||||
#include "lexer-tab.hh"
|
||||
/* The parser is very performance sensitive and loses out on a lot
|
||||
of performance even with basic stdlib assertions. Since those don't
|
||||
affect ABI we can disable those just for this file. */
|
||||
#if defined(_GLIBCXX_ASSERTIONS) && !defined(_GLIBCXX_DEBUG)
|
||||
#undef _GLIBCXX_ASSERTIONS
|
||||
#endif
|
||||
|
||||
#include "parser-scanner-decls.hh"
|
||||
|
||||
YY_DECL;
|
||||
|
||||
using namespace nix;
|
||||
|
||||
#define CUR_POS state->at(yyloc)
|
||||
#define CUR_POS state->at(yylhs.location)
|
||||
|
||||
|
||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
|
||||
void parser::BisonParser::error(const location_type &loc_, const std::string &error)
|
||||
{
|
||||
auto loc = loc_;
|
||||
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
|
||||
loc->beginOffset = loc->endOffset;
|
||||
loc.beginOffset = loc.endOffset;
|
||||
}
|
||||
throw ParseError({
|
||||
.msg = HintFmt(error),
|
||||
.pos = state->positions[state->at(*loc)]
|
||||
.pos = state->positions[state->at(loc)]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +136,7 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
|||
std::vector<nix::AttrName> * attrNames;
|
||||
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
|
||||
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
|
||||
std::variant<nix::Expr *, std::string_view> * to_be_string;
|
||||
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +151,8 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
|||
%type <inheritAttrs> attrs
|
||||
%type <string_parts> string_parts_interpolated
|
||||
%type <ind_string_parts> ind_string_parts
|
||||
%type <e> path_start string_parts string_attr
|
||||
%type <e> path_start
|
||||
%type <to_be_string> string_parts string_attr
|
||||
%type <id> attr
|
||||
%token <id> ID
|
||||
%token <str> STR IND_STR
|
||||
|
|
@ -182,7 +186,7 @@ start: expr {
|
|||
state->result = $1;
|
||||
|
||||
// This parser does not use yynerrs; suppress the warning.
|
||||
(void) yynerrs;
|
||||
(void) yynerrs_;
|
||||
};
|
||||
|
||||
expr: expr_function;
|
||||
|
|
@ -303,7 +307,13 @@ expr_simple
|
|||
}
|
||||
| INT_LIT { $$ = new ExprInt($1); }
|
||||
| FLOAT_LIT { $$ = new ExprFloat($1); }
|
||||
| '"' string_parts '"' { $$ = $2; }
|
||||
| '"' string_parts '"' {
|
||||
std::visit(overloaded{
|
||||
[&](std::string_view str) { $$ = new ExprString(state->alloc, str); },
|
||||
[&](Expr * expr) { $$ = expr; }},
|
||||
*$2);
|
||||
delete $2;
|
||||
}
|
||||
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
|
||||
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
|
||||
delete $2;
|
||||
|
|
@ -314,11 +324,11 @@ expr_simple
|
|||
$$ = new ExprConcatStrings(CUR_POS, false, $2);
|
||||
}
|
||||
| SPATH {
|
||||
std::string path($1.p + 1, $1.l - 2);
|
||||
std::string_view path($1.p + 1, $1.l - 2);
|
||||
$$ = new ExprCall(CUR_POS,
|
||||
new ExprVar(state->s.findFile),
|
||||
{new ExprVar(state->s.nixPath),
|
||||
new ExprString(std::move(path))});
|
||||
new ExprString(state->alloc, path)});
|
||||
}
|
||||
| URI {
|
||||
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
|
||||
|
|
@ -327,7 +337,7 @@ expr_simple
|
|||
.msg = HintFmt("URL literals are disabled"),
|
||||
.pos = state->positions[CUR_POS]
|
||||
});
|
||||
$$ = new ExprString(std::string($1));
|
||||
$$ = new ExprString(state->alloc, $1);
|
||||
}
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
/* Let expressions `let {..., body = ...}' are just desugared
|
||||
|
|
@ -344,19 +354,19 @@ expr_simple
|
|||
;
|
||||
|
||||
string_parts
|
||||
: STR { $$ = new ExprString(std::string($1)); }
|
||||
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
|
||||
| { $$ = new ExprString(""); }
|
||||
: STR { $$ = new std::variant<Expr *, std::string_view>($1); }
|
||||
| string_parts_interpolated { $$ = new std::variant<Expr *, std::string_view>(new ExprConcatStrings(CUR_POS, true, $1)); }
|
||||
| { $$ = new std::variant<Expr *, std::string_view>(std::string_view()); }
|
||||
;
|
||||
|
||||
string_parts_interpolated
|
||||
: string_parts_interpolated STR
|
||||
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(std::string($2))); }
|
||||
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(state->alloc, $2)); }
|
||||
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
|
||||
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(state->at(@1), $2); }
|
||||
| STR DOLLAR_CURLY expr '}' {
|
||||
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
|
||||
$$->emplace_back(state->at(@1), new ExprString(std::string($1)));
|
||||
$$->emplace_back(state->at(@1), new ExprString(state->alloc, $1));
|
||||
$$->emplace_back(state->at(@2), $3);
|
||||
}
|
||||
;
|
||||
|
|
@ -454,15 +464,16 @@ attrs
|
|||
: attrs attr { $$ = $1; $1->emplace_back(AttrName(state->symbols.create($2)), state->at(@2)); }
|
||||
| attrs string_attr
|
||||
{ $$ = $1;
|
||||
ExprString * str = dynamic_cast<ExprString *>($2);
|
||||
if (str) {
|
||||
$$->emplace_back(AttrName(state->symbols.create(str->s)), state->at(@2));
|
||||
delete str;
|
||||
} else
|
||||
throw ParseError({
|
||||
.msg = HintFmt("dynamic attributes not allowed in inherit"),
|
||||
.pos = state->positions[state->at(@2)]
|
||||
});
|
||||
std::visit(overloaded {
|
||||
[&](std::string_view str) { $$->emplace_back(AttrName(state->symbols.create(str)), state->at(@2)); },
|
||||
[&](Expr * expr) {
|
||||
throw ParseError({
|
||||
.msg = HintFmt("dynamic attributes not allowed in inherit"),
|
||||
.pos = state->positions[state->at(@2)]
|
||||
});
|
||||
}
|
||||
}, *$2);
|
||||
delete $2;
|
||||
}
|
||||
| { $$ = new std::vector<std::pair<AttrName, PosIdx>>; }
|
||||
;
|
||||
|
|
@ -471,22 +482,20 @@ attrpath
|
|||
: attrpath '.' attr { $$ = $1; $1->push_back(AttrName(state->symbols.create($3))); }
|
||||
| attrpath '.' string_attr
|
||||
{ $$ = $1;
|
||||
ExprString * str = dynamic_cast<ExprString *>($3);
|
||||
if (str) {
|
||||
$$->push_back(AttrName(state->symbols.create(str->s)));
|
||||
delete str;
|
||||
} else
|
||||
$$->push_back(AttrName($3));
|
||||
std::visit(overloaded {
|
||||
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
|
||||
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
|
||||
}, *$3);
|
||||
delete $3;
|
||||
}
|
||||
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(state->symbols.create($1))); }
|
||||
| string_attr
|
||||
{ $$ = new std::vector<AttrName>;
|
||||
ExprString *str = dynamic_cast<ExprString *>($1);
|
||||
if (str) {
|
||||
$$->push_back(AttrName(state->symbols.create(str->s)));
|
||||
delete str;
|
||||
} else
|
||||
$$->push_back(AttrName($1));
|
||||
std::visit(overloaded {
|
||||
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
|
||||
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
|
||||
}, *$1);
|
||||
delete $1;
|
||||
}
|
||||
;
|
||||
|
||||
|
|
@ -497,7 +506,7 @@ attr
|
|||
|
||||
string_attr
|
||||
: '"' string_parts '"' { $$ = $2; }
|
||||
| DOLLAR_CURLY expr '}' { $$ = $2; }
|
||||
| DOLLAR_CURLY expr '}' { $$ = new std::variant<Expr *, std::string_view>($2); }
|
||||
;
|
||||
|
||||
expr_list
|
||||
|
|
@ -537,6 +546,7 @@ Expr * parseExprFromBuf(
|
|||
size_t length,
|
||||
Pos::Origin origin,
|
||||
const SourcePath & basePath,
|
||||
std::pmr::polymorphic_allocator<char> & alloc,
|
||||
SymbolTable & symbols,
|
||||
const EvalSettings & settings,
|
||||
PosTable & positions,
|
||||
|
|
@ -551,6 +561,7 @@ Expr * parseExprFromBuf(
|
|||
};
|
||||
ParserState state {
|
||||
.lexerState = lexerState,
|
||||
.alloc = alloc,
|
||||
.symbols = symbols,
|
||||
.positions = positions,
|
||||
.basePath = basePath,
|
||||
|
|
@ -563,7 +574,8 @@ Expr * parseExprFromBuf(
|
|||
Finally _destroy([&] { yylex_destroy(scanner); });
|
||||
|
||||
yy_scan_buffer(text, length, scanner);
|
||||
yyparse(scanner, &state);
|
||||
Parser parser(scanner, &state);
|
||||
parser.parse();
|
||||
|
||||
return state.result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path,
|
|||
{
|
||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||
|
||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
||||
Env * env = &state.mem.allocEnv(vScope->attrs()->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv, vScope->attrs()->size());
|
||||
|
|
@ -3161,7 +3161,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args,
|
|||
// Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
|
||||
auto listView = args[0]->listView();
|
||||
size_t listSize = listView.size();
|
||||
auto & bindings = *state.allocBindings(listSize);
|
||||
auto & bindings = *state.mem.allocBindings(listSize);
|
||||
using ElemPtr = decltype(&bindings[0].value);
|
||||
|
||||
for (const auto & [n, v2] : enumerate(listView)) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ add_project_arguments(
|
|||
)
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'nix_api_fetchers.cc',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "nix/store/store-open.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/store/dummy-store.hh"
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/fetchers/fetchers.hh"
|
||||
#include "nix/fetchers/git-utils.hh"
|
||||
|
|
@ -179,10 +180,11 @@ TEST_F(GitTest, submodulePeriodSupport)
|
|||
// 6) Commit the addition in super
|
||||
commitAll(super.get(), "Add submodule with branch='.'");
|
||||
|
||||
// TODO: Use dummy:// store with MemorySourceAccessor.
|
||||
Path storeTmpDir = createTempDir();
|
||||
auto storeTmpDirAutoDelete = AutoDelete(storeTmpDir, true);
|
||||
ref<Store> store = openStore(storeTmpDir);
|
||||
auto store = [] {
|
||||
auto cfg = make_ref<DummyStoreConfig>(StoreReference::Params{});
|
||||
cfg->readOnly = false;
|
||||
return cfg->openStore();
|
||||
}();
|
||||
|
||||
auto settings = fetchers::Settings{};
|
||||
auto input = fetchers::Input::fromAttrs(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ libgit2 = dependency('libgit2')
|
|||
deps_private += libgit2
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'access-tokens.cc',
|
||||
|
|
@ -63,7 +64,7 @@ this_exe = executable(
|
|||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
env : asan_test_options_env + {
|
||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ mkMesonExecutable (finalAttrs: {
|
|||
buildInputs = [ writableTmpDirAsHomeHook ];
|
||||
}
|
||||
''
|
||||
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "nix/fetchers/fetchers.hh"
|
||||
#include "nix/util/json-utils.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "nix/util/tests/characterization.hh"
|
||||
#include "nix/util/tests/json-characterization.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
class PublicKeyTest : public CharacterizationTest
|
||||
class PublicKeyTest : public JsonCharacterizationTest<fetchers::PublicKey>,
|
||||
public ::testing::WithParamInterface<std::pair<std::string_view, fetchers::PublicKey>>
|
||||
{
|
||||
std::filesystem::path unitTestData = getUnitTestData() / "public-key";
|
||||
|
||||
|
|
@ -19,30 +19,35 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
#define TEST_JSON(FIXTURE, NAME, VAL) \
|
||||
TEST_F(FIXTURE, PublicKey_##NAME##_from_json) \
|
||||
{ \
|
||||
readTest(#NAME ".json", [&](const auto & encoded_) { \
|
||||
fetchers::PublicKey expected{VAL}; \
|
||||
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
|
||||
ASSERT_EQ(got, expected); \
|
||||
}); \
|
||||
} \
|
||||
\
|
||||
TEST_F(FIXTURE, PublicKey_##NAME##_to_json) \
|
||||
{ \
|
||||
writeTest( \
|
||||
#NAME ".json", \
|
||||
[&]() -> json { return nlohmann::json(fetchers::PublicKey{VAL}); }, \
|
||||
[](const auto & file) { return json::parse(readFile(file)); }, \
|
||||
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
||||
}
|
||||
TEST_P(PublicKeyTest, from_json)
|
||||
{
|
||||
const auto & [name, expected] = GetParam();
|
||||
readJsonTest(name, expected);
|
||||
}
|
||||
|
||||
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"}))
|
||||
TEST_P(PublicKeyTest, to_json)
|
||||
{
|
||||
const auto & [name, value] = GetParam();
|
||||
writeJsonTest(name, value);
|
||||
}
|
||||
|
||||
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey{.key = "ABCDE"})
|
||||
|
||||
#undef TEST_JSON
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
PublicKeyJSON,
|
||||
PublicKeyTest,
|
||||
::testing::Values(
|
||||
std::pair{
|
||||
"simple",
|
||||
fetchers::PublicKey{
|
||||
.type = "ssh-rsa",
|
||||
.key = "ABCDE",
|
||||
},
|
||||
},
|
||||
std::pair{
|
||||
"defaultType",
|
||||
fetchers::PublicKey{
|
||||
.key = "ABCDE",
|
||||
},
|
||||
}));
|
||||
|
||||
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
#include "nix/util/source-path.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
#include "nix/util/json-utils.hh"
|
||||
#include "nix/fetchers/store-path-accessor.hh"
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
|
@ -332,10 +332,20 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
|||
|
||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
|
||||
|
||||
auto accessor = makeStorePathAccessor(store, storePath);
|
||||
// We just ensured the store object was there
|
||||
auto accessor = ref{store->getFSAccessor(storePath)};
|
||||
|
||||
accessor->fingerprint = getFingerprint(store);
|
||||
|
||||
// Store a cache entry for the substituted tree so later fetches
|
||||
// can reuse the existing nar instead of copying the unpacked
|
||||
// input back into the store on every evaluation.
|
||||
if (accessor->fingerprint) {
|
||||
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
|
||||
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
|
||||
settings->getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||
}
|
||||
|
||||
accessor->setPathDisplay("«" + to_string() + "»");
|
||||
|
||||
return {accessor, *this};
|
||||
|
|
@ -509,7 +519,7 @@ fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json &
|
|||
return res;
|
||||
}
|
||||
|
||||
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p)
|
||||
void adl_serializer<fetchers::PublicKey>::to_json(json & json, const fetchers::PublicKey & p)
|
||||
{
|
||||
json["type"] = p.type;
|
||||
json["key"] = p.key;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/util/json-utils.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/mounted-source-accessor.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <string.h>
|
||||
|
|
|
|||
|
|
@ -398,8 +398,8 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
|||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
auto json = nlohmann::json::parse(
|
||||
readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(store->getFSAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
return RefInfo{
|
||||
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
||||
|
|
@ -472,8 +472,8 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
|||
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
auto json = nlohmann::json::parse(
|
||||
readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(store->getFSAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
||||
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,5 @@ headers = files(
|
|||
'git-utils.hh',
|
||||
'input-cache.hh',
|
||||
'registry.hh',
|
||||
'store-path-accessor.hh',
|
||||
'tarball.hh',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "nix/util/source-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class StorePath;
|
||||
class Store;
|
||||
|
||||
ref<SourceAccessor> makeStorePathAccessor(ref<Store> store, const StorePath & storePath);
|
||||
|
||||
SourcePath getUnfilteredRootPath(CanonPath path);
|
||||
|
||||
} // namespace nix
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
#include "nix/util/tarfile.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/url-parts.hh"
|
||||
#include "nix/fetchers/store-path-accessor.hh"
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
|
@ -331,7 +330,8 @@ struct MercurialInputScheme : InputScheme
|
|||
|
||||
auto storePath = fetchToStore(store, input);
|
||||
|
||||
auto accessor = makeStorePathAccessor(store, storePath);
|
||||
// We just added it, it should be there.
|
||||
auto accessor = ref{store->getFSAccessor(storePath)};
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ libgit2 = dependency('libgit2', version : '>= 1.9')
|
|||
deps_private += libgit2
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'attrs.cc',
|
||||
|
|
@ -49,7 +50,6 @@ sources = files(
|
|||
'mercurial.cc',
|
||||
'path.cc',
|
||||
'registry.cc',
|
||||
'store-path-accessor.cc',
|
||||
'tarball.cc',
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include "nix/fetchers/fetchers.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/fetchers/store-path-accessor.hh"
|
||||
#include "nix/fetchers/cache.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
|
|
@ -153,7 +152,7 @@ struct PathInputScheme : InputScheme
|
|||
if (!input.getLastModified())
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||
|
||||
return {makeStorePathAccessor(store, *storePath), std::move(input)};
|
||||
return {ref{store->getFSAccessor(*storePath)}, std::move(input)};
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
#include "nix/fetchers/store-path-accessor.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
ref<SourceAccessor> makeStorePathAccessor(ref<Store> store, const StorePath & storePath)
|
||||
{
|
||||
return projectSubdirSourceAccessor(store->getFSAccessor(), storePath.to_string());
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/tarfile.hh"
|
||||
#include "nix/util/types.hh"
|
||||
#include "nix/fetchers/store-path-accessor.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/fetchers/git-utils.hh"
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
|
|
@ -354,7 +353,7 @@ struct FileInputScheme : CurlInputScheme
|
|||
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
auto accessor = makeStorePathAccessor(store, file.storePath);
|
||||
auto accessor = ref{store->getFSAccessor(file.storePath)};
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ deps_public_maybe_subproject = [
|
|||
subdir('nix-meson-build-support/subprojects')
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'nix_api_flake.cc',
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ gtest = dependency('gtest', main : true)
|
|||
deps_private += gtest
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'flakeref.cc',
|
||||
|
|
@ -58,7 +59,7 @@ this_exe = executable(
|
|||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
env : asan_test_options_env + {
|
||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||
'NIX_CONFIG' : 'extra-experimental-features = flakes',
|
||||
'HOME' : meson.current_build_dir() / 'test-home',
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ mkMesonExecutable (finalAttrs: {
|
|||
buildInputs = [ writableTmpDirAsHomeHook ];
|
||||
}
|
||||
(''
|
||||
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
export NIX_CONFIG="extra-experimental-features = flakes"
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
|||
deps_public += nlohmann_json
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
subdir('nix-meson-build-support/generate-header')
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ deps_public_maybe_subproject = [
|
|||
subdir('nix-meson-build-support/subprojects')
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'nix_api_main.cc',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "nix_api_util_internal.h"
|
||||
|
||||
#include "nix/main/plugin.hh"
|
||||
#include "nix/main/loggers.hh"
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
|
@ -17,4 +18,16 @@ nix_err nix_init_plugins(nix_c_context * context)
|
|||
NIXC_CATCH_ERRS
|
||||
}
|
||||
|
||||
nix_err nix_set_log_format(nix_c_context * context, const char * format)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
if (format == nullptr)
|
||||
return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Log format is null");
|
||||
try {
|
||||
nix::setLogFormat(format);
|
||||
}
|
||||
NIXC_CATCH_ERRS
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ extern "C" {
|
|||
*/
|
||||
nix_err nix_init_plugins(nix_c_context * context);
|
||||
|
||||
/**
|
||||
* @brief Sets the log format
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] format The string name of the format.
|
||||
*/
|
||||
nix_err nix_set_log_format(nix_c_context * context, const char * format);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ config_priv_h = configure_file(
|
|||
)
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'common-args.cc',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ deps_public_maybe_subproject = [
|
|||
subdir('nix-meson-build-support/subprojects')
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'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 = static_cast<nix::Derivation>(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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ headers = files(
|
|||
'outputs-spec.hh',
|
||||
'path.hh',
|
||||
'protocol.hh',
|
||||
'test-main.hh',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,33 +12,32 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -29,11 +29,13 @@ rapidcheck = dependency('rapidcheck')
|
|||
deps_public += rapidcheck
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
subdir('nix-meson-build-support/asan-options')
|
||||
|
||||
sources = files(
|
||||
'derived-path.cc',
|
||||
'outputs-spec.cc',
|
||||
'path.cc',
|
||||
'test-main.cc',
|
||||
)
|
||||
|
||||
subdir('include/nix/store/tests')
|
||||
|
|
|
|||
47
src/libstore-test-support/test-main.cc
Normal file
47
src/libstore-test-support/test-main.cc
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include <cstdlib>
|
||||
|
||||
#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
|
||||
|
|
@ -21,5 +21,6 @@
|
|||
"method": "nar"
|
||||
}
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,5 +32,6 @@
|
|||
],
|
||||
"system": "my-system"
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@
|
|||
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||
"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
],
|
||||
"name": "advanced-attributes-structured-attrs",
|
||||
"outputs": {
|
||||
|
|
@ -100,5 +100,6 @@
|
|||
],
|
||||
"system": "my-system"
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@
|
|||
"system": "my-system"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||
"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||
],
|
||||
"name": "advanced-attributes",
|
||||
"outputs": {
|
||||
|
|
@ -51,5 +51,6 @@
|
|||
"method": "nar"
|
||||
}
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
24
src/libstore-tests/data/derivation/ca/self-contained.json
Normal file
24
src/libstore-tests/data/derivation/ca/self-contained.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"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",
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
"BIG_BAD": "WOLF"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
||||
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
||||
"dynamicOutputs": {
|
||||
"cat": {
|
||||
"dynamicOutputs": {},
|
||||
|
|
@ -30,9 +30,10 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||
],
|
||||
"name": "dyn-dep-derivation",
|
||||
"outputs": {},
|
||||
"system": "wasm-sel4"
|
||||
"system": "wasm-sel4",
|
||||
"version": 3
|
||||
}
|
||||
|
|
@ -15,8 +15,9 @@
|
|||
"name": "advanced-attributes-defaults",
|
||||
"outputs": {
|
||||
"out": {
|
||||
"path": "/nix/store/1qsc7svv43m4dw2prh6mvyf7cai5czji-advanced-attributes-defaults"
|
||||
"path": "1qsc7svv43m4dw2prh6mvyf7cai5czji-advanced-attributes-defaults"
|
||||
}
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
"name": "advanced-attributes-structured-attrs-defaults",
|
||||
"outputs": {
|
||||
"dev": {
|
||||
"path": "/nix/store/8bazivnbipbyi569623skw5zm91z6kc2-advanced-attributes-structured-attrs-defaults-dev"
|
||||
"path": "8bazivnbipbyi569623skw5zm91z6kc2-advanced-attributes-structured-attrs-defaults-dev"
|
||||
},
|
||||
"out": {
|
||||
"path": "/nix/store/f8f8nvnx32bxvyxyx2ff7akbvwhwd9dw-advanced-attributes-structured-attrs-defaults"
|
||||
"path": "f8f8nvnx32bxvyxyx2ff7akbvwhwd9dw-advanced-attributes-structured-attrs-defaults"
|
||||
}
|
||||
},
|
||||
"structuredAttrs": {
|
||||
|
|
@ -28,5 +28,6 @@
|
|||
],
|
||||
"system": "my-system"
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@
|
|||
"out": "/nix/store/7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv": {
|
||||
"afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv": {
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
|
|
@ -26,18 +26,18 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
],
|
||||
"name": "advanced-attributes-structured-attrs",
|
||||
"outputs": {
|
||||
"bin": {
|
||||
"path": "/nix/store/33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin"
|
||||
"path": "33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin"
|
||||
},
|
||||
"dev": {
|
||||
"path": "/nix/store/wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev"
|
||||
"path": "wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev"
|
||||
},
|
||||
"out": {
|
||||
"path": "/nix/store/7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
|
||||
"path": "7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
|
||||
}
|
||||
},
|
||||
"structuredAttrs": {
|
||||
|
|
@ -95,5 +95,6 @@
|
|||
],
|
||||
"system": "my-system"
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@
|
|||
"system": "my-system"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv": {
|
||||
"afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv": {
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"dev",
|
||||
|
|
@ -40,13 +40,14 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
"vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"
|
||||
],
|
||||
"name": "advanced-attributes",
|
||||
"outputs": {
|
||||
"out": {
|
||||
"path": "/nix/store/wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes"
|
||||
"path": "wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes"
|
||||
}
|
||||
},
|
||||
"system": "my-system"
|
||||
"system": "my-system",
|
||||
"version": 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
|
||||
"hashAlgo": "sha256",
|
||||
"method": "flat",
|
||||
"path": "/nix/store/rhcg9h16sqvlbpsa6dqm57sbr2al6nzg-drv-name-output-name"
|
||||
"method": "flat"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
|
||||
"hashAlgo": "sha256",
|
||||
"method": "nar",
|
||||
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
|
||||
"method": "nar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
|
||||
"hashAlgo": "sha256",
|
||||
"method": "text",
|
||||
"path": "/nix/store/6s1zwabh956jvhv4w9xcdb5jiyanyxg1-drv-name-output-name"
|
||||
"method": "text"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
|
||||
"path": "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"BIG_BAD": "WOLF"
|
||||
},
|
||||
"inputDrvs": {
|
||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
||||
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
||||
"dynamicOutputs": {},
|
||||
"outputs": [
|
||||
"cat",
|
||||
|
|
@ -17,9 +17,10 @@
|
|||
}
|
||||
},
|
||||
"inputSrcs": [
|
||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||
],
|
||||
"name": "simple-derivation",
|
||||
"outputs": {},
|
||||
"system": "wasm-sel4"
|
||||
"system": "wasm-sel4",
|
||||
"version": 3
|
||||
}
|
||||
10
src/libstore-tests/data/derived-path/multi_built_built.json
Normal file
10
src/libstore-tests/data/derived-path/multi_built_built.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"drvPath": {
|
||||
"drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"output": "bar"
|
||||
},
|
||||
"outputs": [
|
||||
"baz",
|
||||
"quux"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"drvPath": {
|
||||
"drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"output": "bar"
|
||||
},
|
||||
"outputs": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
1
src/libstore-tests/data/derived-path/multi_opaque.json
Normal file
1
src/libstore-tests/data/derived-path/multi_opaque.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
|
||||
7
src/libstore-tests/data/derived-path/mutli_built.json
Normal file
7
src/libstore-tests/data/derived-path/mutli_built.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"outputs": [
|
||||
"bar",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
4
src/libstore-tests/data/derived-path/single_built.json
Normal file
4
src/libstore-tests/data/derived-path/single_built.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"output": "bar"
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"drvPath": {
|
||||
"drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"output": "bar"
|
||||
},
|
||||
"output": "baz"
|
||||
}
|
||||
1
src/libstore-tests/data/derived-path/single_opaque.json
Normal file
1
src/libstore-tests/data/derived-path/single_opaque.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
|
||||
3
src/libstore-tests/data/outputs-spec/all.json
Normal file
3
src/libstore-tests/data/outputs-spec/all.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
"*"
|
||||
]
|
||||
3
src/libstore-tests/data/outputs-spec/extended/all.json
Normal file
3
src/libstore-tests/data/outputs-spec/extended/all.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
"*"
|
||||
]
|
||||
1
src/libstore-tests/data/outputs-spec/extended/def.json
Normal file
1
src/libstore-tests/data/outputs-spec/extended/def.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
null
|
||||
3
src/libstore-tests/data/outputs-spec/extended/name.json
Normal file
3
src/libstore-tests/data/outputs-spec/extended/name.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
"a"
|
||||
]
|
||||
4
src/libstore-tests/data/outputs-spec/extended/names.json
Normal file
4
src/libstore-tests/data/outputs-spec/extended/names.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
3
src/libstore-tests/data/outputs-spec/name.json
Normal file
3
src/libstore-tests/data/outputs-spec/name.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
"a"
|
||||
]
|
||||
4
src/libstore-tests/data/outputs-spec/names.json
Normal file
4
src/libstore-tests/data/outputs-spec/names.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
6
src/libstore-tests/data/realisation/simple.json
Normal file
6
src/libstore-tests/data/realisation/simple.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dependentRealisations": {},
|
||||
"id": "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!foo",
|
||||
"outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"signatures": []
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependentRealisations": {
|
||||
"sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!foo": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
|
||||
},
|
||||
"id": "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!foo",
|
||||
"outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
|
||||
"signatures": []
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue