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.cc358
1 files changed, 283 insertions, 75 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 6fbd66389..8a61e57cc 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -1,11 +1,12 @@
#include "archive.hh"
#include "derivations.hh"
+#include "downstream-placeholder.hh"
#include "eval-inline.hh"
#include "eval.hh"
#include "globals.hh"
#include "json-to-value.hh"
#include "names.hh"
-#include "references.hh"
+#include "path-references.hh"
#include "store-api.hh"
#include "util.hh"
#include "value-to-json.hh"
@@ -87,7 +88,7 @@ StringMap EvalState::realiseContext(const NixStringContext & 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)
);
}
@@ -237,7 +238,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
}
}
-static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info {
+static RegisterPrimOp primop_scopedImport(PrimOp {
.name = "scopedImport",
.arity = 2,
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -691,7 +692,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
v.listElems()[n++] = i;
}
-static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
+static RegisterPrimOp primop_genericClosure(PrimOp {
.name = "__genericClosure",
.args = {"attrset"},
.arity = 1,
@@ -808,7 +809,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
}
}
-static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info {
+static RegisterPrimOp primop_addErrorContext(PrimOp {
.name = "__addErrorContext",
.arity = 2,
.fun = prim_addErrorContext,
@@ -1151,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
@@ -1401,7 +1400,7 @@ drvName, Bindings * attrs, Value & v)
v.mkAttrs(result);
}
-static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
+static RegisterPrimOp primop_derivationStrict(PrimOp {
.name = "derivationStrict",
.arity = 1,
.fun = prim_derivationStrict,
@@ -1502,7 +1501,9 @@ 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).
+
+ See also [`builtins.fetchClosure`](#builtins-fetchClosure).
)",
.fun = prim_storePath,
});
@@ -1657,7 +1658,10 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
}));
}
- searchPath.emplace_back(prefix, path);
+ searchPath.elements.emplace_back(SearchPath::Elem {
+ .prefix = SearchPath::Prefix { .s = prefix },
+ .path = SearchPath::Path { .s = path },
+ });
}
auto path = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
@@ -1665,9 +1669,52 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
}
-static RegisterPrimOp primop_findFile(RegisterPrimOp::Info {
+static RegisterPrimOp primop_findFile(PrimOp {
.name = "__findFile",
- .arity = 2,
+ .args = {"search path", "lookup path"},
+ .doc = R"(
+ Look up the given path with the given search path.
+
+ A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes, `prefix`, and `path`.
+ `prefix` is a relative path.
+ `path` denotes a file system location; the exact syntax depends on the command line interface.
+
+ Examples of search path attribute sets:
+
+ - ```
+ {
+ prefix = "nixos-config";
+ path = "/etc/nixos/configuration.nix";
+ }
+ ```
+
+ - ```
+ {
+ prefix = "";
+ path = "/nix/var/nix/profiles/per-user/root/channels";
+ }
+ ```
+
+ The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match.
+
+ This is the process for each entry:
+ If the lookup path matches `prefix`, then the remainder of the lookup path (the "suffix") is searched for within the directory denoted by `patch`.
+ Note that the `path` may need to be downloaded at this point to look inside.
+ If the suffix is found inside that directory, then the entry is a match;
+ the combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
+
+ The syntax
+
+ ```nix
+ <nixpkgs>
+ ```
+
+ is equivalent to:
+
+ ```nix
+ builtins.findFile builtins.nixPath "nixpkgs"
+ ```
+ )",
.fun = prim_findFile,
});
@@ -2386,7 +2433,7 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * *
state.mkPos(v, i->pos);
}
-static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info {
+static RegisterPrimOp primop_unsafeGetAttrPos(PrimOp {
.name = "__unsafeGetAttrPos",
.arity = 2,
.fun = prim_unsafeGetAttrPos,
@@ -3909,13 +3956,8 @@ 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, NixStringContext>> to;
- to.reserve(args[1]->listSize());
- for (auto elem : args[1]->listItems()) {
- NixStringContext 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();
NixStringContext context;
auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
@@ -3926,10 +3968,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];
@@ -3937,9 +3988,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) {
@@ -3957,7 +4005,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"
@@ -4054,22 +4106,10 @@ static RegisterPrimOp primop_splitVersion({
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
-RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
-{
- if (!primOps) primOps = new PrimOps;
- primOps->push_back({
- .name = name,
- .args = {},
- .arity = arity,
- .fun = fun,
- });
-}
-
-
-RegisterPrimOp::RegisterPrimOp(Info && info)
+RegisterPrimOp::RegisterPrimOp(PrimOp && primOp)
{
if (!primOps) primOps = new PrimOps;
- primOps->push_back(std::move(info));
+ primOps->push_back(std::move(primOp));
}
@@ -4082,85 +4122,253 @@ void EvalState::createBaseEnv()
/* `builtins' must be first! */
v.mkAttrs(buildBindings(128).finish());
- addConstant("builtins", v);
+ addConstant("builtins", v, {
+ .type = nAttrs,
+ .doc = R"(
+ Contains all the [built-in functions](@docroot@/language/builtins.md) and values.
+
+ Since built-in functions were added over time, [testing for attributes](./operators.md#has-attribute) in `builtins` can be used for graceful fallback on older Nix installations:
+
+ ```nix
+ # if hasContext is not available, we assume `s` has a context
+ if builtins ? hasContext then builtins.hasContext s else true
+ ```
+ )",
+ });
v.mkBool(true);
- addConstant("true", v);
+ addConstant("true", v, {
+ .type = nBool,
+ .doc = R"(
+ Primitive value.
+
+ It can be returned by
+ [comparison operators](@docroot@/language/operators.md#Comparison)
+ and used in
+ [conditional expressions](@docroot@/language/constructs.md#Conditionals).
+
+ The name `true` is not special, and can be shadowed:
+
+ ```nix-repl
+ nix-repl> let true = 1; in true
+ 1
+ ```
+ )",
+ });
v.mkBool(false);
- addConstant("false", v);
+ addConstant("false", v, {
+ .type = nBool,
+ .doc = R"(
+ Primitive value.
+
+ It can be returned by
+ [comparison operators](@docroot@/language/operators.md#Comparison)
+ and used in
+ [conditional expressions](@docroot@/language/constructs.md#Conditionals).
+
+ The name `false` is not special, and can be shadowed:
+
+ ```nix-repl
+ nix-repl> let false = 1; in false
+ 1
+ ```
+ )",
+ });
v.mkNull();
- addConstant("null", v);
+ addConstant("null", v, {
+ .type = nNull,
+ .doc = R"(
+ Primitive value.
+
+ The name `null` is not special, and can be shadowed:
+
+ ```nix-repl
+ nix-repl> let null = 1; in null
+ 1
+ ```
+ )",
+ });
if (!evalSettings.pureEval) {
v.mkInt(time(0));
- addConstant("__currentTime", v);
+ }
+ addConstant("__currentTime", v, {
+ .type = nInt,
+ .doc = R"(
+ Return the [Unix time](https://en.wikipedia.org/wiki/Unix_time) at first evaluation.
+ Repeated references to that name will re-use the initially obtained value.
+
+ Example:
+
+ ```console
+ $ nix repl
+ Welcome to Nix 2.15.1 Type :? for help.
+ nix-repl> builtins.currentTime
+ 1683705525
+
+ nix-repl> builtins.currentTime
+ 1683705525
+ ```
+
+ The [store path](@docroot@/glossary.md#gloss-store-path) of a derivation depending on `currentTime` will differ for each evaluation, unless both evaluate `builtins.currentTime` in the same second.
+ )",
+ .impureOnly = true,
+ });
+
+ if (!evalSettings.pureEval) {
v.mkString(settings.thisSystem.get());
- addConstant("__currentSystem", v);
}
+ addConstant("__currentSystem", v, {
+ .type = nString,
+ .doc = R"(
+ The value of the [`system` configuration option](@docroot@/command-ref/conf-file.md#conf-pure-eval).
+
+ It can be used to set the `system` attribute for [`builtins.derivation`](@docroot@/language/derivations.md) such that the resulting derivation can be built on the same system that evaluates the Nix expression:
+
+ ```nix
+ builtins.derivation {
+ # ...
+ system = builtins.currentSystem;
+ }
+ ```
+
+ It can be overridden in order to create derivations for different system than the current one:
+
+ ```console
+ $ nix-instantiate --system "mips64-linux" --eval --expr 'builtins.currentSystem'
+ "mips64-linux"
+ ```
+ )",
+ .impureOnly = true,
+ });
v.mkString(nixVersion);
- addConstant("__nixVersion", v);
+ addConstant("__nixVersion", v, {
+ .type = nString,
+ .doc = R"(
+ The version of Nix.
+
+ For example, where the command line returns the current Nix version,
+
+ ```shell-session
+ $ nix --version
+ nix (Nix) 2.16.0
+ ```
+
+ the Nix language evaluator returns the same value:
+
+ ```nix-repl
+ nix-repl> builtins.nixVersion
+ "2.16.0"
+ ```
+ )",
+ });
v.mkString(store->storeDir);
- addConstant("__storeDir", v);
+ addConstant("__storeDir", v, {
+ .type = nString,
+ .doc = R"(
+ Logical file system location of the [Nix store](@docroot@/glossary.md#gloss-store) currently in use.
+
+ This value is determined by the `store` parameter in [Store URLs](@docroot@/command-ref/new-cli/nix3-help-stores.md):
+
+ ```shell-session
+ $ nix-instantiate --store 'dummy://?store=/blah' --eval --expr builtins.storeDir
+ "/blah"
+ ```
+ )",
+ });
/* Language version. This should be increased every time a new
language feature gets added. It's not necessary to increase it
when primops get added, because you can just use `builtins ?
primOp' to check. */
v.mkInt(6);
- addConstant("__langVersion", v);
+ addConstant("__langVersion", v, {
+ .type = nInt,
+ .doc = R"(
+ The current version of the Nix language.
+ )",
+ });
// Miscellaneous
if (evalSettings.enableNativeCode) {
- addPrimOp("__importNative", 2, prim_importNative);
- addPrimOp("__exec", 1, prim_exec);
+ addPrimOp({
+ .name = "__importNative",
+ .arity = 2,
+ .fun = prim_importNative,
+ });
+ addPrimOp({
+ .name = "__exec",
+ .arity = 1,
+ .fun = prim_exec,
+ });
}
addPrimOp({
- .fun = evalSettings.traceVerbose ? prim_trace : prim_second,
- .arity = 2,
.name = "__traceVerbose",
.args = { "e1", "e2" },
+ .arity = 2,
.doc = R"(
Evaluate *e1* and print its abstract syntax representation on standard
error if `--trace-verbose` is enabled. Then return *e2*. This function
is useful for debugging.
)",
+ .fun = evalSettings.traceVerbose ? prim_trace : prim_second,
});
/* Add a value containing the current Nix expression search path. */
- mkList(v, searchPath.size());
+ mkList(v, searchPath.elements.size());
int n = 0;
- for (auto & i : searchPath) {
+ for (auto & i : searchPath.elements) {
auto attrs = buildBindings(2);
- attrs.alloc("path").mkString(i.second);
- attrs.alloc("prefix").mkString(i.first);
+ attrs.alloc("path").mkString(i.path.s);
+ attrs.alloc("prefix").mkString(i.prefix.s);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs);
}
- addConstant("__nixPath", v);
+ addConstant("__nixPath", v, {
+ .type = nList,
+ .doc = R"(
+ The search path used to resolve angle bracket path lookups.
+
+ Angle bracket expressions can be
+ [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar)
+ using this and
+ [`builtins.findFile`](./builtins.html#builtins-findFile):
+
+ ```nix
+ <nixpkgs>
+ ```
+
+ is equivalent to:
+
+ ```nix
+ builtins.findFile builtins.nixPath "nixpkgs"
+ ```
+ )",
+ });
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
- if (!primOp.experimentalFeature
- || experimentalFeatureSettings.isEnabled(*primOp.experimentalFeature))
+ if (experimentalFeatureSettings.isEnabled(primOp.experimentalFeature))
{
- addPrimOp({
- .fun = primOp.fun,
- .arity = std::max(primOp.args.size(), primOp.arity),
- .name = primOp.name,
- .args = primOp.args,
- .doc = primOp.doc,
- });
+ auto primOpAdjusted = primOp;
+ primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
+ addPrimOp(std::move(primOpAdjusted));
}
/* Add a wrapper around the derivation primop that computes the
- `drvPath' and `outPath' attributes lazily. */
+ `drvPath' and `outPath' attributes lazily.
+
+ Null docs because it is documented separately.
+ */
auto vDerivation = allocValue();
- addConstant("derivation", vDerivation);
+ addConstant("derivation", vDerivation, {
+ .type = nFunction,
+ });
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */