diff options
Diffstat (limited to 'third_party/nix/src/libexpr/primops.cc')
-rw-r--r-- | third_party/nix/src/libexpr/primops.cc | 2335 |
1 files changed, 2335 insertions, 0 deletions
diff --git a/third_party/nix/src/libexpr/primops.cc b/third_party/nix/src/libexpr/primops.cc new file mode 100644 index 000000000000..f196c5ed723c --- /dev/null +++ b/third_party/nix/src/libexpr/primops.cc @@ -0,0 +1,2335 @@ +#include "libexpr/primops.hh" + +#include <algorithm> +#include <cstring> +#include <iostream> +#include <regex> + +#include <absl/strings/str_split.h> +#include <glog/logging.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "libexpr/eval-inline.hh" +#include "libexpr/eval.hh" +#include "libexpr/json-to-value.hh" +#include "libexpr/names.hh" +#include "libexpr/value-to-json.hh" +#include "libexpr/value-to-xml.hh" +#include "libstore/derivations.hh" +#include "libstore/download.hh" +#include "libstore/globals.hh" +#include "libstore/store-api.hh" +#include "libutil/archive.hh" +#include "libutil/json.hh" +#include "libutil/status.hh" +#include "libutil/util.hh" + +namespace nix { + +/************************************************************* + * Miscellaneous + *************************************************************/ + +/* Decode a context string ‘!<name>!<path>’ into a pair <path, + name>. */ +std::pair<std::string, std::string> decodeContext(const std::string& s) { + if (s.at(0) == '!') { + size_t index = s.find('!', 1); + return std::pair<std::string, std::string>(std::string(s, index + 1), + std::string(s, 1, index - 1)); + } + return std::pair<std::string, std::string>( + s.at(0) == '/' ? s : std::string(s, 1), ""); +} + +InvalidPathError::InvalidPathError(const Path& path) + : EvalError(format("path '%1%' is not valid") % path), path(path) {} + +void EvalState::realiseContext(const PathSet& context) { + PathSet drvs; + + for (auto& i : context) { + std::pair<std::string, std::string> decoded = decodeContext(i); + Path ctx = decoded.first; + assert(store->isStorePath(ctx)); + if (!store->isValidPath(ctx)) { + throw InvalidPathError(ctx); + } + if (!decoded.second.empty() && nix::isDerivation(ctx)) { + drvs.insert(decoded.first + "!" + decoded.second); + + /* Add the output of this derivation to the allowed + paths. */ + if (allowedPaths) { + auto drv = store->derivationFromPath(decoded.first); + auto i = drv.outputs.find(decoded.second); + if (i == drv.outputs.end()) { + throw Error("derivation '%s' does not have an output named '%s'", + decoded.first, decoded.second); + } + allowedPaths->insert(i->second.path); + } + } + } + + if (drvs.empty()) { + return; + } + + if (!evalSettings.enableImportFromDerivation) { + throw EvalError(format("attempted to realize '%1%' during evaluation but " + "'allow-import-from-derivation' is false") % + *(drvs.begin())); + } + + /* For performance, prefetch all substitute info. */ + PathSet willBuild; + PathSet willSubstitute; + PathSet unknown; + unsigned long long downloadSize; + unsigned long long narSize; + store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, + narSize); + + nix::util::OkOrThrow(store->buildPaths(std::cerr, drvs)); +} + +/* Load and evaluate an expression from path specified by the + argument. */ +static void prim_scopedImport(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[1], context); + + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot import '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + Path realPath = state.checkSourcePath(state.toRealPath(path, context)); + + if (state.store->isStorePath(path) && state.store->isValidPath(path) && + isDerivation(path)) { + Derivation drv = readDerivation(realPath); + Value& w = *state.allocValue(); + state.mkAttrs(w, 3 + drv.outputs.size()); + Value* v2 = state.allocAttr(w, state.sDrvPath); + mkString(*v2, path, {"=" + path}); + v2 = state.allocAttr(w, state.sName); + mkString(*v2, drv.env["name"]); + Value* outputsVal = state.allocAttr(w, state.symbols.Create("outputs")); + state.mkList(*outputsVal, drv.outputs.size()); + unsigned int outputs_index = 0; + + for (const auto& o : drv.outputs) { + v2 = state.allocAttr(w, state.symbols.Create(o.first)); + mkString(*v2, o.second.path, {"!" + o.first + "!" + path}); + (*outputsVal->list)[outputs_index] = state.allocValue(); + mkString(*((*outputsVal->list)[outputs_index++]), o.first); + } + + Value fun; + state.evalFile( + settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", + fun); + state.forceFunction(fun, pos); + mkApp(v, fun, w); + state.forceAttrs(v, pos); + } else { + state.forceAttrs(*args[0]); + if (args[0]->attrs->empty()) { + state.evalFile(realPath, v); + } else { + Env* env = &state.allocEnv(args[0]->attrs->size()); + env->up = &state.baseEnv; + + StaticEnv staticEnv(false, &state.staticBaseEnv); + + unsigned int displ = 0; + for (auto& attr : *args[0]->attrs) { + staticEnv.vars[attr.second.name] = displ; + env->values[displ++] = attr.second.value; + } + + DLOG(INFO) << "evaluating file '" << realPath << "'"; + Expr* e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv); + + e->eval(state, *env, v); + } + } +} + +/* Return a string representing the type of the expression. */ +static void prim_typeOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + std::string t; + switch (args[0]->type) { + case tInt: + t = "int"; + break; + case tBool: + t = "bool"; + break; + case tString: + t = "string"; + break; + case tPath: + t = "path"; + break; + case tNull: + t = "null"; + break; + case tAttrs: + t = "set"; + break; + case tList: + t = "list"; + break; + case tLambda: + case tPrimOp: + case tPrimOpApp: + t = "lambda"; + break; + case tFloat: + t = "float"; + break; + default: + abort(); + } + mkString(v, state.symbols.Create(t)); +} + +/* Determine whether the argument is the null value. */ +static void prim_isNull(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tNull); +} + +/* Determine whether the argument is a function. */ +static void prim_isFunction(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + bool res; + switch (args[0]->type) { + case tLambda: + case tPrimOp: + case tPrimOpApp: + res = true; + break; + default: + res = false; + break; + } + mkBool(v, res); +} + +/* Determine whether the argument is an integer. */ +static void prim_isInt(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tInt); +} + +/* Determine whether the argument is a float. */ +static void prim_isFloat(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tFloat); +} + +/* Determine whether the argument is a string. */ +static void prim_isString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tString); +} + +/* Determine whether the argument is a Boolean. */ +static void prim_isBool(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tBool); +} + +/* Determine whether the argument is a path. */ +static void prim_isPath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tPath); +} + +struct CompareValues { + bool operator()(const Value* v1, const Value* v2) const { + if (v1->type == tFloat && v2->type == tInt) { + return v1->fpoint < v2->integer; + } + if (v1->type == tInt && v2->type == tFloat) { + return v1->integer < v2->fpoint; + } + if (v1->type != v2->type) { + throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % + showType(*v2)); + } + switch (v1->type) { + case tInt: + return v1->integer < v2->integer; + case tFloat: + return v1->fpoint < v2->fpoint; + case tString: + return strcmp(v1->string.s, v2->string.s) < 0; + case tPath: + return strcmp(v1->path, v2->path) < 0; + default: + throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % + showType(*v2)); + } + } +}; + +typedef std::list<Value*> ValueList; + +static void prim_genericClosure(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + + /* Get the start set. */ + Bindings::iterator startSet = + args[0]->attrs->find(state.symbols.Create("startSet")); + if (startSet == args[0]->attrs->end()) { + throw EvalError(format("attribute 'startSet' required, at %1%") % pos); + } + state.forceList(*startSet->second.value, pos); + + ValueList workSet; + for (Value* elem : *startSet->second.value->list) { + workSet.push_back(elem); + } + + /* Get the operator. */ + Bindings::iterator op = + args[0]->attrs->find(state.symbols.Create("operator")); + if (op == args[0]->attrs->end()) { + throw EvalError(format("attribute 'operator' required, at %1%") % pos); + } + state.forceValue(*op->second.value); + + /* Construct the closure by applying the operator to element of + `workSet', adding the result to `workSet', continuing until + no new elements are found. */ + ValueList res; + // `doneKeys' doesn't need to be a GC root, because its values are + // reachable from res. + std::set<Value*, CompareValues> doneKeys; + while (!workSet.empty()) { + Value* e = *(workSet.begin()); + workSet.pop_front(); + + state.forceAttrs(*e, pos); + + Bindings::iterator key = e->attrs->find(state.symbols.Create("key")); + if (key == e->attrs->end()) { + throw EvalError(format("attribute 'key' required, at %1%") % pos); + } + state.forceValue(*key->second.value); + + if (doneKeys.find(key->second.value) != doneKeys.end()) { + continue; + } + doneKeys.insert(key->second.value); + res.push_back(e); + + /* Call the `operator' function with `e' as argument. */ + Value call; + mkApp(call, *op->second.value, *e); + state.forceList(call, pos); + + /* Add the values returned by the operator to the work set. */ + for (unsigned int n = 0; n < call.listSize(); ++n) { + state.forceValue(*(*call.list)[n]); + workSet.push_back((*call.list)[n]); + } + } + + /* Create the result list. */ + state.mkList(v, res.size()); + unsigned int n = 0; + for (auto& i : res) { + (*v.list)[n++] = i; + } +} + +static void prim_abort(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + std::string s = state.coerceToString(pos, *args[0], context); + throw Abort( + format("evaluation aborted with the following error message: '%1%'") % s); +} + +static void prim_throw(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + std::string s = state.coerceToString(pos, *args[0], context); + throw ThrownError(s); +} + +static void prim_addErrorContext(EvalState& state, const Pos& pos, Value** args, + Value& v) { + try { + state.forceValue(*args[1]); + v = *args[1]; + } catch (Error& e) { + PathSet context; + e.addPrefix(format("%1%\n") % state.coerceToString(pos, *args[0], context)); + throw; + } +} + +/* Try evaluating the argument. Success => {success=true; value=something;}, + * else => {success=false; value=false;} */ +static void prim_tryEval(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.mkAttrs(v, 2); + try { + state.forceValue(*args[0]); + v.attrs->push_back(Attr(state.sValue, args[0])); + mkBool(*state.allocAttr(v, state.symbols.Create("success")), true); + } catch (AssertionError& e) { + mkBool(*state.allocAttr(v, state.sValue), false); + mkBool(*state.allocAttr(v, state.symbols.Create("success")), false); + } +} + +/* Return an environment variable. Use with care. */ +static void prim_getEnv(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string name = state.forceStringNoCtx(*args[0], pos); + mkString(v, evalSettings.restrictEval || evalSettings.pureEval + ? "" + : getEnv(name).value_or("")); +} + +/* Evaluate the first argument, then return the second argument. */ +static void prim_seq(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0]); + state.forceValue(*args[1]); + v = *args[1]; +} + +/* Evaluate the first argument deeply (i.e. recursing into lists and + attrsets), then return the second argument. */ +static void prim_deepSeq(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValueDeep(*args[0]); + state.forceValue(*args[1]); + v = *args[1]; +} + +/* Evaluate the first expression and print it on standard error. Then + return the second expression. Useful for debugging. */ +static void prim_trace(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + if (args[0]->type == tString) { + LOG(INFO) << "trace: " << args[0]->string.s; + } else { + LOG(INFO) << "trace: " << *args[0]; + } + state.forceValue(*args[1]); + v = *args[1]; +} + +void prim_valueSize(EvalState& state, const Pos& pos, Value** args, Value& v) { + /* We're not forcing the argument on purpose. */ + mkInt(v, valueSize(*args[0])); +} + +/************************************************************* + * Derivations + *************************************************************/ + +/* Construct (as a unobservable side effect) a Nix derivation + expression that performs the derivation described by the argument + set. Returns the original set extended with the following + attributes: `outPath' containing the primary output path of the + derivation; `drvPath' containing the path of the Nix expression; + and `type' set to `derivation' to indicate that this is a + derivation. */ +static void prim_derivationStrict(EvalState& state, const Pos& pos, + Value** args, Value& v) { + state.forceAttrs(*args[0], pos); + + /* Figure out the name first (for stack backtraces). */ + Bindings::iterator attr = args[0]->attrs->find(state.sName); + if (attr == args[0]->attrs->end()) { + throw EvalError(format("required attribute 'name' missing, at %1%") % pos); + } + std::string drvName; + Pos& posDrvName(*attr->second.pos); + try { + drvName = state.forceStringNoCtx(*attr->second.value, pos); + } catch (Error& e) { + e.addPrefix( + format("while evaluating the derivation attribute 'name' at %1%:\n") % + posDrvName); + throw; + } + + /* Check whether attributes should be passed as a JSON file. */ + std::ostringstream jsonBuf; + std::unique_ptr<JSONObject> jsonObject; + attr = args[0]->attrs->find(state.sStructuredAttrs); + if (attr != args[0]->attrs->end() && + state.forceBool(*attr->second.value, pos)) { + jsonObject = std::make_unique<JSONObject>(jsonBuf); + } + + /* Check whether null attributes should be ignored. */ + bool ignoreNulls = false; + attr = args[0]->attrs->find(state.sIgnoreNulls); + if (attr != args[0]->attrs->end()) { + ignoreNulls = state.forceBool(*attr->second.value, pos); + } + + /* Build the derivation expression by processing the attributes. */ + Derivation drv; + + PathSet context; + + std::optional<std::string> outputHash; + std::string outputHashAlgo; + bool outputHashRecursive = false; + + StringSet outputs; + outputs.insert("out"); + + for (auto& [_, i] : *args[0]->attrs) { + if (i.name == state.sIgnoreNulls) { + continue; + } + const std::string& key = i.name; + + auto handleHashMode = [&](const std::string& s) { + if (s == "recursive") { + outputHashRecursive = true; + } else if (s == "flat") { + outputHashRecursive = false; + } else { + throw EvalError( + "invalid value '%s' for 'outputHashMode' attribute, at %s", s, + posDrvName); + } + }; + + auto handleOutputs = [&](const Strings& ss) { + outputs.clear(); + for (auto& j : ss) { + if (outputs.find(j) != outputs.end()) { + throw EvalError(format("duplicate derivation output '%1%', at %2%") % + j % posDrvName); + } + /* !!! Check whether j is a valid attribute + name. */ + /* Derivations cannot be named ‘drv’, because + then we'd have an attribute ‘drvPath’ in + the resulting set. */ + if (j == "drv") { + throw EvalError( + format("invalid derivation output name 'drv', at %1%") % + posDrvName); + } + outputs.insert(j); + } + if (outputs.empty()) { + throw EvalError( + format("derivation cannot have an empty set of outputs, at %1%") % + posDrvName); + } + }; + + try { + if (ignoreNulls) { + state.forceValue(*i.value); + if (i.value->type == tNull) { + continue; + } + } + + /* The `args' attribute is special: it supplies the + command-line arguments to the builder. */ + if (i.name == state.sArgs) { + state.forceList(*i.value, pos); + for (unsigned int n = 0; n < i.value->listSize(); ++n) { + std::string s = state.coerceToString(posDrvName, *(*i.value->list)[n], + context, true); + drv.args.push_back(s); + } + } + + /* All other attributes are passed to the builder through + the environment. */ + else { + if (jsonObject) { + if (i.name == state.sStructuredAttrs) { + continue; + } + + auto placeholder(jsonObject->placeholder(key)); + printValueAsJSON(state, true, *i.value, placeholder, context); + + if (i.name == state.sBuilder) { + drv.builder = state.forceString(*i.value, context, posDrvName); + } else if (i.name == state.sSystem) { + drv.platform = state.forceStringNoCtx(*i.value, posDrvName); + } else if (i.name == state.sOutputHash) { + outputHash = state.forceStringNoCtx(*i.value, posDrvName); + } else if (i.name == state.sOutputHashAlgo) { + outputHashAlgo = state.forceStringNoCtx(*i.value, posDrvName); + } else if (i.name == state.sOutputHashMode) { + handleHashMode(state.forceStringNoCtx(*i.value, posDrvName)); + } else if (i.name == state.sOutputs) { + /* Require ‘outputs’ to be a list of strings. */ + state.forceList(*i.value, posDrvName); + Strings ss; + for (unsigned int n = 0; n < i.value->listSize(); ++n) { + ss.emplace_back( + state.forceStringNoCtx(*(*i.value->list)[n], posDrvName)); + } + handleOutputs(ss); + } + + } else { + auto s = state.coerceToString(posDrvName, *i.value, context, true); + drv.env.emplace(key, s); + if (i.name == state.sBuilder) { + drv.builder = s; + } else if (i.name == state.sSystem) { + drv.platform = s; + } else if (i.name == state.sOutputHash) { + outputHash = s; + } else if (i.name == state.sOutputHashAlgo) { + outputHashAlgo = s; + } else if (i.name == state.sOutputHashMode) { + handleHashMode(s); + } else if (i.name == state.sOutputs) { + handleOutputs(absl::StrSplit(s, absl::ByAnyChar(" \t\n\r"), + absl::SkipEmpty())); + } + } + } + + } catch (Error& e) { + e.addPrefix(format("while evaluating the attribute '%1%' of the " + "derivation '%2%' at %3%:\n") % + key % drvName % posDrvName); + throw; + } + } + + if (jsonObject) { + jsonObject.reset(); + drv.env.emplace("__json", jsonBuf.str()); + } + + /* Everything in the context of the strings in the derivation + attributes should be added as dependencies of the resulting + derivation. */ + for (auto& path : context) { + /* Paths marked with `=' denote that the path of a derivation + is explicitly passed to the builder. Since that allows the + builder to gain access to every path in the dependency + graph of the derivation (including all outputs), all paths + in the graph must be added to this derivation's list of + inputs to ensure that they are available when the builder + runs. */ + if (path.at(0) == '=') { + /* !!! This doesn't work if readOnlyMode is set. */ + PathSet refs; + state.store->computeFSClosure(std::string(path, 1), refs); + for (auto& j : refs) { + drv.inputSrcs.insert(j); + if (isDerivation(j)) { + drv.inputDrvs[j] = state.store->queryDerivationOutputNames(j); + } + } + } + + /* Handle derivation outputs of the form ‘!<name>!<path>’. */ + else if (path.at(0) == '!') { + std::pair<std::string, std::string> ctx = decodeContext(path); + drv.inputDrvs[ctx.first].insert(ctx.second); + } + + /* Otherwise it's a source file. */ + else { + drv.inputSrcs.insert(path); + } + } + + /* Do we have all required attributes? */ + if (drv.builder.empty()) { + throw EvalError(format("required attribute 'builder' missing, at %1%") % + posDrvName); + } + if (drv.platform.empty()) { + throw EvalError(format("required attribute 'system' missing, at %1%") % + posDrvName); + } + + /* Check whether the derivation name is valid. */ + checkStoreName(drvName); + if (isDerivation(drvName)) { + throw EvalError( + format("derivation names are not allowed to end in '%1%', at %2%") % + drvExtension % posDrvName); + } + + if (outputHash) { + /* Handle fixed-output derivations. */ + if (outputs.size() != 1 || *(outputs.begin()) != "out") { + throw Error(format("multiple outputs are not supported in fixed-output " + "derivations, at %1%") % + posDrvName); + } + + HashType ht = + outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo); + auto hash_ = Hash::deserialize(*outputHash, ht); + auto h = Hash::unwrap_throw(hash_); + + Path outPath = + state.store->makeFixedOutputPath(outputHashRecursive, h, drvName); + if (!jsonObject) { + drv.env["out"] = outPath; + } + drv.outputs["out"] = DerivationOutput( + outPath, (outputHashRecursive ? "r:" : "") + printHashType(h.type), + h.to_string(Base16, false)); + } + + else { + /* Construct the "masked" store derivation, which is the final + one except that in the list of outputs, the output paths + are empty, and the corresponding environment variables have + an empty value. This ensures that changes in the set of + output names do get reflected in the hash. */ + for (auto& i : outputs) { + if (!jsonObject) { + drv.env[i] = ""; + } + drv.outputs[i] = DerivationOutput("", "", ""); + } + + /* Use the masked derivation expression to compute the output + path. */ + Hash h = hashDerivationModulo(*state.store, drv); + + for (auto& i : drv.outputs) { + if (i.second.path.empty()) { + Path outPath = state.store->makeOutputPath(i.first, h, drvName); + if (!jsonObject) { + drv.env[i.first] = outPath; + } + i.second.path = outPath; + } + } + } + + /* Write the resulting term into the Nix store directory. */ + Path drvPath = writeDerivation(state.store, drv, drvName, state.repair); + + VLOG(2) << "instantiated '" << drvName << "' -> '" << drvPath << "'"; + + /* Optimisation, but required in read-only mode! because in that + case we don't actually write store derivations, so we can't + read them later. */ + drvHashes[drvPath] = hashDerivationModulo(*state.store, drv); + + state.mkAttrs(v, 1 + drv.outputs.size()); + mkString(*state.allocAttr(v, state.sDrvPath), drvPath, {"=" + drvPath}); + for (auto& i : drv.outputs) { + mkString(*state.allocAttr(v, state.symbols.Create(i.first)), i.second.path, + {"!" + i.first + "!" + drvPath}); + } +} + +/* Return a placeholder string for the specified output that will be + substituted by the corresponding output path at build time. For + example, 'placeholder "out"' returns the string + /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build + time, any occurence of this string in an derivation attribute will + be replaced with the concrete path in the Nix store of the output + ‘out’. */ +static void prim_placeholder(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); +} + +/************************************************************* + * Paths + *************************************************************/ + +/* Convert the argument to a path. !!! obsolete? */ +static void prim_toPath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + mkString(v, canonPath(path), context); +} + +/* Allow a valid store path to be used in an expression. This is + useful in some generated expressions such as in nix-push, which + generates a call to a function with an already existing store path + as argument. You don't want to use `toPath' here because it copies + the path to the Nix store, which yields a copy like + /nix/store/newhash-oldhash-oldname. In the past, `toPath' had + special case behaviour for store paths, but that created weird + corner cases. */ +static void prim_storePath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context)); + /* 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)) { + throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % + path % pos); + } + Path path2 = state.store->toStorePath(path); + if (!settings.readOnlyMode) { + state.store->ensurePath(path2); + } + context.insert(path2); + mkString(v, path, context); +} + +static void prim_pathExists(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError(format("cannot check the existence of '%1%', since path " + "'%2%' is not valid, at %3%") % + path % e.path % pos); + } + + try { + mkBool(v, pathExists(state.checkSourcePath(path))); + } catch (SysError& e) { + /* Don't give away info from errors while canonicalising + ‘path’ in restricted mode. */ + mkBool(v, false); + } catch (RestrictedPathError& e) { + mkBool(v, false); + } +} + +/* Return the base name of the given string, i.e., everything + following the last slash. */ +static void prim_baseNameOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + mkString( + v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), + context); +} + +/* Return the directory of the given path, i.e., everything before the + last slash. Return either a path or a string depending on the type + of the argument. */ +static void prim_dirOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false)); + if (args[0]->type == tPath) { + mkPath(v, dir.c_str()); + } else { + mkString(v, dir, context); + } +} + +/* Return the contents of a file as a string. */ +static void prim_readFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot read '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + std::string s = + readFile(state.checkSourcePath(state.toRealPath(path, context))); + if (s.find(static_cast<char>(0)) != std::string::npos) { + throw Error(format("the contents of the file '%1%' cannot be represented " + "as a Nix string") % + path); + } + mkString(v, s.c_str()); +} + +/* Find a file in the Nix search path. Used to implement <x> paths, + which are desugared to 'findFile __nixPath "x"'. */ +static void prim_findFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + + SearchPath searchPath; + + for (unsigned int n = 0; n < args[0]->listSize(); ++n) { + Value& v2(*(*args[0]->list)[n]); + state.forceAttrs(v2, pos); + + std::string prefix; + Bindings::iterator i = v2.attrs->find(state.symbols.Create("prefix")); + if (i != v2.attrs->end()) { + prefix = state.forceStringNoCtx(*i->second.value, pos); + } + + i = v2.attrs->find(state.symbols.Create("path")); + if (i == v2.attrs->end()) { + throw EvalError(format("attribute 'path' missing, at %1%") % pos); + } + + PathSet context; + std::string path = + state.coerceToString(pos, *i->second.value, context, false, false); + + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot find '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + searchPath.emplace_back(prefix, path); + } + + std::string path = state.forceStringNoCtx(*args[1], pos); + + mkPath(v, + state.checkSourcePath(state.findFile(searchPath, path, pos)).c_str()); +} + +/* Return the cryptographic hash of a file in base-16. */ +static void prim_hashFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string type = state.forceStringNoCtx(*args[0], pos); + HashType ht = parseHashType(type); + if (ht == htUnknown) { + throw Error(format("unknown hash type '%1%', at %2%") % type % pos); + } + + PathSet context; // discarded + Path p = state.coerceToPath(pos, *args[1], context); + + mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), + context); +} + +/* Read a directory (without . or ..) */ +static void prim_readDir(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet ctx; + Path path = state.coerceToPath(pos, *args[0], ctx); + try { + state.realiseContext(ctx); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot read '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + DirEntries entries = readDirectory(state.checkSourcePath(path)); + state.mkAttrs(v, entries.size()); + + for (auto& ent : entries) { + Value* ent_val = state.allocAttr(v, state.symbols.Create(ent.name)); + if (ent.type == DT_UNKNOWN) { + ent.type = getFileType(path + "/" + ent.name); + } + mkStringNoCopy(*ent_val, ent.type == DT_REG ? "regular" + : ent.type == DT_DIR ? "directory" + : ent.type == DT_LNK ? "symlink" + : "unknown"); + } +} + +/************************************************************* + * Creating files + *************************************************************/ + +/* Convert the argument (which can be any Nix expression) to an XML + representation returned in a string. Not all Nix expressions can + be sensibly or completely represented (e.g., functions). */ +static void prim_toXML(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::ostringstream out; + PathSet context; + printValueAsXML(state, true, false, *args[0], out, context); + mkString(v, out.str(), context); +} + +/* Convert the argument (which can be any Nix expression) to a JSON + string. Not all Nix expressions can be sensibly or completely + represented (e.g., functions). */ +static void prim_toJSON(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::ostringstream out; + PathSet context; + printValueAsJSON(state, true, *args[0], out, context); + mkString(v, out.str(), context); +} + +/* Parse a JSON string to a value. */ +static void prim_fromJSON(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string s = state.forceStringNoCtx(*args[0], pos); + parseJSON(state, s, v); +} + +/* Store a string in the Nix store as a source file that can be used + as an input by derivations. */ +static void prim_toFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + std::string name = state.forceStringNoCtx(*args[0], pos); + std::string contents = state.forceString(*args[1], context, pos); + + PathSet refs; + + for (auto path : context) { + if (path.at(0) != '/') { + throw EvalError(format("in 'toFile': the file '%1%' cannot refer to " + "derivation outputs, at %2%") % + name % pos); + } + refs.insert(path); + } + + Path storePath = + settings.readOnlyMode + ? state.store->computeStorePathForText(name, contents, refs) + : state.store->addTextToStore(name, contents, refs, state.repair); + + /* Note: we don't need to add `context' to the context of the + result, since `storePath' itself has references to the paths + used in args[1]. */ + + mkString(v, storePath, {storePath}); +} + +static void addPath(EvalState& state, const Pos& pos, const std::string& name, + const Path& path_, Value* filterFun, bool recursive, + const Hash& expectedHash, Value& v) { + const auto path = evalSettings.pureEval && expectedHash + ? path_ + : state.checkSourcePath(path_); + PathFilter filter = filterFun != nullptr ? ([&](const Path& path) { + auto st = lstat(path); + + /* Call the filter function. The first argument is the path, + the second is a string indicating the type of the file. */ + Value arg1; + mkString(arg1, path); + + Value fun2; + state.callFunction(*filterFun, arg1, fun2, noPos); + + Value arg2; + mkString(arg2, S_ISREG(st.st_mode) ? "regular" + : S_ISDIR(st.st_mode) ? "directory" + : S_ISLNK(st.st_mode) + ? "symlink" + : "unknown" /* not supported, will fail! */); + + Value res; + state.callFunction(fun2, arg2, res, noPos); + + return state.forceBool(res, pos); + }) + : defaultPathFilter; + + Path expectedStorePath; + if (expectedHash) { + expectedStorePath = + state.store->makeFixedOutputPath(recursive, expectedHash, name); + } + Path dstPath; + if (!expectedHash || !state.store->isValidPath(expectedStorePath)) { + dstPath = settings.readOnlyMode + ? state.store + ->computeStorePathForPath(name, path, recursive, + htSHA256, filter) + .first + : state.store->addToStore(name, path, recursive, htSHA256, + filter, state.repair); + if (expectedHash && expectedStorePath != dstPath) { + throw Error(format("store path mismatch in (possibly filtered) path " + "added from '%1%'") % + path); + } + } else { + dstPath = expectedStorePath; + } + + mkString(v, dstPath, {dstPath}); +} + +static void prim_filterSource(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[1], context); + if (!context.empty()) { + throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % + path % pos); + } + + state.forceValue(*args[0]); + if (args[0]->type != tLambda) { + throw TypeError(format("first argument in call to 'filterSource' is not a " + "function but %1%, at %2%") % + showType(*args[0]) % pos); + } + + addPath(state, pos, baseNameOf(path), path, args[0], true, Hash(), v); +} + +static void prim_path(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + Path path; + std::string name; + Value* filterFun = nullptr; + auto recursive = true; + Hash expectedHash; + + for (auto& attr : *args[0]->attrs) { + const std::string& n(attr.second.name); + if (n == "path") { + PathSet context; + path = state.coerceToPath(*attr.second.pos, *attr.second.value, context); + if (!context.empty()) { + throw EvalError( + format("string '%1%' cannot refer to other paths, at %2%") % path % + *attr.second.pos); + } + } else if (attr.second.name == state.sName) { + name = state.forceStringNoCtx(*attr.second.value, *attr.second.pos); + } else if (n == "filter") { + state.forceValue(*attr.second.value); + filterFun = attr.second.value; + } else if (n == "recursive") { + recursive = state.forceBool(*attr.second.value, *attr.second.pos); + } else if (n == "sha256") { + auto hash_ = Hash::deserialize( + state.forceStringNoCtx(*attr.second.value, *attr.second.pos), + htSHA256); + expectedHash = Hash::unwrap_throw(hash_); + } else { + throw EvalError( + format("unsupported argument '%1%' to 'addPath', at %2%") % + attr.second.name % *attr.second.pos); + } + } + if (path.empty()) { + throw EvalError(format("'path' required, at %1%") % pos); + } + if (name.empty()) { + name = baseNameOf(path); + } + + addPath(state, pos, name, path, filterFun, recursive, expectedHash, v); +} + +/************************************************************* + * Sets + *************************************************************/ + +/* Return the names of the attributes in a set as a sorted list of + strings. */ +static void prim_attrNames(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + + state.mkList(v, args[0]->attrs->size()); + + unsigned int n = 0; + for (auto& [key, value] : *args[0]->attrs) { + mkString(*((*v.list)[n++] = state.allocValue()), key); + } +} + +/* Return the values of the attributes in a set as a list, in the same + order as attrNames. */ +static void prim_attrValues(EvalState& state, const Pos& pos, Value** input, + Value& output) { + state.forceAttrs(*input[0], pos); + + state.mkList(output, input[0]->attrs->size()); + + unsigned int n = 0; + for (auto& [key, value] : *input[0]->attrs) { + (*output.list)[n++] = value.value; + } +} + +/* Dynamic version of the `.' operator. */ +void prim_getAttr(EvalState& state, const Pos& pos, Value** args, Value& v) { + std::string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + // !!! Should we create a symbol here or just do a lookup? + Bindings::iterator i = args[1]->attrs->find(state.symbols.Create(attr)); + if (i == args[1]->attrs->end()) { + throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos); + } + // !!! add to stack trace? + if (state.countCalls && (i->second.pos != nullptr)) { + state.attrSelects[*i->second.pos]++; + } + state.forceValue(*i->second.value); + v = *i->second.value; +} + +/* Return position information of the specified attribute. */ +void prim_unsafeGetAttrPos(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + Bindings::iterator i = args[1]->attrs->find(state.symbols.Create(attr)); + if (i == args[1]->attrs->end()) { + mkNull(v); + } else { + state.mkPos(v, i->second.pos); + } +} + +/* Dynamic version of the `?' operator. */ +static void prim_hasAttr(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + mkBool(v, args[1]->attrs->find(state.symbols.Create(attr)) != + args[1]->attrs->end()); +} + +/* Determine whether the argument is a set. */ +static void prim_isAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tAttrs); +} + +static void prim_removeAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + state.forceList(*args[1], pos); + + /* Get the attribute names to be removed. */ + std::set<Symbol> names; + for (unsigned int i = 0; i < args[1]->listSize(); ++i) { + state.forceStringNoCtx(*(*args[1]->list)[i], pos); + names.insert(state.symbols.Create((*args[1]->list)[i]->string.s)); + } + + /* Copy all attributes not in that set. Note that we don't need + to sort v.attrs because it's a subset of an already sorted + vector. */ + state.mkAttrs(v, args[0]->attrs->size()); + for (auto& i : *args[0]->attrs) { + if (names.find(i.second.name) == names.end()) { + v.attrs->push_back(i.second); + } + } +} + +/* Builds a set from a list specifying (name, value) pairs. To be + precise, a list [{name = "name1"; value = value1;} ... {name = + "nameN"; value = valueN;}] is transformed to {name1 = value1; + ... nameN = valueN;}. In case of duplicate occurences of the same + name, the first takes precedence. */ +static void prim_listToAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + + state.mkAttrs(v, args[0]->listSize()); + + std::set<Symbol> seen; + + for (unsigned int i = 0; i < args[0]->listSize(); ++i) { + Value& v2(*(*args[0]->list)[i]); + state.forceAttrs(v2, pos); + + Bindings::iterator j = v2.attrs->find(state.sName); + if (j == v2.attrs->end()) { + throw TypeError( + format( + "'name' attribute missing in a call to 'listToAttrs', at %1%") % + pos); + } + std::string name = state.forceStringNoCtx(*j->second.value, pos); + + Symbol sym = state.symbols.Create(name); + if (seen.find(sym) == seen.end()) { + Bindings::iterator j2 = + // TODO(tazjin): this line used to construct the symbol again: + // state.symbols.Create(state.sValue)); + // Why? + v2.attrs->find(state.sValue); + if (j2 == v2.attrs->end()) { + throw TypeError(format("'value' attribute missing in a call to " + "'listToAttrs', at %1%") % + pos); + } + + v.attrs->push_back(Attr(sym, j2->second.value, j2->second.pos)); + seen.insert(sym); + } + } +} + +/* Return the right-biased intersection of two sets as1 and as2, + i.e. a set that contains every attribute from as2 that is also a + member of as1. */ +static void prim_intersectAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[1], pos); + + state.mkAttrs(v, std::min(args[0]->attrs->size(), args[1]->attrs->size())); + + for (auto& i : *args[0]->attrs) { + Bindings::iterator j = args[1]->attrs->find(i.second.name); + if (j != args[1]->attrs->end()) { + v.attrs->push_back(j->second); + } + } +} + +/* Collect each attribute named `attr' from a list of attribute sets. + Sets that don't contain the named attribute are ignored. + + Example: + catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}] + => [1 2] +*/ +static void prim_catAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + Symbol attrName = state.symbols.Create(state.forceStringNoCtx(*args[0], pos)); + state.forceList(*args[1], pos); + + Value* res[args[1]->listSize()]; + unsigned int found = 0; + + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + Value& v2(*(*args[1]->list)[n]); + state.forceAttrs(v2, pos); + Bindings::iterator i = v2.attrs->find(attrName); + if (i != v2.attrs->end()) { + res[found++] = i->second.value; + } + } + + state.mkList(v, found); + for (unsigned int n = 0; n < found; ++n) { + (*v.list)[n] = res[n]; + } +} + +/* Return a set containing the names of the formal arguments expected + by the function `f'. The value of each attribute is a Boolean + denoting whether the corresponding argument has a default value. For + instance, + + functionArgs ({ x, y ? 123}: ...) + => { x = false; y = true; } + + "Formal argument" here refers to the attributes pattern-matched by + the function. Plain lambdas are not included, e.g. + + functionArgs (x: ...) + => { } +*/ +static void prim_functionArgs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + if (args[0]->type == tPrimOpApp || args[0]->type == tPrimOp) { + state.mkAttrs(v, 0); + return; + } + if (args[0]->type != tLambda) { + throw TypeError(format("'functionArgs' requires a function, at %1%") % pos); + } + + if (!args[0]->lambda.fun->matchAttrs) { + state.mkAttrs(v, 0); + return; + } + + state.mkAttrs(v, args[0]->lambda.fun->formals->formals.size()); + for (auto& i : args[0]->lambda.fun->formals->formals) { + // !!! should optimise booleans (allocate only once) + // TODO(tazjin): figure out what the above comment means + mkBool(*state.allocAttr(v, i.name), i.def != nullptr); + } +} + +/* Apply a function to every element of an attribute set. */ +static void prim_mapAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[1], pos); + + state.mkAttrs(v, args[1]->attrs->size()); + + for (auto& i : *args[1]->attrs) { + Value* vName = state.allocValue(); + Value* vFun2 = state.allocValue(); + mkString(*vName, i.second.name); + mkApp(*vFun2, *args[0], *vName); + mkApp(*state.allocAttr(v, i.second.name), *vFun2, *i.second.value); + } +} + +/************************************************************* + * Lists + *************************************************************/ + +/* Determine whether the argument is a list. */ +static void prim_isList(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->isList()); +} + +static void elemAt(EvalState& state, const Pos& pos, Value& list, int n, + Value& v) { + state.forceList(list, pos); + if (n < 0 || static_cast<unsigned int>(n) >= list.listSize()) { + throw Error(format("list index %1% is out of bounds, at %2%") % n % pos); + } + state.forceValue(*(*list.list)[n]); + v = *(*list.list)[n]; +} + +/* Return the n-1'th element of a list. */ +static void prim_elemAt(EvalState& state, const Pos& pos, Value** args, + Value& v) { + elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); +} + +/* Return the first element of a list. */ +static void prim_head(EvalState& state, const Pos& pos, Value** args, + Value& v) { + elemAt(state, pos, *args[0], 0, v); +} + +/* Return a list consisting of everything but the first element of + a list. Warning: this function takes O(n) time, so you probably + don't want to use it! */ +static void prim_tail(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + if (args[0]->listSize() == 0) { + throw Error(format("'tail' called on an empty list, at %1%") % pos); + } + state.mkList(v, args[0]->listSize() - 1); + for (unsigned int n = 0; n < v.listSize(); ++n) { + (*v.list)[n] = (*args[0]->list)[n + 1]; + } +} + +/* Apply a function to every element of a list. */ +static void prim_map(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceList(*args[1], pos); + + state.mkList(v, args[1]->listSize()); + + for (unsigned int n = 0; n < v.listSize(); ++n) { + mkApp(*((*v.list)[n] = state.allocValue()), *args[0], *(*args[1]->list)[n]); + } +} + +/* Filter a list using a predicate; that is, return a list containing + every element from the list for which the predicate function + returns true. */ +static void prim_filter(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + // FIXME: putting this on the stack is risky. + Value* vs[args[1]->listSize()]; + unsigned int k = 0; + + bool same = true; + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + Value res; + state.callFunction(*args[0], *(*args[1]->list)[n], res, noPos); + if (state.forceBool(res, pos)) { + vs[k++] = (*args[1]->list)[n]; + } else { + same = false; + } + } + + if (same) { + v = *args[1]; + } else { + state.mkList(v, k); + for (unsigned int n = 0; n < k; ++n) { + (*v.list)[n] = vs[n]; + } + } +} + +/* Return true if a list contains a given element. */ +static void prim_elem(EvalState& state, const Pos& pos, Value** args, + Value& v) { + bool res = false; + state.forceList(*args[1], pos); + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + if (state.eqValues(*args[0], *(*args[1]->list)[n])) { + res = true; + break; + } + } + mkBool(v, res); +} + +/* Concatenate a list of lists. */ +static void prim_concatLists(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + state.concatLists(v, *args[0]->list, pos); +} + +/* Return the length of a list. This is an O(1) time operation. */ +static void prim_length(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + mkInt(v, args[0]->listSize()); +} + +/* Reduce a list by applying a binary operator, from left to + right. The operator is applied strictly. */ +static void prim_foldlStrict(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[2], pos); + + if (args[2]->listSize() != 0u) { + Value* vCur = args[1]; + + for (unsigned int n = 0; n < args[2]->listSize(); ++n) { + Value vTmp; + state.callFunction(*args[0], *vCur, vTmp, pos); + vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue(); + state.callFunction(vTmp, *(*args[2]->list)[n], *vCur, pos); + } + state.forceValue(v); + } else { + state.forceValue(*args[1]); + v = *args[1]; + } +} + +static void anyOrAll(bool any, EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + Value vTmp; + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + state.callFunction(*args[0], *(*args[1]->list)[n], vTmp, pos); + bool res = state.forceBool(vTmp, pos); + if (res == any) { + mkBool(v, any); + return; + } + } + + mkBool(v, !any); +} + +static void prim_any(EvalState& state, const Pos& pos, Value** args, Value& v) { + anyOrAll(true, state, pos, args, v); +} + +static void prim_all(EvalState& state, const Pos& pos, Value** args, Value& v) { + anyOrAll(false, state, pos, args, v); +} + +static void prim_genList(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto len = state.forceInt(*args[1], pos); + + if (len < 0) { + throw EvalError(format("cannot create list of size %1%, at %2%") % len % + pos); + } + + state.mkList(v, len); + + for (unsigned int n = 0; n < static_cast<unsigned int>(len); ++n) { + Value* arg = state.allocValue(); + mkInt(*arg, n); + mkApp(*((*v.list)[n] = state.allocValue()), *args[0], *arg); + } +} + +static void prim_lessThan(EvalState& state, const Pos& pos, Value** args, + Value& v); + +static void prim_sort(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + // Copy of the input list which can be sorted in place. + v.type = tList; + v.list = std::make_shared<NixList>(*args[1]->list); + + std::for_each(v.list->begin(), v.list->end(), + [&](Value* val) { state.forceValue(*val); }); + + auto comparator = [&](Value* a, Value* b) { + /* Optimization: if the comparator is lessThan, bypass + callFunction. */ + if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan) { + return CompareValues()(a, b); + } + + Value vTmp1{}; + Value vTmp2{}; + state.callFunction(*args[0], *a, vTmp1, pos); + state.callFunction(vTmp1, *b, vTmp2, pos); + return state.forceBool(vTmp2, pos); + }; + + /* FIXME: std::sort can segfault if the comparator is not a strict + weak ordering. What to do? std::stable_sort() seems more + resilient, but no guarantees... */ + std::stable_sort(v.list->begin(), v.list->end(), comparator); +} + +static void prim_partition(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + std::shared_ptr<NixList> right = std::make_shared<NixList>(); + std::shared_ptr<NixList> wrong = std::make_shared<NixList>(); + + for (Value* elem : *args[1]->list) { + state.forceValue(*elem, pos); + + Value res; + state.callFunction(*args[0], *elem, res, pos); + if (state.forceBool(res, pos)) { + right->push_back(elem); + } else { + wrong->push_back(elem); + } + } + + state.mkAttrs(v, 2); + + Value* vRight = state.allocAttr(v, state.sRight); + state.mkList(*vRight, right); + + Value* vWrong = state.allocAttr(v, state.sWrong); + state.mkList(*vWrong, wrong); +} + +/* concatMap = f: list: concatLists (map f list); */ +/* C++-version is to avoid allocating `mkApp', call `f' eagerly */ +static void prim_concatMap(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + std::shared_ptr<NixList> outlist = std::make_shared<NixList>(); + + for (Value* elem : *args[1]->list) { + auto out = state.allocValue(); + state.callFunction(*args[0], *elem, *out, pos); + state.forceList(*out, pos); + + outlist->insert(outlist->end(), out->list->begin(), out->list->end()); + } + + state.mkList(v, outlist); +} + +/************************************************************* + * Integer arithmetic + *************************************************************/ + +static void prim_add(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) { + mkFloat(v, + state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos)); + } else { + mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos)); + } +} + +static void prim_sub(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) { + mkFloat(v, + state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos)); + } else { + mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos)); + } +} + +static void prim_mul(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) { + mkFloat(v, + state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos)); + } else { + mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos)); + } +} + +static void prim_div(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + + NixFloat f2 = state.forceFloat(*args[1], pos); + if (f2 == 0) { + throw EvalError(format("division by zero, at %1%") % pos); + } + + if (args[0]->type == tFloat || args[1]->type == tFloat) { + mkFloat(v, + state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); + } else { + NixInt i1 = state.forceInt(*args[0], pos); + NixInt i2 = state.forceInt(*args[1], pos); + /* Avoid division overflow as it might raise SIGFPE. */ + if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) { + throw EvalError(format("overflow in integer division, at %1%") % pos); + } + mkInt(v, i1 / i2); + } +} + +static void prim_bitAnd(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); +} + +static void prim_bitOr(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); +} + +static void prim_bitXor(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); +} + +static void prim_lessThan(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + state.forceValue(*args[1]); + CompareValues comp; + mkBool(v, comp(args[0], args[1])); +} + +/************************************************************* + * String manipulation + *************************************************************/ + +/* Convert the argument to a string. Paths are *not* copied to the + store, so `toString /foo/bar' yields `"/foo/bar"', not + `"/nix/store/whatever..."'. */ +static void prim_toString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + std::string s = state.coerceToString(pos, *args[0], context, true, false); + mkString(v, s, context); +} + +/* `substring start len str' returns the substring of `str' starting + at character position `min(start, stringLength str)' inclusive and + ending at `min(start + len, stringLength str)'. `start' must be + non-negative. */ +static void prim_substring(EvalState& state, const Pos& pos, Value** args, + Value& v) { + int start = state.forceInt(*args[0], pos); + int len = state.forceInt(*args[1], pos); + PathSet context; + std::string s = state.coerceToString(pos, *args[2], context); + + if (start < 0) { + throw EvalError(format("negative start position in 'substring', at %1%") % + pos); + } + + mkString(v, + static_cast<unsigned int>(start) >= s.size() + ? "" + : std::string(s, start, len), + context); +} + +static void prim_stringLength(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + std::string s = state.coerceToString(pos, *args[0], context); + mkInt(v, s.size()); +} + +/* Return the cryptographic hash of a string in base-16. */ +static void prim_hashString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string type = state.forceStringNoCtx(*args[0], pos); + HashType ht = parseHashType(type); + if (ht == htUnknown) { + throw Error(format("unknown hash type '%1%', at %2%") % type % pos); + } + + PathSet context; // discarded + std::string s = state.forceString(*args[1], context, pos); + + mkString(v, hashString(ht, s).to_string(Base16, false), context); +} + +/* Match a regular expression against a string and return either + ‘null’ or a list containing substring matches. */ +static void prim_match(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto re = state.forceStringNoCtx(*args[0], pos); + + try { + std::regex regex(re, std::regex::extended); + + PathSet context; + const std::string str = state.forceString(*args[1], context, pos); + + std::smatch match; + if (!std::regex_match(str, match, regex)) { + mkNull(v); + return; + } + + // the first match is the whole string + const size_t len = match.size() - 1; + state.mkList(v, len); + for (size_t i = 0; i < len; ++i) { + if (!match[i + 1].matched) { + mkNull(*((*v.list)[i] = state.allocValue())); + } else { + mkString(*((*v.list)[i] = state.allocValue()), + match[i + 1].str().c_str()); + } + } + + } catch (std::regex_error& e) { + if (e.code() == std::regex_constants::error_space) { + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError("memory limit exceeded by regular expression '%s', at %s", + re, pos); + } + throw EvalError("invalid regular expression '%s', at %s", re, pos); + } +} + +/* Split a std::string with a regular expression, and return a list of the + non-matching parts interleaved by the lists of the matching groups. */ +static void prim_split(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto re = state.forceStringNoCtx(*args[0], pos); + + try { + std::regex regex(re, std::regex::extended); + + PathSet context; + const std::string str = state.forceString(*args[1], context, pos); + + auto begin = std::sregex_iterator(str.begin(), str.end(), regex); + auto end = std::sregex_iterator(); + + // Any matches results are surrounded by non-matching results. + const size_t len = std::distance(begin, end); + state.mkList(v, 2 * len + 1); + size_t idx = 0; + Value* elem; + + if (len == 0) { + (*v.list)[idx++] = args[1]; + return; + } + + for (std::sregex_iterator i = begin; i != end; ++i) { + assert(idx <= 2 * len + 1 - 3); + std::smatch match = *i; + + // Add a string for non-matched characters. + elem = (*v.list)[idx++] = state.allocValue(); + mkString(*elem, match.prefix().str().c_str()); + + // Add a list for matched substrings. + const size_t slen = match.size() - 1; + elem = (*v.list)[idx++] = state.allocValue(); + + // Start at 1, beacause the first match is the whole string. + state.mkList(*elem, slen); + for (size_t si = 0; si < slen; ++si) { + if (!match[si + 1].matched) { + mkNull(*((*elem->list)[si] = state.allocValue())); + } else { + mkString(*((*elem->list)[si] = state.allocValue()), + match[si + 1].str().c_str()); + } + } + + // Add a string for non-matched suffix characters. + if (idx == 2 * len) { + elem = (*v.list)[idx++] = state.allocValue(); + mkString(*elem, match.suffix().str().c_str()); + } + } + assert(idx == 2 * len + 1); + + } catch (std::regex_error& e) { + if (e.code() == std::regex_constants::error_space) { + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError("memory limit exceeded by regular expression '%s', at %s", + re, pos); + } + throw EvalError("invalid regular expression '%s', at %s", re, pos); + } +} + +static void prim_concatStringSep(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + + auto sep = state.forceString(*args[0], context, pos); + state.forceList(*args[1], pos); + + std::string res; + res.reserve((args[1]->listSize() + 32) * sep.size()); + bool first = true; + + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + if (first) { + first = false; + } else { + res += sep; + } + + res += state.coerceToString(pos, *(*args[1]->list)[n], context); + } + + mkString(v, res, context); +} + +static void prim_replaceStrings(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + state.forceList(*args[1], pos); + if (args[0]->listSize() != args[1]->listSize()) { + throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have " + "different lengths, at %1%") % + pos); + } + + std::vector<std::string> from; + from.reserve(args[0]->listSize()); + for (unsigned int n = 0; n < args[0]->listSize(); ++n) { + from.push_back(state.forceString(*(*args[0]->list)[n], pos)); + } + + std::vector<std::pair<std::string, PathSet>> to; + to.reserve(args[1]->listSize()); + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + PathSet ctx; + auto s = state.forceString(*(*args[1]->list)[n], ctx, pos); + to.emplace_back(std::move(s), std::move(ctx)); + } + + PathSet context; + auto s = state.forceString(*args[2], context, pos); + + std::string res; + // Loops one past last character to handle the case where 'from' contains an + // empty string. + for (size_t p = 0; p <= s.size();) { + bool found = false; + auto i = from.begin(); + auto j = to.begin(); + for (; i != from.end(); ++i, ++j) { + if (s.compare(p, i->size(), *i) == 0) { + found = true; + res += j->first; + if (i->empty()) { + if (p < s.size()) { + res += s[p]; + } + p++; + } else { + p += i->size(); + } + for (auto& path : j->second) { + context.insert(path); + } + j->second.clear(); + break; + } + } + if (!found) { + if (p < s.size()) { + res += s[p]; + } + p++; + } + } + + mkString(v, res, context); +} + +/************************************************************* + * Versions + *************************************************************/ + +static void prim_parseDrvName(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string name = state.forceStringNoCtx(*args[0], pos); + DrvName parsed(name); + state.mkAttrs(v, 2); + mkString(*state.allocAttr(v, state.sName), parsed.name); + mkString(*state.allocAttr(v, state.symbols.Create("version")), + parsed.version); +} + +static void prim_compareVersions(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string version1 = state.forceStringNoCtx(*args[0], pos); + std::string version2 = state.forceStringNoCtx(*args[1], pos); + mkInt(v, compareVersions(version1, version2)); +} + +static void prim_splitVersion(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string version = state.forceStringNoCtx(*args[0], pos); + auto iter = version.cbegin(); + Strings components; + while (iter != version.cend()) { + auto component = nextComponent(iter, version.cend()); + if (component.empty()) { + break; + } + components.emplace_back(std::move(component)); + } + state.mkList(v, components.size()); + unsigned int n = 0; + for (auto& component : components) { + auto listElem = (*v.list)[n++] = state.allocValue(); + mkString(*listElem, component); + } +} + +/************************************************************* + * Networking + *************************************************************/ + +void fetch(EvalState& state, const Pos& pos, Value** args, Value& v, + const std::string& who, bool unpack, + const std::string& defaultName) { + CachedDownloadRequest request(""); + request.unpack = unpack; + request.name = defaultName; + + state.forceValue(*args[0]); + + if (args[0]->type == tAttrs) { + state.forceAttrs(*args[0], pos); + + for (auto& attr : *args[0]->attrs) { + std::string n(attr.second.name); + if (n == "url") { + request.uri = + state.forceStringNoCtx(*attr.second.value, *attr.second.pos); + } else if (n == "sha256") { + auto hash_ = Hash::deserialize( + state.forceStringNoCtx(*attr.second.value, *attr.second.pos), + htSHA256); + request.expectedHash = Hash::unwrap_throw(hash_); + } else if (n == "name") { + request.name = + state.forceStringNoCtx(*attr.second.value, *attr.second.pos); + } else { + throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") % + attr.second.name % who % attr.second.pos); + } + } + + if (request.uri.empty()) { + throw EvalError(format("'url' argument required, at %1%") % pos); + } + + } else { + request.uri = state.forceStringNoCtx(*args[0], pos); + } + + state.checkURI(request.uri); + + if (evalSettings.pureEval && !request.expectedHash) { + throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", + who); + } + + auto res = getDownloader()->downloadCached(state.store, request); + + if (state.allowedPaths) { + state.allowedPaths->insert(res.path); + } + + mkString(v, res.storePath, PathSet({res.storePath})); +} + +static void prim_fetchurl(EvalState& state, const Pos& pos, Value** args, + Value& v) { + fetch(state, pos, args, v, "fetchurl", false, ""); +} + +static void prim_fetchTarball(EvalState& state, const Pos& pos, Value** args, + Value& v) { + fetch(state, pos, args, v, "fetchTarball", true, "source"); +} + +/************************************************************* + * Primop registration + *************************************************************/ + +RegisterPrimOp::PrimOps* RegisterPrimOp::primOps; + +RegisterPrimOp::RegisterPrimOp(const std::string& name, size_t arity, + PrimOpFun fun) { + if (primOps == nullptr) { + primOps = new PrimOps; + } + primOps->emplace_back(name, arity, fun); +} + +void EvalState::createBaseEnv() { + baseEnv.up = nullptr; + + /* Add global constants such as `true' to the base environment. */ + Value v; + + /* `builtins' must be first! */ + mkAttrs(v, 128); + addConstant("builtins", v); + + mkBool(v, true); + addConstant("true", v); + + mkBool(v, false); + addConstant("false", v); + + mkNull(v); + addConstant("null", v); + + auto vThrow = addPrimOp("throw", 1, prim_throw); + + auto addPurityError = [&](const std::string& name) { + Value* v2 = allocValue(); + mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name)); + mkApp(v, *vThrow, *v2); + addConstant(name, v); + }; + + if (!evalSettings.pureEval) { + mkInt(v, time(nullptr)); + addConstant("__currentTime", v); + } + + if (!evalSettings.pureEval) { + mkString(v, settings.thisSystem); + addConstant("__currentSystem", v); + } + + mkString(v, nixVersion); + addConstant("__nixVersion", v); + + mkString(v, store->storeDir); + addConstant("__storeDir", v); + + /* 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. */ + mkInt(v, 5); + addConstant("__langVersion", v); + + // Miscellaneous + auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport); + Value* v2 = allocValue(); + mkAttrs(*v2, 0); + mkApp(v, *vScopedImport, *v2); + forceValue(v); + addConstant("import", v); + addPrimOp("__typeOf", 1, prim_typeOf); + addPrimOp("isNull", 1, prim_isNull); + addPrimOp("__isFunction", 1, prim_isFunction); + addPrimOp("__isString", 1, prim_isString); + addPrimOp("__isInt", 1, prim_isInt); + addPrimOp("__isFloat", 1, prim_isFloat); + addPrimOp("__isBool", 1, prim_isBool); + addPrimOp("__isPath", 1, prim_isPath); + addPrimOp("__genericClosure", 1, prim_genericClosure); + addPrimOp("abort", 1, prim_abort); + addPrimOp("__addErrorContext", 2, prim_addErrorContext); + addPrimOp("__tryEval", 1, prim_tryEval); + addPrimOp("__getEnv", 1, prim_getEnv); + + // Strictness + addPrimOp("__seq", 2, prim_seq); + addPrimOp("__deepSeq", 2, prim_deepSeq); + + // Debugging + addPrimOp("__trace", 2, prim_trace); + addPrimOp("__valueSize", 1, prim_valueSize); + + // Paths + addPrimOp("__toPath", 1, prim_toPath); + if (evalSettings.pureEval) { + addPurityError("__storePath"); + } else { + addPrimOp("__storePath", 1, prim_storePath); + } + addPrimOp("__pathExists", 1, prim_pathExists); + addPrimOp("baseNameOf", 1, prim_baseNameOf); + addPrimOp("dirOf", 1, prim_dirOf); + addPrimOp("__readFile", 1, prim_readFile); + addPrimOp("__readDir", 1, prim_readDir); + addPrimOp("__findFile", 2, prim_findFile); + addPrimOp("__hashFile", 2, prim_hashFile); + + // Creating files + addPrimOp("__toXML", 1, prim_toXML); + addPrimOp("__toJSON", 1, prim_toJSON); + addPrimOp("__fromJSON", 1, prim_fromJSON); + addPrimOp("__toFile", 2, prim_toFile); + addPrimOp("__filterSource", 2, prim_filterSource); + addPrimOp("__path", 1, prim_path); + + // Sets + addPrimOp("__attrNames", 1, prim_attrNames); + addPrimOp("__attrValues", 1, prim_attrValues); + addPrimOp("__getAttr", 2, prim_getAttr); + addPrimOp("__unsafeGetAttrPos", 2, prim_unsafeGetAttrPos); + addPrimOp("__hasAttr", 2, prim_hasAttr); + addPrimOp("__isAttrs", 1, prim_isAttrs); + addPrimOp("removeAttrs", 2, prim_removeAttrs); + addPrimOp("__listToAttrs", 1, prim_listToAttrs); + addPrimOp("__intersectAttrs", 2, prim_intersectAttrs); + addPrimOp("__catAttrs", 2, prim_catAttrs); + addPrimOp("__functionArgs", 1, prim_functionArgs); + addPrimOp("__mapAttrs", 2, prim_mapAttrs); + + // Lists + addPrimOp("__isList", 1, prim_isList); + addPrimOp("__elemAt", 2, prim_elemAt); + addPrimOp("__head", 1, prim_head); + addPrimOp("__tail", 1, prim_tail); + addPrimOp("map", 2, prim_map); + addPrimOp("__filter", 2, prim_filter); + addPrimOp("__elem", 2, prim_elem); + addPrimOp("__concatLists", 1, prim_concatLists); + addPrimOp("__length", 1, prim_length); + addPrimOp("__foldl'", 3, prim_foldlStrict); + addPrimOp("__any", 2, prim_any); + addPrimOp("__all", 2, prim_all); + addPrimOp("__genList", 2, prim_genList); + addPrimOp("__sort", 2, prim_sort); + addPrimOp("__partition", 2, prim_partition); + addPrimOp("__concatMap", 2, prim_concatMap); + + // Integer arithmetic + addPrimOp("__add", 2, prim_add); + addPrimOp("__sub", 2, prim_sub); + addPrimOp("__mul", 2, prim_mul); + addPrimOp("__div", 2, prim_div); + addPrimOp("__bitAnd", 2, prim_bitAnd); + addPrimOp("__bitOr", 2, prim_bitOr); + addPrimOp("__bitXor", 2, prim_bitXor); + addPrimOp("__lessThan", 2, prim_lessThan); + + // String manipulation + addPrimOp("toString", 1, prim_toString); + addPrimOp("__substring", 3, prim_substring); + addPrimOp("__stringLength", 1, prim_stringLength); + addPrimOp("__hashString", 2, prim_hashString); + addPrimOp("__match", 2, prim_match); + addPrimOp("__split", 2, prim_split); + addPrimOp("__concatStringsSep", 2, prim_concatStringSep); + addPrimOp("__replaceStrings", 3, prim_replaceStrings); + + // Versions + addPrimOp("__parseDrvName", 1, prim_parseDrvName); + addPrimOp("__compareVersions", 2, prim_compareVersions); + addPrimOp("__splitVersion", 1, prim_splitVersion); + + // Derivations + addPrimOp("derivationStrict", 1, prim_derivationStrict); + addPrimOp("placeholder", 1, prim_placeholder); + + // Networking + addPrimOp("__fetchurl", 1, prim_fetchurl); + addPrimOp("fetchTarball", 1, prim_fetchTarball); + + /* Add a wrapper around the derivation primop that computes the + `drvPath' and `outPath' attributes lazily. */ + std::string path = + canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true); + sDerivationNix = symbols.Create(path); + evalFile(path, v); + addConstant("derivation", v); + + /* Add a value containing the current Nix expression search path. */ + mkList(v, searchPath.size()); + int n = 0; + for (auto& i : searchPath) { + v2 = (*v.list)[n++] = allocValue(); + mkAttrs(*v2, 2); + mkString(*allocAttr(*v2, symbols.Create("path")), i.second); + mkString(*allocAttr(*v2, symbols.Create("prefix")), i.first); + } + addConstant("__nixPath", v); + + if (RegisterPrimOp::primOps != nullptr) { + for (auto& primOp : *RegisterPrimOp::primOps) { + addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp)); + } + } +} + +} // namespace nix |