mirror of
https://github.com/NixOS/nix.git
synced 2024-11-25 16:23:02 +00:00
libexpr-c: Add nix_string_realise
This commit is contained in:
parent
62f8d8c9a9
commit
02c41aba5b
@ -35,4 +35,10 @@ struct nix_string_context
|
||||
nix::NixStringContext & ctx;
|
||||
};
|
||||
|
||||
struct nix_realised_string
|
||||
{
|
||||
std::string str;
|
||||
std::vector<StorePath> storePaths;
|
||||
};
|
||||
|
||||
#endif // NIX_API_EXPR_INTERNAL_H
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "config.hh"
|
||||
#include "eval.hh"
|
||||
#include "globals.hh"
|
||||
#include "path.hh"
|
||||
#include "primops.hh"
|
||||
#include "value.hh"
|
||||
|
||||
@ -9,7 +10,9 @@
|
||||
#include "nix_api_expr_internal.h"
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_util_internal.h"
|
||||
#include "nix_api_store_internal.h"
|
||||
#include "nix_api_value.h"
|
||||
#include "value/context.hh"
|
||||
|
||||
#ifdef HAVE_BOEHMGC
|
||||
# include "gc/gc.h"
|
||||
@ -528,3 +531,55 @@ void nix_bindings_builder_free(BindingsBuilder * bb)
|
||||
delete (nix::BindingsBuilder *) bb;
|
||||
#endif
|
||||
}
|
||||
|
||||
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, Value * value, bool isIFD)
|
||||
{
|
||||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
auto &v = check_value_not_null(value);
|
||||
nix::NixStringContext stringContext;
|
||||
auto rawStr = state->state.coerceToString(nix::noPos, v, stringContext, "while realising a string").toOwned();
|
||||
nix::StorePathSet storePaths;
|
||||
auto rewrites = state->state.realiseContext(stringContext, &storePaths);
|
||||
|
||||
auto s = nix::rewriteStrings(rawStr, rewrites);
|
||||
|
||||
// Convert to the C API StorePath type and convert to vector for index-based access
|
||||
std::vector<StorePath> vec;
|
||||
for (auto &sp : storePaths) {
|
||||
vec.push_back(StorePath{sp});
|
||||
}
|
||||
|
||||
return new nix_realised_string {
|
||||
.str = s,
|
||||
.storePaths = vec
|
||||
};
|
||||
}
|
||||
NIXC_CATCH_ERRS_NULL
|
||||
}
|
||||
|
||||
void nix_realised_string_free(nix_realised_string * s)
|
||||
{
|
||||
delete s;
|
||||
}
|
||||
|
||||
size_t nix_realised_string_get_buffer_size(nix_realised_string * s)
|
||||
{
|
||||
return s->str.size();
|
||||
}
|
||||
|
||||
const char * nix_realised_string_get_buffer_start(nix_realised_string * s)
|
||||
{
|
||||
return s->str.data();
|
||||
}
|
||||
|
||||
size_t nix_realised_string_get_store_path_count(nix_realised_string * s)
|
||||
{
|
||||
return s->storePaths.size();
|
||||
}
|
||||
|
||||
const StorePath * nix_realised_string_get_store_path(nix_realised_string * s, size_t i)
|
||||
{
|
||||
return &s->storePaths[i];
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_store.h"
|
||||
#include "stdbool.h"
|
||||
#include "stddef.h"
|
||||
#include "stdint.h"
|
||||
@ -69,6 +70,10 @@ typedef struct PrimOp PrimOp;
|
||||
*/
|
||||
typedef struct ExternalValue ExternalValue;
|
||||
|
||||
/** @brief String without placeholders, and realised store paths
|
||||
*/
|
||||
typedef struct nix_realised_string nix_realised_string;
|
||||
|
||||
/** @defgroup primops
|
||||
* @brief Create your own primops
|
||||
* @{
|
||||
@ -167,7 +172,10 @@ const char * nix_get_typename(nix_c_context * context, const Value * value);
|
||||
*/
|
||||
bool nix_get_bool(nix_c_context * context, const Value * value);
|
||||
|
||||
/** @brief Get string
|
||||
/** @brief Get the raw string
|
||||
*
|
||||
* This may contain placeholders.
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value to inspect
|
||||
* @return string
|
||||
@ -425,6 +433,56 @@ nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder,
|
||||
void nix_bindings_builder_free(BindingsBuilder * builder);
|
||||
/**@}*/
|
||||
|
||||
/** @brief Realise a string context.
|
||||
*
|
||||
* This will
|
||||
* - realise the store paths referenced by the string's context, and
|
||||
* - perform the replacement of placeholders.
|
||||
* - create temporary garbage collection roots for the store paths, for
|
||||
* the lifetime of the current process.
|
||||
* - log to stderr
|
||||
*
|
||||
* @param[out] context Optional, stores error information
|
||||
* @param[in] value Nix value, which must be a string
|
||||
* @param[in] state Nix evaluator state
|
||||
* @param[in] isIFD If true, disallow derivation outputs if setting `allow-import-from-derivation` is false.
|
||||
You should set this to true when this call is part of a primop.
|
||||
You should set this to false when building for your application's purpose.
|
||||
* @return NULL if failed, are a new nix_realised_string, which must be freed with nix_realised_string_free
|
||||
*/
|
||||
nix_realised_string * nix_string_realise(nix_c_context * context, EvalState * state, Value * value, bool isIFD);
|
||||
|
||||
/** @brief Start of the string
|
||||
* @param[in] realised_string
|
||||
* @return pointer to the start of the string. It may not be null-terminated.
|
||||
*/
|
||||
const char * nix_realised_string_get_buffer_start(nix_realised_string * realised_string);
|
||||
|
||||
/** @brief Length of the string
|
||||
* @param[in] realised_string
|
||||
* @return length of the string in bytes
|
||||
*/
|
||||
size_t nix_realised_string_get_buffer_size(nix_realised_string * realised_string);
|
||||
|
||||
/** @brief Number of realised store paths
|
||||
* @param[in] realised_string
|
||||
* @return number of realised store paths that were referenced by the string via its context
|
||||
*/
|
||||
size_t nix_realised_string_get_store_path_count(nix_realised_string * realised_string);
|
||||
|
||||
/** @brief Get a store path. The store paths are stored in an arbitrary order.
|
||||
* @param[in] realised_string
|
||||
* @param[in] index index of the store path, must be less than the count
|
||||
* @return store path
|
||||
*/
|
||||
const StorePath * nix_realised_string_get_store_path(nix_realised_string * realised_string, size_t index);
|
||||
|
||||
/** @brief Free a realised string
|
||||
* @param[in] realised_string
|
||||
*/
|
||||
void nix_realised_string_free(nix_realised_string * realised_string);
|
||||
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -733,10 +733,12 @@ public:
|
||||
bool fullGC();
|
||||
|
||||
/**
|
||||
* Realise the given context, and return a mapping from the placeholders
|
||||
* used to construct the associated value to their final store path
|
||||
* Realise the given context
|
||||
* @param[in] context the context to realise
|
||||
* @param[out] maybePaths if not nullptr, all built or referenced store paths will be added to this set
|
||||
* @return a mapping from the placeholders used to construct the associated value to their final store path.
|
||||
*/
|
||||
[[nodiscard]] StringMap realiseContext(const NixStringContext & context);
|
||||
[[nodiscard]] StringMap realiseContext(const NixStringContext & context, StorePathSet * maybePaths = nullptr, bool isIFD = true);
|
||||
|
||||
/* Call the binary path filter predicate used builtins.path etc. */
|
||||
bool callPathFilter(
|
||||
|
@ -39,7 +39,7 @@ namespace nix {
|
||||
* Miscellaneous
|
||||
*************************************************************/
|
||||
|
||||
StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||
StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
|
||||
{
|
||||
std::vector<DerivedPath::Built> drvs;
|
||||
StringMap res;
|
||||
@ -61,19 +61,23 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||
auto ctxS = store->printStorePath(o.path);
|
||||
res.insert_or_assign(ctxS, ctxS);
|
||||
ensureValid(o.path);
|
||||
if (maybePathsOut)
|
||||
maybePathsOut->emplace(o.path);
|
||||
},
|
||||
[&](const NixStringContextElem::DrvDeep & d) {
|
||||
/* Treat same as Opaque */
|
||||
auto ctxS = store->printStorePath(d.drvPath);
|
||||
res.insert_or_assign(ctxS, ctxS);
|
||||
ensureValid(d.drvPath);
|
||||
if (maybePathsOut)
|
||||
maybePathsOut->emplace(d.drvPath);
|
||||
},
|
||||
}, c.raw);
|
||||
}
|
||||
|
||||
if (drvs.empty()) return {};
|
||||
|
||||
if (!evalSettings.enableImportFromDerivation)
|
||||
if (isIFD && !evalSettings.enableImportFromDerivation)
|
||||
error<EvalError>(
|
||||
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
|
||||
drvs.begin()->to_string(*store)
|
||||
@ -90,6 +94,8 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||
auto outputs = resolveDerivedPath(*buildStore, drv, &*store);
|
||||
for (auto & [outputName, outputPath] : outputs) {
|
||||
outputsToCopyAndAllow.insert(outputPath);
|
||||
if (maybePathsOut)
|
||||
maybePathsOut->emplace(outputPath);
|
||||
|
||||
/* Get all the output paths corresponding to the placeholders we had */
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||
@ -106,10 +112,13 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||
}
|
||||
|
||||
if (store != buildStore) copyClosure(*buildStore, *store, outputsToCopyAndAllow);
|
||||
for (auto & outputPath : outputsToCopyAndAllow) {
|
||||
/* Add the output of this derivations to the allowed
|
||||
paths. */
|
||||
allowPath(outputPath);
|
||||
|
||||
if (isIFD) {
|
||||
for (auto & outputPath : outputsToCopyAndAllow) {
|
||||
/* Add the output of this derivations to the allowed
|
||||
paths. */
|
||||
allowPath(outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "tests/nix_api_expr.hh"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nixC {
|
||||
@ -97,4 +98,88 @@ TEST_F(nix_api_expr_test, nix_build_drv)
|
||||
nix_store_path_free(drvStorePath);
|
||||
nix_store_path_free(outStorePath);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_value)
|
||||
{
|
||||
auto expr = "true";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
ASSERT_EQ(nullptr, r);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("cannot coerce")));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_build)
|
||||
{
|
||||
auto expr = R"(
|
||||
derivation { name = "letsbuild";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo failing a build for testing purposes; exit 1;" ];
|
||||
}
|
||||
)";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
ASSERT_EQ(nullptr, r);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("failed with exit code 1")));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context)
|
||||
{
|
||||
// TODO (ca-derivations): add a content-addressed derivation output, which produces a placeholder
|
||||
auto expr = R"(
|
||||
''
|
||||
a derivation output: ${
|
||||
derivation { name = "letsbuild";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo foo > $out" ];
|
||||
}}
|
||||
a path: ${builtins.toFile "just-a-file" "ooh file good"}
|
||||
a derivation path by itself: ${
|
||||
builtins.unsafeDiscardOutputDependency
|
||||
(derivation {
|
||||
name = "not-actually-built-yet";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo foo > $out" ];
|
||||
}).drvPath}
|
||||
''
|
||||
)";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, r);
|
||||
|
||||
auto s = std::string(nix_realised_string_get_buffer_start(r), nix_realised_string_get_buffer_size(r));
|
||||
|
||||
EXPECT_THAT(s, testing::StartsWith("a derivation output:"));
|
||||
EXPECT_THAT(s, testing::HasSubstr("-letsbuild\n"));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-letsbuild.drv")));
|
||||
EXPECT_THAT(s, testing::HasSubstr("a path:"));
|
||||
EXPECT_THAT(s, testing::HasSubstr("-just-a-file"));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-just-a-file.drv")));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("ooh file good")));
|
||||
EXPECT_THAT(s, testing::HasSubstr("a derivation path by itself:"));
|
||||
EXPECT_THAT(s, testing::EndsWith("-not-actually-built-yet.drv\n"));
|
||||
|
||||
std::vector<std::string_view> names;
|
||||
size_t n = nix_realised_string_get_store_path_count(r);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const StorePath * p = nix_realised_string_get_store_path(r, i);
|
||||
names.push_back(p->path.name());
|
||||
}
|
||||
std::sort(names.begin(), names.end());
|
||||
ASSERT_EQ(3, names.size());
|
||||
EXPECT_THAT(names[0], testing::StrEq("just-a-file"));
|
||||
EXPECT_THAT(names[1], testing::StrEq("letsbuild"));
|
||||
EXPECT_THAT(names[2], testing::StrEq("not-actually-built-yet.drv"));
|
||||
|
||||
nix_realised_string_free(r);
|
||||
}
|
||||
|
||||
} // namespace nixC
|
||||
|
@ -23,5 +23,15 @@ protected:
|
||||
}
|
||||
|
||||
nix_c_context * ctx;
|
||||
|
||||
inline void assert_ctx_ok() {
|
||||
if (nix_err_code(ctx) == NIX_OK) {
|
||||
return;
|
||||
}
|
||||
unsigned int n;
|
||||
const char * p = nix_err_msg(nullptr, ctx, &n);
|
||||
std::string msg(p, n);
|
||||
FAIL() << "nix_err_code(ctx) != NIX_OK, message: " << msg;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user