aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/primops.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/primops.cc')
-rw-r--r--src/libexpr/primops.cc364
1 files changed, 188 insertions, 176 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 510f674eb..42efca4e7 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -1,5 +1,6 @@
#include "archive.hh"
#include "derivations.hh"
+#include "downstream-placeholder.hh"
#include "eval-inline.hh"
#include "eval.hh"
#include "globals.hh"
@@ -38,17 +39,16 @@ namespace nix {
InvalidPathError::InvalidPathError(const Path & path) :
EvalError("path '%s' is not valid", path), path(path) {}
-StringMap EvalState::realiseContext(const PathSet & context)
+StringMap EvalState::realiseContext(const NixStringContext & context)
{
std::vector<DerivedPath::Built> drvs;
StringMap res;
- for (auto & c_ : context) {
+ for (auto & c : context) {
auto ensureValid = [&](const StorePath & p) {
if (!store->isValidPath(p))
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
};
- auto c = NixStringContextElem::parse(*store, c_);
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
drvs.push_back(DerivedPath::Built {
@@ -88,7 +88,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
auto outputs = resolveDerivedPath(*store, drv);
for (auto & [outputName, outputPath] : outputs) {
res.insert_or_assign(
- downstreamPlaceholder(*store, drv.drvPath, outputName),
+ DownstreamPlaceholder::unknownCaOutput(drv.drvPath, outputName).render(),
store->printStorePath(outputPath)
);
}
@@ -110,16 +110,16 @@ struct RealisePathFlags {
bool checkForPureEval = true;
};
-static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
+static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
{
- PathSet context;
+ NixStringContext context;
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
try {
StringMap rewrites = state.realiseContext(context);
- auto realPath = state.toRealPath(rewriteStrings(path, rewrites), context);
+ auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
return flags.checkForPureEval
? state.checkSourcePath(realPath)
@@ -130,35 +130,31 @@ static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const Re
}
}
-/* Add and attribute to the given attribute map from the output name to
- the output path, or a placeholder.
-
- Where possible the path is used, but for floating CA derivations we
- may not know it. For sake of determinism we always assume we don't
- and instead put in a place holder. In either case, however, the
- string context will contain the drv path and output name, so
- downstream derivations will have the proper dependency, and in
- addition, before building, the placeholder will be rewritten to be
- the actual path.
-
- The 'drv' and 'drvPath' outputs must correspond. */
+/**
+ * Add and attribute to the given attribute map from the output name to
+ * the output path, or a placeholder.
+ *
+ * Where possible the path is used, but for floating CA derivations we
+ * may not know it. For sake of determinism we always assume we don't
+ * and instead put in a place holder. In either case, however, the
+ * string context will contain the drv path and output name, so
+ * downstream derivations will have the proper dependency, and in
+ * addition, before building, the placeholder will be rewritten to be
+ * the actual path.
+ *
+ * The 'drv' and 'drvPath' outputs must correspond.
+ */
static void mkOutputString(
EvalState & state,
BindingsBuilder & attrs,
const StorePath & drvPath,
- const BasicDerivation & drv,
const std::pair<std::string, DerivationOutput> & o)
{
- auto optOutputPath = o.second.path(*state.store, drv.name, o.first);
- attrs.alloc(o.first).mkString(
- optOutputPath
- ? state.store->printStorePath(*optOutputPath)
- /* Downstream we would substitute this for an actual path once
- we build the floating CA derivation */
- /* FIXME: we need to depend on the basic derivation, not
- derivation */
- : downstreamPlaceholder(*state.store, drvPath, o.first),
- {"!" + o.first + "!" + state.store->printStorePath(drvPath)});
+ state.mkOutputString(
+ attrs.alloc(o.first),
+ drvPath,
+ o.first,
+ o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first));
}
/* Load and evaluate an expression from path specified by the
@@ -166,28 +162,30 @@ static void mkOutputString(
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{
auto path = realisePath(state, pos, vPath);
+ auto path2 = path.path.abs();
// FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
- if (!state.store->isStorePath(path))
+ if (!state.store->isStorePath(path2))
return std::nullopt;
- auto storePath = state.store->parseStorePath(path);
- if (!(state.store->isValidPath(storePath) && isDerivation(path)))
+ auto storePath = state.store->parseStorePath(path2);
+ if (!(state.store->isValidPath(storePath) && isDerivation(path2)))
return std::nullopt;
return storePath;
};
- if (auto optStorePath = isValidDerivationInStore()) {
- auto storePath = *optStorePath;
- Derivation drv = state.store->readDerivation(storePath);
+ if (auto storePath = isValidDerivationInStore()) {
+ Derivation drv = state.store->readDerivation(*storePath);
auto attrs = state.buildBindings(3 + drv.outputs.size());
- attrs.alloc(state.sDrvPath).mkString(path, {"=" + path});
+ attrs.alloc(state.sDrvPath).mkString(path2, {
+ NixStringContextElem::DrvDeep { .drvPath = *storePath },
+ });
attrs.alloc(state.sName).mkString(drv.env["name"]);
auto & outputsVal = attrs.alloc(state.sOutputs);
state.mkList(outputsVal, drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) {
- mkOutputString(state, attrs, storePath, drv, o);
+ mkOutputString(state, attrs, *storePath, o);
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first);
}
@@ -198,7 +196,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
- , "/"), **state.vImportedDrvToDerivation);
+ , CanonPath::root), **state.vImportedDrvToDerivation);
}
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
@@ -206,10 +204,10 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
- else if (path == corepkgsPrefix + "fetchurl.nix") {
+ else if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh"
- , "/"), v);
+ , CanonPath::root), v);
}
else {
@@ -330,7 +328,7 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
- void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
+ void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
@@ -358,7 +356,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto count = args[0]->listSize();
if (count == 0)
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
- PathSet context;
+ NixStringContext context;
auto program = state.coerceToString(pos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec",
false, false).toOwned();
@@ -378,7 +376,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
- parsed = state.parseExprFromString(std::move(output), "/");
+ parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
} catch (Error & e) {
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw;
@@ -588,7 +586,7 @@ struct CompareValues
case nString:
return strcmp(v1->string.s, v2->string.s) < 0;
case nPath:
- return strcmp(v1->path, v2->path) < 0;
+ return strcmp(v1->_path, v2->_path) < 0;
case nList:
// Lexicographic comparison
for (size_t i = 0;; i++) {
@@ -700,12 +698,14 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.arity = 1,
.doc = R"(
Take an *attrset* with values named `startSet` and `operator` in order to
- return a *list of attrsets* by starting with the `startSet`, recursively
- applying the `operator` function to each element. The *attrsets* in the
- `startSet` and produced by the `operator` must each contain value named
- `key` which are comparable to each other. The result is produced by
- repeatedly calling the operator for each element encountered with a
- unique key, terminating when no new elements are produced. For example,
+ return a *list of attrsets* by starting with the `startSet` and recursively
+ applying the `operator` function to each `item`. The *attrsets* in the
+ `startSet` and the *attrsets* produced by `operator` must contain a value
+ named `key` which is comparable. The result is produced by calling `operator`
+ for each `item` with a value for `key` that has not been called yet including
+ newly produced `item`s. The function terminates when no new `item`s are
+ produced. The resulting *list of attrsets* contains only *attrsets* with a
+ unique key. For example,
```
builtins.genericClosure {
@@ -768,7 +768,7 @@ static RegisterPrimOp primop_abort({
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.abort").toOwned();
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
@@ -787,7 +787,7 @@ static RegisterPrimOp primop_throw({
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtin.throw").toOwned();
state.debugThrowLastTrace(ThrownError(s));
@@ -800,7 +800,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
state.forceValue(*args[1], pos);
v = *args[1];
} catch (Error & e) {
- PathSet context;
+ NixStringContext context;
auto message = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext",
false, false).toOwned();
@@ -1086,13 +1086,13 @@ drvName, Bindings * attrs, Value & v)
Derivation drv;
drv.name = drvName;
- PathSet context;
+ NixStringContext context;
bool contentAddressed = false;
bool isImpure = false;
std::optional<std::string> outputHash;
std::string outputHashAlgo;
- std::optional<FileIngestionMethod> ingestionMethod;
+ std::optional<ContentAddressMethod> ingestionMethod;
StringSet outputs;
outputs.insert("out");
@@ -1105,7 +1105,10 @@ drvName, Bindings * attrs, Value & v)
auto handleHashMode = [&](const std::string_view s) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
- else
+ else if (s == "text") {
+ experimentalFeatureSettings.require(Xp::DynamicDerivations);
+ ingestionMethod = TextIngestionMethod {};
+ } else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = state.positions[noPos]
@@ -1149,16 +1152,14 @@ drvName, Bindings * attrs, Value & v)
if (i->value->type() == nNull) continue;
}
- if (i->name == state.sContentAddressed) {
- contentAddressed = state.forceBool(*i->value, noPos, context_below);
- if (contentAddressed)
- experimentalFeatureSettings.require(Xp::CaDerivations);
+ if (i->name == state.sContentAddressed && state.forceBool(*i->value, noPos, context_below)) {
+ contentAddressed = true;
+ experimentalFeatureSettings.require(Xp::CaDerivations);
}
- else if (i->name == state.sImpure) {
- isImpure = state.forceBool(*i->value, noPos, context_below);
- if (isImpure)
- experimentalFeatureSettings.require(Xp::ImpureDerivations);
+ else if (i->name == state.sImpure && state.forceBool(*i->value, noPos, context_below)) {
+ isImpure = true;
+ experimentalFeatureSettings.require(Xp::ImpureDerivations);
}
/* The `args' attribute is special: it supplies the
@@ -1232,8 +1233,7 @@ drvName, Bindings * attrs, Value & v)
/* Everything in the context of the strings in the derivation
attributes should be added as dependencies of the resulting
derivation. */
- for (auto & c_ : context) {
- auto c = NixStringContextElem::parse(*state.store, c_);
+ for (auto & c : context) {
std::visit(overloaded {
/* Since this allows the builder to gain access to every
path in the dependency graph of the derivation (including
@@ -1273,11 +1273,16 @@ drvName, Bindings * attrs, Value & v)
}));
/* Check whether the derivation name is valid. */
- if (isDerivation(drvName))
+ if (isDerivation(drvName) &&
+ !(ingestionMethod == ContentAddressMethod { TextIngestionMethod { } } &&
+ outputs.size() == 1 &&
+ *(outputs.begin()) == "out"))
+ {
state.debugThrowLastTrace(EvalError({
- .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
+ .msg = hintfmt("derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension),
.errPos = state.positions[noPos]
}));
+ }
if (outputHash) {
/* Handle fixed-output derivations.
@@ -1293,21 +1298,15 @@ drvName, Bindings * attrs, Value & v)
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
- auto outPath = state.store->makeFixedOutputPath(drvName, FixedOutputInfo {
- .hash = {
- .method = method,
- .hash = h,
- },
- .references = {},
- });
- drv.env["out"] = state.store->printStorePath(outPath);
- drv.outputs.insert_or_assign("out",
- DerivationOutput::CAFixed {
- .hash = FixedOutputHash {
- .method = method,
- .hash = std::move(h),
- },
- });
+
+ DerivationOutput::CAFixed dof {
+ .ca = ContentAddress::fromParts(
+ std::move(method),
+ std::move(h)),
+ };
+
+ drv.env["out"] = state.store->printStorePath(dof.path(*state.store, drvName, "out"));
+ drv.outputs.insert_or_assign("out", std::move(dof));
}
else if (contentAddressed || isImpure) {
@@ -1325,13 +1324,13 @@ drvName, Bindings * attrs, Value & v)
if (isImpure)
drv.outputs.insert_or_assign(i,
DerivationOutput::Impure {
- .method = method,
+ .method = method.raw,
.hashType = ht,
});
else
drv.outputs.insert_or_assign(i,
DerivationOutput::CAFloating {
- .method = method,
+ .method = method.raw,
.hashType = ht,
});
}
@@ -1392,9 +1391,11 @@ drvName, Bindings * attrs, Value & v)
}
auto result = state.buildBindings(1 + drv.outputs.size());
- result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
+ result.alloc(state.sDrvPath).mkString(drvPathS, {
+ NixStringContextElem::DrvDeep { .drvPath = drvPath },
+ });
for (auto & i : drv.outputs)
- mkOutputString(state, result, drvPath, drv, i);
+ mkOutputString(state, result, drvPath, i);
v.mkAttrs(result);
}
@@ -1437,9 +1438,9 @@ static RegisterPrimOp primop_placeholder({
/* Convert the argument to a path. !!! obsolete? */
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
- Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
- v.mkString(canonPath(path), context);
+ NixStringContext context;
+ auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
+ v.mkString(path.path.abs(), context);
}
static RegisterPrimOp primop_toPath({
@@ -1468,22 +1469,23 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
.errPos = state.positions[pos]
}));
- PathSet context;
- Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
+ NixStringContext context;
+ auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
- if (!state.store->isStorePath(path)) path = canonPath(path, true);
- if (!state.store->isInStore(path))
+ if (!state.store->isStorePath(path.abs()))
+ path = CanonPath(canonPath(path.abs(), true));
+ if (!state.store->isInStore(path.abs()))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
- auto path2 = state.store->toStorePath(path).first;
+ auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
- context.insert(state.store->printStorePath(path2));
- v.mkString(path, context);
+ context.insert(NixStringContextElem::Opaque { .path = path2 });
+ v.mkString(path.abs(), context);
}
static RegisterPrimOp primop_storePath({
@@ -1499,7 +1501,7 @@ static RegisterPrimOp primop_storePath({
causes the path to be *copied* again to the Nix store, resulting
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
- This function is not available in pure evaluation mode.
+ Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
)",
.fun = prim_storePath,
});
@@ -1514,7 +1516,7 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try {
- v.mkBool(pathExists(state.checkSourcePath(path)));
+ v.mkBool(state.checkSourcePath(path).pathExists());
} catch (SysError & e) {
/* Don't give away info from errors while canonicalising
‘path’ in restricted mode. */
@@ -1538,7 +1540,7 @@ static RegisterPrimOp primop_pathExists({
following the last slash. */
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.baseNameOf",
false, false)), context);
@@ -1560,12 +1562,18 @@ static RegisterPrimOp primop_baseNameOf({
of the argument. */
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
- auto path = state.coerceToString(pos, *args[0], context,
- "while evaluating the first argument passed to builtins.dirOf",
+ state.forceValue(*args[0], pos);
+ if (args[0]->type() == nPath) {
+ auto path = args[0]->path();
+ v.mkPath(path.path.isRoot() ? path : path.parent());
+ } else {
+ NixStringContext context;
+ auto path = state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to 'builtins.dirOf'",
false, false);
- auto dir = dirOf(*path);
- if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
+ auto dir = dirOf(*path);
+ v.mkString(dir, context);
+ }
}
static RegisterPrimOp primop_dirOf({
@@ -1583,13 +1591,13 @@ static RegisterPrimOp primop_dirOf({
static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
- auto s = readFile(path);
+ auto s = path.readFile();
if (s.find((char) 0) != std::string::npos)
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs;
- if (state.store->isInStore(path)) {
+ if (state.store->isInStore(path.path.abs())) {
try {
- refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references;
+ refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references;
} catch (Error &) { // FIXME: should be InvalidPathError
}
// Re-scan references to filter down to just the ones that actually occur in the file.
@@ -1597,7 +1605,12 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
refsSink << s;
refs = refsSink.getResultPaths();
}
- auto context = state.store->printStorePathSet(refs);
+ NixStringContext context;
+ for (auto && p : std::move(refs)) {
+ context.insert(NixStringContextElem::Opaque {
+ .path = std::move((StorePath &&)p),
+ });
+ }
v.mkString(s, context);
}
@@ -1628,7 +1641,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
- PathSet context;
+ NixStringContext context;
auto path = state.coerceToString(pos, *i->value, context,
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
false, false).toOwned();
@@ -1670,7 +1683,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[1]);
- v.mkString(hashFile(*ht, path).to_string(Base16, false));
+ v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
}
static RegisterPrimOp primop_hashFile({
@@ -1684,26 +1697,20 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile,
});
-
-/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
-static const char * dirEntTypeToString(unsigned char dtType)
+static std::string_view fileTypeToString(InputAccessor::Type type)
{
- /* Enum DT_(DIR|LNK|REG|UNKNOWN) */
- switch(dtType) {
- case DT_REG: return "regular"; break;
- case DT_DIR: return "directory"; break;
- case DT_LNK: return "symlink"; break;
- default: return "unknown"; break;
- }
- return "unknown"; /* Unreachable */
+ return
+ type == InputAccessor::Type::tRegular ? "regular" :
+ type == InputAccessor::Type::tDirectory ? "directory" :
+ type == InputAccessor::Type::tSymlink ? "symlink" :
+ "unknown";
}
-
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
/* Retrieve the directory entry type and stringize it. */
- v.mkString(dirEntTypeToString(getFileType(path)));
+ v.mkString(fileTypeToString(path.lstat().type));
}
static RegisterPrimOp primop_readFileType({
@@ -1724,8 +1731,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// Retrieve directory entries for all nodes in a directory.
// This is similar to `getFileType` but is optimized to reduce system calls
// on many systems.
- DirEntries entries = readDirectory(path);
-
+ auto entries = path.readDirectory();
auto attrs = state.buildBindings(entries.size());
// If we hit unknown directory entry types we may need to fallback to
@@ -1734,22 +1740,21 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// `builtins.readFileType` application.
Value * readFileType = nullptr;
- for (auto & ent : entries) {
- auto & attr = attrs.alloc(ent.name);
- if (ent.type == DT_UNKNOWN) {
+ for (auto & [name, type] : entries) {
+ auto & attr = attrs.alloc(name);
+ if (!type) {
// Some filesystems or operating systems may not be able to return
// detailed node info quickly in this case we produce a thunk to
// query the file type lazily.
auto epath = state.allocValue();
- Path path2 = path + "/" + ent.name;
- epath->mkString(path2);
+ epath->mkPath(path + name);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath);
} else {
// This branch of the conditional is much more likely.
// Here we just stringize the directory entry type.
- attr.mkString(dirEntTypeToString(ent.type));
+ attr.mkString(fileTypeToString(*type));
}
}
@@ -1787,7 +1792,7 @@ static RegisterPrimOp primop_readDir({
static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::ostringstream out;
- PathSet context;
+ NixStringContext context;
printValueAsXML(state, true, false, *args[0], out, context, pos);
v.mkString(out.str(), context);
}
@@ -1895,7 +1900,7 @@ static RegisterPrimOp primop_toXML({
static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::ostringstream out;
- PathSet context;
+ NixStringContext context;
printValueAsJSON(state, true, *args[0], pos, out, context);
v.mkString(out.str(), context);
}
@@ -1945,22 +1950,23 @@ static RegisterPrimOp primop_fromJSON({
as an input by derivations. */
static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.toFile"));
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
StorePathSet refs;
- for (auto path : context) {
- if (path.at(0) != '/')
+ for (auto c : context) {
+ if (auto p = std::get_if<NixStringContextElem::Opaque>(&c))
+ refs.insert(p->path);
+ else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt(
"in 'toFile': the file named '%1%' must not contain a reference "
"to a derivation but contains (%2%)",
- name, path),
+ name, c.to_string()),
.errPos = state.positions[pos]
}));
- refs.insert(state.store->parseStorePath(path));
}
auto storePath = settings.readOnlyMode
@@ -2055,13 +2061,13 @@ static RegisterPrimOp primop_toFile({
static void addPath(
EvalState & state,
const PosIdx pos,
- const std::string & name,
+ std::string_view name,
Path path,
Value * filterFun,
FileIngestionMethod method,
const std::optional<Hash> expectedHash,
Value & v,
- const PathSet & context)
+ const NixStringContext & context)
{
try {
// FIXME: handle CA derivation outputs (where path needs to
@@ -2083,7 +2089,7 @@ static void addPath(
path = evalSettings.pureEval && expectedHash
? path
- : state.checkSourcePath(path);
+ : state.checkSourcePath(CanonPath(path)).path.abs();
PathFilter filter = filterFun ? ([&](const Path & path) {
auto st = lstat(path);
@@ -2135,10 +2141,11 @@ static void addPath(
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
- Path path = state.coerceToPath(pos, *args[1], context, "while evaluating the second argument (the path to filter) passed to builtins.filterSource");
+ NixStringContext context;
+ auto path = state.coerceToPath(pos, *args[1], context,
+ "while evaluating the second argument (the path to filter) passed to builtins.filterSource");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
- addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
+ addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
static RegisterPrimOp primop_filterSource({
@@ -2198,18 +2205,19 @@ static RegisterPrimOp primop_filterSource({
static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.path");
- Path path;
+ std::optional<SourcePath> path;
std::string name;
Value * filterFun = nullptr;
auto method = FileIngestionMethod::Recursive;
std::optional<Hash> expectedHash;
- PathSet context;
+ NixStringContext context;
+
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
for (auto & attr : *args[0]->attrs) {
auto n = state.symbols[attr.name];
if (n == "path")
- path = state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the `path` attribute passed to builtins.path");
+ path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
else if (attr.name == state.sName)
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
else if (n == "filter")
@@ -2224,15 +2232,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
.errPos = state.positions[attr.pos]
}));
}
- if (path.empty())
+ if (!path)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
.errPos = state.positions[pos]
}));
if (name.empty())
- name = baseNameOf(path);
+ name = path->baseName();
- addPath(state, pos, name, path, filterFun, method, expectedHash, v, context);
+ addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
}
static RegisterPrimOp primop_path({
@@ -3538,7 +3546,7 @@ static RegisterPrimOp primop_lessThan({
`"/nix/store/whatever..."'. */
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.toString",
true, false);
@@ -3577,7 +3585,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
{
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
- PathSet context;
+ NixStringContext context;
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
if (start < 0)
@@ -3611,7 +3619,7 @@ static RegisterPrimOp primop_substring({
static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
v.mkInt(s->size());
}
@@ -3637,7 +3645,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
.errPos = state.positions[pos]
}));
- PathSet context; // discarded
+ NixStringContext context; // discarded
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
v.mkString(hashString(*ht, s).to_string(Base16, false));
@@ -3683,7 +3691,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto regex = state.regexCache->get(re);
- PathSet context;
+ NixStringContext context;
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match");
std::cmatch match;
@@ -3763,7 +3771,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto regex = state.regexCache->get(re);
- PathSet context;
+ NixStringContext context;
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split");
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
@@ -3860,7 +3868,7 @@ static RegisterPrimOp primop_split({
static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- PathSet context;
+ NixStringContext context;
auto sep = state.forceString(*args[0], context, pos, "while evaluating the first argument (the separator string) passed to builtins.concatStringsSep");
state.forceList(*args[1], pos, "while evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep");
@@ -3900,15 +3908,10 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
for (auto elem : args[0]->listItems())
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
- std::vector<std::pair<std::string, PathSet>> to;
- to.reserve(args[1]->listSize());
- for (auto elem : args[1]->listItems()) {
- PathSet ctx;
- auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
- to.emplace_back(s, std::move(ctx));
- }
+ std::unordered_map<size_t, std::string> cache;
+ auto to = args[1]->listItems();
- PathSet context;
+ NixStringContext context;
auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
std::string res;
@@ -3917,10 +3920,19 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
bool found = false;
auto i = from.begin();
auto j = to.begin();
- for (; i != from.end(); ++i, ++j)
+ size_t j_index = 0;
+ for (; i != from.end(); ++i, ++j, ++j_index)
if (s.compare(p, i->size(), *i) == 0) {
found = true;
- res += j->first;
+ auto v = cache.find(j_index);
+ if (v == cache.end()) {
+ NixStringContext ctx;
+ auto ts = state.forceString(**j, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
+ v = (cache.emplace(j_index, ts)).first;
+ for (auto& path : ctx)
+ context.insert(path);
+ }
+ res += v->second;
if (i->empty()) {
if (p < s.size())
res += s[p];
@@ -3928,9 +3940,6 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
} else {
p += i->size();
}
- for (auto& path : j->second)
- context.insert(path);
- j->second.clear();
break;
}
if (!found) {
@@ -3948,7 +3957,11 @@ static RegisterPrimOp primop_replaceStrings({
.args = {"from", "to", "s"},
.doc = R"(
Given string *s*, replace every occurrence of the strings in *from*
- with the corresponding string in *to*. For example,
+ with the corresponding string in *to*.
+
+ The argument *to* is lazy, that is, it is only evaluated when its corresponding pattern in *from* is matched in the string *s*
+
+ Example:
```nix
builtins.replaceStrings ["oo" "a"] ["a" "i"] "foobar"
@@ -4150,7 +4163,6 @@ void EvalState::createBaseEnv()
/* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */
- sDerivationNix = symbols.create(derivationNixPath);
auto vDerivation = allocValue();
addConstant("derivation", vDerivation);
@@ -4167,7 +4179,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
- eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
+ eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
}