aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/eval-cache.cc6
-rw-r--r--src/libexpr/eval.cc254
-rw-r--r--src/libexpr/eval.hh15
-rw-r--r--src/libexpr/flake/call-flake.nix2
-rw-r--r--src/libexpr/flake/config.cc2
-rw-r--r--src/libexpr/flake/flake.cc53
-rw-r--r--src/libexpr/flake/flake.hh6
-rw-r--r--src/libexpr/flake/flakeref.hh2
-rw-r--r--src/libexpr/flake/lockfile.cc48
-rw-r--r--src/libexpr/flake/lockfile.hh9
-rw-r--r--src/libexpr/get-drvs.cc2
-rw-r--r--src/libexpr/nixexpr.cc73
-rw-r--r--src/libexpr/nixexpr.hh29
-rw-r--r--src/libexpr/parser.y65
-rw-r--r--src/libexpr/primops.cc113
-rw-r--r--src/libexpr/primops/fetchTree.cc2
-rw-r--r--src/libexpr/tests/libexprtests.hh3
-rw-r--r--src/libexpr/tests/primops.cc17
-rw-r--r--src/libexpr/value-to-json.cc50
-rw-r--r--src/libexpr/value-to-json.hh7
-rw-r--r--src/libexpr/value-to-xml.cc3
-rw-r--r--src/libexpr/value.hh6
22 files changed, 455 insertions, 312 deletions
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 9a8a7fe62..afe575fee 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -645,17 +645,17 @@ NixInt AttrCursor::getInt()
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto i = std::get_if<int_t>(&cachedValue->second)) {
- debug("using cached Integer attribute '%s'", getAttrPathStr());
+ debug("using cached integer attribute '%s'", getAttrPathStr());
return i->x;
} else
- throw TypeError("'%s' is not an Integer", getAttrPathStr());
+ throw TypeError("'%s' is not an integer", getAttrPathStr());
}
}
auto & v = forceValue();
if (v.type() != nInt)
- throw TypeError("'%s' is not an Integer", getAttrPathStr());
+ throw TypeError("'%s' is not an integer", getAttrPathStr());
return v.integer;
}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 0ff8bba4d..72c2b104f 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -7,7 +7,6 @@
#include "globals.hh"
#include "eval-inline.hh"
#include "filetransfer.hh"
-#include "json.hh"
#include "function-trace.hh"
#include <algorithm>
@@ -21,6 +20,7 @@
#include <functional>
#include <sys/resource.h>
+#include <nlohmann/json.hpp>
#if HAVE_BOEHMGC
@@ -35,6 +35,8 @@
#endif
+using json = nlohmann::json;
+
namespace nix {
static char * allocString(size_t size)
@@ -43,7 +45,7 @@ static char * allocString(size_t size)
#if HAVE_BOEHMGC
t = (char *) GC_MALLOC_ATOMIC(size);
#else
- t = malloc(size);
+ t = (char *) malloc(size);
#endif
if (!t) throw std::bad_alloc();
return t;
@@ -65,26 +67,19 @@ static char * dupString(const char * s)
// When there's no need to write to the string, we can optimize away empty
// string allocations.
-// This function handles makeImmutableStringWithLen(null, 0) by returning the
-// empty string.
-static const char * makeImmutableStringWithLen(const char * s, size_t size)
+// This function handles makeImmutableString(std::string_view()) by returning
+// the empty string.
+static const char * makeImmutableString(std::string_view s)
{
- char * t;
+ const size_t size = s.size();
if (size == 0)
return "";
-#if HAVE_BOEHMGC
- t = GC_STRNDUP(s, size);
-#else
- t = strndup(s, size);
-#endif
- if (!t) throw std::bad_alloc();
+ auto t = allocString(size + 1);
+ memcpy(t, s.data(), size);
+ t[size] = '\0';
return t;
}
-static inline const char * makeImmutableString(std::string_view s) {
- return makeImmutableStringWithLen(s.data(), s.size());
-}
-
RootValue allocRootValue(Value * v)
{
@@ -404,7 +399,8 @@ static Strings parseNixPath(const std::string & s)
}
if (*p == ':') {
- if (isUri(std::string(start2, s.end()))) {
+ auto prefix = std::string(start2, s.end());
+ if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) {
++p;
while (p != s.end() && *p != ':') ++p;
}
@@ -436,21 +432,23 @@ ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view t
return *this;
}
-ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s) {
+ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
+{
info.suggestions = s;
return *this;
}
-ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr) {
+ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
+{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
state.debugTraces.push_front(DebugTrace {
- .pos = std::nullopt,
- .expr = expr,
- .env = env,
- .hint = hintformat("Fake frame for debugg{ing,er} purposes"),
- .isError = true
- });
+ .pos = nullptr,
+ .expr = expr,
+ .env = env,
+ .hint = hintformat("Fake frame for debugging purposes"),
+ .isError = true
+ });
return *this;
}
@@ -508,9 +506,6 @@ EvalState::EvalState(
#if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
-#else
- , valueAllocCache(std::make_shared<void *>(nullptr))
- , env1AllocCache(std::make_shared<void *>(nullptr))
#endif
, baseEnv(allocEnv(128))
, staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
@@ -842,7 +837,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>(
*this,
DebugTrace {
- .pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()],
+ .pos = error->info().errPos ? error->info().errPos : static_cast<std::shared_ptr<AbstractPos>>(positions[expr.getPos()]),
.expr = expr,
.env = env,
.hint = error->info().msg,
@@ -869,7 +864,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{
- e.addTrace(std::nullopt, s, s2);
+ e.addTrace(nullptr, s, s2);
}
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
@@ -881,13 +876,13 @@ static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
EvalState & state,
Expr & expr,
Env & env,
- std::optional<ErrPos> pos,
+ std::shared_ptr<AbstractPos> && pos,
const char * s,
const std::string & s2)
{
return std::make_unique<DebugTraceStacker>(state,
DebugTrace {
- .pos = pos,
+ .pos = std::move(pos),
.expr = expr,
.env = env,
.hint = hintfmt(s, s2),
@@ -993,9 +988,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
void EvalState::mkPos(Value & v, PosIdx p)
{
auto pos = positions[p];
- if (!pos.file.empty()) {
+ if (auto path = std::get_if<Path>(&pos.origin)) {
auto attrs = buildBindings(3);
- attrs.alloc(sFile).mkString(pos.file);
+ attrs.alloc(sFile).mkString(*path);
attrs.alloc(sLine).mkInt(pos.line);
attrs.alloc(sColumn).mkInt(pos.column);
v.mkAttrs(attrs);
@@ -1103,7 +1098,7 @@ void EvalState::cacheFile(
*this,
*e,
this->baseEnv,
- e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt,
+ e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
"while evaluating the file '%1%':", resolvedPath)
: nullptr;
@@ -1373,10 +1368,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) );
} catch (Error & e) {
- auto pos2r = state.positions[pos2];
- if (pos2 && pos2r.file != state.derivationNixPath)
- state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
- showAttrPath(state, env, attrPath));
+ if (pos2) {
+ auto pos2r = state.positions[pos2];
+ auto origin = std::get_if<Path>(&pos2r.origin);
+ if (!(origin && *origin == state.derivationNixPath))
+ state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
+ showAttrPath(state, env, attrPath));
+ }
throw;
}
@@ -1519,7 +1517,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto dts = debugRepl
? makeDebugTraceStacker(
*this, *lambda.body, env2, positions[lambda.pos],
- "while evaluating %s",
+ "while calling %s",
lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda")
@@ -1528,13 +1526,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
- addErrorTrace(e,
- lambda.pos,
- "while evaluating %s",
- lambda.name
- ? concatStrings("'", symbols[lambda.name], "'")
- : "anonymous lambda",
- true);
+ addErrorTrace(
+ e,
+ lambda.pos,
+ "while calling %s",
+ lambda.name
+ ? concatStrings("'", symbols[lambda.name], "'")
+ : "anonymous lambda",
+ true);
if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
}
throw;
@@ -1704,7 +1703,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
-https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name])
+https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
}
}
@@ -2357,97 +2356,99 @@ void EvalState::printStats()
std::fstream fs;
if (outPath != "-")
fs.open(outPath, std::fstream::out);
- JSONObject topObj(outPath == "-" ? std::cerr : fs, true);
- topObj.attr("cpuTime",cpuTime);
- {
- auto envs = topObj.object("envs");
- envs.attr("number", nrEnvs);
- envs.attr("elements", nrValuesInEnvs);
- envs.attr("bytes", bEnvs);
- }
- {
- auto lists = topObj.object("list");
- lists.attr("elements", nrListElems);
- lists.attr("bytes", bLists);
- lists.attr("concats", nrListConcats);
- }
- {
- auto values = topObj.object("values");
- values.attr("number", nrValues);
- values.attr("bytes", bValues);
- }
- {
- auto syms = topObj.object("symbols");
- syms.attr("number", symbols.size());
- syms.attr("bytes", symbols.totalSize());
- }
- {
- auto sets = topObj.object("sets");
- sets.attr("number", nrAttrsets);
- sets.attr("bytes", bAttrsets);
- sets.attr("elements", nrAttrsInAttrsets);
- }
- {
- auto sizes = topObj.object("sizes");
- sizes.attr("Env", sizeof(Env));
- sizes.attr("Value", sizeof(Value));
- sizes.attr("Bindings", sizeof(Bindings));
- sizes.attr("Attr", sizeof(Attr));
- }
- topObj.attr("nrOpUpdates", nrOpUpdates);
- topObj.attr("nrOpUpdateValuesCopied", nrOpUpdateValuesCopied);
- topObj.attr("nrThunks", nrThunks);
- topObj.attr("nrAvoided", nrAvoided);
- topObj.attr("nrLookups", nrLookups);
- topObj.attr("nrPrimOpCalls", nrPrimOpCalls);
- topObj.attr("nrFunctionCalls", nrFunctionCalls);
+ json topObj = json::object();
+ topObj["cpuTime"] = cpuTime;
+ topObj["envs"] = {
+ {"number", nrEnvs},
+ {"elements", nrValuesInEnvs},
+ {"bytes", bEnvs},
+ };
+ topObj["list"] = {
+ {"elements", nrListElems},
+ {"bytes", bLists},
+ {"concats", nrListConcats},
+ };
+ topObj["values"] = {
+ {"number", nrValues},
+ {"bytes", bValues},
+ };
+ topObj["symbols"] = {
+ {"number", symbols.size()},
+ {"bytes", symbols.totalSize()},
+ };
+ topObj["sets"] = {
+ {"number", nrAttrsets},
+ {"bytes", bAttrsets},
+ {"elements", nrAttrsInAttrsets},
+ };
+ topObj["sizes"] = {
+ {"Env", sizeof(Env)},
+ {"Value", sizeof(Value)},
+ {"Bindings", sizeof(Bindings)},
+ {"Attr", sizeof(Attr)},
+ };
+ topObj["nrOpUpdates"] = nrOpUpdates;
+ topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
+ topObj["nrThunks"] = nrThunks;
+ topObj["nrAvoided"] = nrAvoided;
+ topObj["nrLookups"] = nrLookups;
+ topObj["nrPrimOpCalls"] = nrPrimOpCalls;
+ topObj["nrFunctionCalls"] = nrFunctionCalls;
#if HAVE_BOEHMGC
- {
- auto gc = topObj.object("gc");
- gc.attr("heapSize", heapSize);
- gc.attr("totalBytes", totalBytes);
- }
+ topObj["gc"] = {
+ {"heapSize", heapSize},
+ {"totalBytes", totalBytes},
+ };
#endif
if (countCalls) {
+ topObj["primops"] = primOpCalls;
{
- auto obj = topObj.object("primops");
- for (auto & i : primOpCalls)
- obj.attr(i.first, i.second);
- }
- {
- auto list = topObj.list("functions");
+ auto& list = topObj["functions"];
+ list = json::array();
for (auto & [fun, count] : functionCalls) {
- auto obj = list.object();
+ json obj = json::object();
if (fun->name)
- obj.attr("name", (std::string_view) symbols[fun->name]);
+ obj["name"] = (std::string_view) symbols[fun->name];
else
- obj.attr("name", nullptr);
+ obj["name"] = nullptr;
if (auto pos = positions[fun->pos]) {
- obj.attr("file", (std::string_view) pos.file);
- obj.attr("line", pos.line);
- obj.attr("column", pos.column);
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
+ obj["line"] = pos.line;
+ obj["column"] = pos.column;
}
- obj.attr("count", count);
+ obj["count"] = count;
+ list.push_back(obj);
}
}
{
- auto list = topObj.list("attributes");
+ auto list = topObj["attributes"];
+ list = json::array();
for (auto & i : attrSelects) {
- auto obj = list.object();
+ json obj = json::object();
if (auto pos = positions[i.first]) {
- obj.attr("file", (const std::string &) pos.file);
- obj.attr("line", pos.line);
- obj.attr("column", pos.column);
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
+ obj["line"] = pos.line;
+ obj["column"] = pos.column;
}
- obj.attr("count", i.second);
+ obj["count"] = i.second;
+ list.push_back(obj);
}
}
}
if (getEnv("NIX_SHOW_SYMBOLS").value_or("0") != "0") {
- auto list = topObj.list("symbols");
- symbols.dump([&](const std::string & s) { list.elem(s); });
+ // XXX: overrides earlier assignment
+ topObj["symbols"] = json::array();
+ auto &list = topObj["symbols"];
+ symbols.dump([&](const std::string & s) { list.emplace_back(s); });
+ }
+ if (outPath == "-") {
+ std::cerr << topObj.dump(2) << std::endl;
+ } else {
+ fs << topObj.dump(2) << std::endl;
}
}
}
@@ -2502,6 +2503,23 @@ Strings EvalSettings::getDefaultNixPath()
return res;
}
+bool EvalSettings::isPseudoUrl(std::string_view s)
+{
+ if (s.compare(0, 8, "channel:") == 0) return true;
+ size_t pos = s.find("://");
+ if (pos == std::string::npos) return false;
+ std::string scheme(s, 0, pos);
+ return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git" || scheme == "s3" || scheme == "ssh";
+}
+
+std::string EvalSettings::resolvePseudoUrl(std::string_view url)
+{
+ if (hasPrefix(url, "channel:"))
+ return "https://nixos.org/channels/" + std::string(url.substr(8)) + "/nixexprs.tar.xz";
+ else
+ return std::string(url);
+}
+
EvalSettings evalSettings;
static GlobalConfig::Register rEvalSettings(&evalSettings);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 1f610a02f..9b3d160ea 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -60,7 +60,6 @@ void copyContext(const Value & v, PathSet & context);
typedef std::map<Path, StorePath> SrcToStore;
-std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
@@ -78,7 +77,7 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace {
- std::optional<ErrPos> pos;
+ std::shared_ptr<AbstractPos> pos;
const Expr & expr;
const Env & env;
hintformat hint;
@@ -437,8 +436,12 @@ private:
friend struct ExprAttrs;
friend struct ExprLet;
- Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
- const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv);
+ Expr * parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv);
public:
@@ -570,6 +573,10 @@ struct EvalSettings : Config
static Strings getDefaultNixPath();
+ static bool isPseudoUrl(std::string_view s);
+
+ static std::string resolvePseudoUrl(std::string_view url);
+
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation",
"Whether builtin functions that allow executing native code should be enabled."};
diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix
index 932ac5e90..8061db3df 100644
--- a/src/libexpr/flake/call-flake.nix
+++ b/src/libexpr/flake/call-flake.nix
@@ -43,7 +43,7 @@ let
outputs = flake.outputs (inputs // { self = result; });
- result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
+ result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; };
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index 6df95f1f0..89ddbde7e 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -56,7 +56,7 @@ void ConfigFile::apply()
auto tlname = get(trustedList, name);
if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
trusted = *saved;
- warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
+ printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS);
} else {
// FIXME: filter ANSI escapes, newlines, \r, etc.
if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') {
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index a9f15b0f6..fc4be5678 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -143,7 +143,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
} catch (Error & e) {
e.addTrace(
state.positions[attr.pos],
- hintfmt("in flake attribute '%s'", state.symbols[attr.name]));
+ hintfmt("while evaluating flake attribute '%s'", state.symbols[attr.name]));
throw;
}
}
@@ -152,7 +152,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
try {
input.ref = FlakeRef::fromAttrs(attrs);
} catch (Error & e) {
- e.addTrace(state.positions[pos], hintfmt("in flake input"));
+ e.addTrace(state.positions[pos], hintfmt("while evaluating flake input"));
throw;
}
else {
@@ -220,7 +220,7 @@ static Flake getFlake(
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
- expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0));
+ expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1));
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, nString, *description->value, description->pos);
@@ -353,7 +353,7 @@ LockedFlake lockFlake(
std::function<void(
const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
+ ref<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath,
@@ -362,9 +362,15 @@ LockedFlake lockFlake(
computeLocks;
computeLocks = [&](
+ /* The inputs of this node, either from flake.nix or
+ flake.lock. */
const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
+ /* The node whose locks are to be updated.*/
+ ref<Node> node,
+ /* The path to this node in the lock file graph. */
const InputPath & inputPathPrefix,
+ /* The old node, if any, from which locks can be
+ copied. */
std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath,
const Path & parentPath,
@@ -452,7 +458,7 @@ LockedFlake lockFlake(
/* Copy the input from the old lock since its flakeref
didn't change and there is no override from a
higher level flake. */
- auto childNode = std::make_shared<LockedNode>(
+ auto childNode = make_ref<LockedNode>(
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
node->inputs.insert_or_assign(id, childNode);
@@ -481,7 +487,7 @@ LockedFlake lockFlake(
.isFlake = (*lockedNode)->isFlake,
});
} else if (auto follows = std::get_if<1>(&i.second)) {
- if (! trustLock) {
+ if (!trustLock) {
// It is possible that the flake has changed,
// so we must confirm all the follows that are in the lock file are also in the flake.
auto overridePath(inputPath);
@@ -521,8 +527,8 @@ LockedFlake lockFlake(
this input. */
debug("creating new input '%s'", inputPathS);
- if (!lockFlags.allowMutable && !input.ref->input.isLocked())
- throw Error("cannot update flake input '%s' in pure mode", inputPathS);
+ if (!lockFlags.allowUnlocked && !input.ref->input.isLocked())
+ throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS);
/* Note: in case of an --override-input, we use
the *original* ref (input2.ref) for the
@@ -544,7 +550,7 @@ LockedFlake lockFlake(
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
- auto childNode = std::make_shared<LockedNode>(inputFlake.lockedRef, ref);
+ auto childNode = make_ref<LockedNode>(inputFlake.lockedRef, ref);
node->inputs.insert_or_assign(id, childNode);
@@ -564,15 +570,19 @@ LockedFlake lockFlake(
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
- inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
- oldLock ? lockRootPath : inputPath, localPath, false);
+ inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(),
+ oldLock ? lockRootPath : inputPath,
+ localPath,
+ false);
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);
- node->inputs.insert_or_assign(id,
- std::make_shared<LockedNode>(lockedRef, ref, false));
+
+ auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
+
+ node->inputs.insert_or_assign(id, childNode);
}
}
@@ -587,8 +597,13 @@ LockedFlake lockFlake(
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks(
- flake.inputs, newLockFile.root, {},
- lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false);
+ flake.inputs,
+ newLockFile.root,
+ {},
+ lockFlags.recreateLockFile ? nullptr : oldLockFile.root.get_ptr(),
+ {},
+ parentPath,
+ false);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
@@ -611,9 +626,9 @@ LockedFlake lockFlake(
if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) {
- if (!newLockFile.isImmutable()) {
+ if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty)
- warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
+ warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
@@ -737,7 +752,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
- .allowMutable = !evalSettings.pureEval,
+ .allowUnlocked = !evalSettings.pureEval,
}),
v);
}
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index 524b18af1..10301d8aa 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -108,11 +108,11 @@ struct LockFlags
bool applyNixConfig = false;
- /* Whether mutable flake references (i.e. those without a Git
+ /* Whether unlocked flake references (i.e. those without a Git
revision or similar) without a corresponding lock are
- allowed. Mutable flake references with a lock are always
+ allowed. Unlocked flake references with a lock are always
allowed. */
- bool allowMutable = true;
+ bool allowUnlocked = true;
/* Whether to commit changes to flake.lock. */
bool commitLockFile = false;
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index fe4f67193..a36d852a8 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -35,7 +35,7 @@ typedef std::string FlakeId;
struct FlakeRef
{
- /* fetcher-specific representation of the input, sufficient to
+ /* Fetcher-specific representation of the input, sufficient to
perform the fetch operation. */
fetchers::Input input;
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index 629d2e669..a3ed90e1f 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -31,7 +31,7 @@ FlakeRef getFlakeRef(
}
LockedNode::LockedNode(const nlohmann::json & json)
- : lockedRef(getFlakeRef(json, "locked", "info"))
+ : lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info"
, originalRef(getFlakeRef(json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
@@ -49,15 +49,15 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{
auto pos = root;
- if (!pos) return {};
-
for (auto & elem : path) {
if (auto i = get(pos->inputs, elem)) {
if (auto node = std::get_if<0>(&*i))
pos = *node;
else if (auto follows = std::get_if<1>(&*i)) {
- pos = findInput(*follows);
- if (!pos) return {};
+ if (auto p = findInput(*follows))
+ pos = ref(p);
+ else
+ return {};
}
} else
return {};
@@ -72,7 +72,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
if (version < 5 || version > 7)
throw Error("lock file '%s' has unsupported version %d", path, version);
- std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap;
+ std::map<std::string, ref<Node>> nodeMap;
std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs;
@@ -93,12 +93,12 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
auto jsonNode2 = nodes.find(inputKey);
if (jsonNode2 == nodes.end())
throw Error("lock file references missing node '%s'", inputKey);
- auto input = std::make_shared<LockedNode>(*jsonNode2);
+ auto input = make_ref<LockedNode>(*jsonNode2);
k = nodeMap.insert_or_assign(inputKey, input).first;
getInputs(*input, *jsonNode2);
}
- if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second))
- node.inputs.insert_or_assign(i.key(), child);
+ if (auto child = k->second.dynamic_pointer_cast<LockedNode>())
+ node.inputs.insert_or_assign(i.key(), ref(child));
else
// FIXME: replace by follows node
throw Error("lock file contains cycle to root node");
@@ -122,9 +122,9 @@ nlohmann::json LockFile::toJSON() const
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
std::unordered_set<std::string> keys;
- std::function<std::string(const std::string & key, std::shared_ptr<const Node> node)> dumpNode;
+ std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
- dumpNode = [&](std::string key, std::shared_ptr<const Node> node) -> std::string
+ dumpNode = [&](std::string key, ref<const Node> node) -> std::string
{
auto k = nodeKeys.find(node);
if (k != nodeKeys.end())
@@ -159,10 +159,11 @@ nlohmann::json LockFile::toJSON() const
n["inputs"] = std::move(inputs);
}
- if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
+ if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) {
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
- if (!lockedNode->isFlake) n["flake"] = false;
+ if (!lockedNode->isFlake)
+ n["flake"] = false;
}
nodes[key] = std::move(n);
@@ -201,13 +202,13 @@ void LockFile::write(const Path & path) const
writeFile(path, fmt("%s\n", *this));
}
-bool LockFile::isImmutable() const
+std::optional<FlakeRef> LockFile::isUnlocked() const
{
- std::unordered_set<std::shared_ptr<const Node>> nodes;
+ std::set<ref<const Node>> nodes;
- std::function<void(std::shared_ptr<const Node> node)> visit;
+ std::function<void(ref<const Node> node)> visit;
- visit = [&](std::shared_ptr<const Node> node)
+ visit = [&](ref<const Node> node)
{
if (!nodes.insert(node).second) return;
for (auto & i : node->inputs)
@@ -219,11 +220,12 @@ bool LockFile::isImmutable() const
for (auto & i : nodes) {
if (i == root) continue;
- auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
- if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false;
+ auto node = i.dynamic_pointer_cast<const LockedNode>();
+ if (node && !node->lockedRef.input.isLocked())
+ return node->lockedRef;
}
- return true;
+ return {};
}
bool LockFile::operator ==(const LockFile & other) const
@@ -247,12 +249,12 @@ InputPath parseInputPath(std::string_view s)
std::map<InputPath, Node::Edge> LockFile::getAllInputs() const
{
- std::unordered_set<std::shared_ptr<Node>> done;
+ std::set<ref<Node>> done;
std::map<InputPath, Node::Edge> res;
- std::function<void(const InputPath & prefix, std::shared_ptr<Node> node)> recurse;
+ std::function<void(const InputPath & prefix, ref<Node> node)> recurse;
- recurse = [&](const InputPath & prefix, std::shared_ptr<Node> node)
+ recurse = [&](const InputPath & prefix, ref<Node> node)
{
if (!done.insert(node).second) return;
diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh
index 96f1edc76..02e9bdfbc 100644
--- a/src/libexpr/flake/lockfile.hh
+++ b/src/libexpr/flake/lockfile.hh
@@ -20,7 +20,7 @@ struct LockedNode;
type LockedNode. */
struct Node : std::enable_shared_from_this<Node>
{
- typedef std::variant<std::shared_ptr<LockedNode>, InputPath> Edge;
+ typedef std::variant<ref<LockedNode>, InputPath> Edge;
std::map<FlakeId, Edge> inputs;
@@ -47,11 +47,13 @@ struct LockedNode : Node
struct LockFile
{
- std::shared_ptr<Node> root = std::make_shared<Node>();
+ ref<Node> root = make_ref<Node>();
LockFile() {};
LockFile(const nlohmann::json & json, const Path & path);
+ typedef std::map<ref<const Node>, std::string> KeyMap;
+
nlohmann::json toJSON() const;
std::string to_string() const;
@@ -60,7 +62,8 @@ struct LockFile
void write(const Path & path) const;
- bool isImmutable() const;
+ /* Check whether this lock file has any unlocked inputs. */
+ std::optional<FlakeRef> isUnlocked() const;
bool operator ==(const LockFile & other) const;
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 544f8efc4..1602fbffb 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -150,7 +150,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
const Value * outTI = queryMeta("outputsToInstall");
if (!outTI) return outputs;
- const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
+ auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
/* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw errMsg;
Outputs result;
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 7c623a07d..eb6f062b4 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -8,6 +8,58 @@
namespace nix {
+struct PosAdapter : AbstractPos
+{
+ Pos::Origin origin;
+
+ PosAdapter(Pos::Origin origin)
+ : origin(std::move(origin))
+ {
+ }
+
+ std::optional<std::string> getSource() const override
+ {
+ return std::visit(overloaded {
+ [](const Pos::none_tag &) -> std::optional<std::string> {
+ return std::nullopt;
+ },
+ [](const Pos::Stdin & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Pos::String & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Path & path) -> std::optional<std::string> {
+ try {
+ return readFile(path);
+ } catch (Error &) {
+ return std::nullopt;
+ }
+ }
+ }, origin);
+ }
+
+ void print(std::ostream & out) const override
+ {
+ std::visit(overloaded {
+ [&](const Pos::none_tag &) { out << "«none»"; },
+ [&](const Pos::Stdin &) { out << "«stdin»"; },
+ [&](const Pos::String & s) { out << "«string»"; },
+ [&](const Path & path) { out << path; }
+ }, origin);
+ }
+};
+
+Pos::operator std::shared_ptr<AbstractPos>() const
+{
+ auto pos = std::make_shared<PosAdapter>(origin);
+ pos->line = line;
+ pos->column = column;
+ return pos;
+}
+
/* Displaying abstract syntax trees. */
static void showString(std::ostream & str, std::string_view s)
@@ -248,24 +300,10 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
std::ostream & operator << (std::ostream & str, const Pos & pos)
{
- if (!pos)
+ if (auto pos2 = (std::shared_ptr<AbstractPos>) pos) {
+ str << *pos2;
+ } else
str << "undefined position";
- else
- {
- auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%");
- switch (pos.origin) {
- case foFile:
- f % (const std::string &) pos.file;
- break;
- case foStdin:
- case foString:
- f % "(string)";
- break;
- default:
- throw Error("unhandled Pos origin!");
- }
- str << (f % pos.line % pos.column).str();
- }
return str;
}
@@ -289,7 +327,6 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
}
-
/* Computing levels/displacements for variables. */
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index ea0ce1a7f..ffe67f97d 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -22,15 +22,22 @@ MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
/* Position objects. */
-
struct Pos
{
- std::string file;
- FileOrigin origin;
uint32_t line;
uint32_t column;
+ struct none_tag { };
+ struct Stdin { ref<std::string> source; };
+ struct String { ref<std::string> source; };
+
+ typedef std::variant<none_tag, Stdin, String, Path> Origin;
+
+ Origin origin;
+
explicit operator bool() const { return line > 0; }
+
+ operator std::shared_ptr<AbstractPos>() const;
};
class PosIdx {
@@ -46,7 +53,11 @@ public:
explicit operator bool() const { return id > 0; }
- bool operator<(const PosIdx other) const { return id < other.id; }
+ bool operator <(const PosIdx other) const { return id < other.id; }
+
+ bool operator ==(const PosIdx other) const { return id == other.id; }
+
+ bool operator !=(const PosIdx other) const { return id != other.id; }
};
class PosTable
@@ -60,13 +71,13 @@ public:
// current origins.back() can be reused or not.
mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
- explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {}
+ // Used for searching in PosTable::[].
+ explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {}
public:
- const std::string file;
- const FileOrigin origin;
+ const Pos::Origin origin;
- Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {}
+ Origin(Pos::Origin origin): origin(origin) {}
};
struct Offset {
@@ -106,7 +117,7 @@ public:
[] (const auto & a, const auto & b) { return a.idx < b.idx; });
const auto origin = *std::prev(pastOrigin);
const auto offset = offsets[idx];
- return {origin.file, origin.origin, offset.line, offset.column};
+ return {offset.line, offset.column, origin.origin};
}
};
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 0cf0d56f0..ffb364a90 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -34,11 +34,6 @@ namespace nix {
Path basePath;
PosTable::Origin origin;
std::optional<ErrorInfo> error;
- ParseData(EvalState & state, PosTable::Origin origin)
- : state(state)
- , symbols(state.symbols)
- , origin(std::move(origin))
- { };
};
struct ParserFormals {
@@ -643,29 +638,26 @@ formal
#include "filetransfer.hh"
#include "fetchers.hh"
#include "store-api.hh"
+#include "flake/flake.hh"
namespace nix {
-Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
- const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv)
+Expr * EvalState::parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
- std::string file;
- switch (origin) {
- case foFile:
- file = path;
- break;
- case foStdin:
- case foString:
- file = text;
- break;
- default:
- assert(false);
- }
- ParseData data(*this, {file, origin});
- data.basePath = basePath;
+ ParseData data {
+ .state = *this,
+ .symbols = symbols,
+ .basePath = std::move(basePath),
+ .origin = {origin},
+ };
yylex_init(&scanner);
yy_scan_buffer(text, length, scanner);
@@ -717,14 +709,15 @@ Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv
auto buffer = readFile(path);
// readFile should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foFile, path, dirOf(path), staticEnv);
+ return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv);
}
-Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
+Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
- s.append("\0\0", 2);
- return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
+ auto s = make_ref<std::string>(std::move(s_));
+ s->append("\0\0", 2);
+ return parse(s->data(), s->size(), Pos::String{.source = s}, basePath, staticEnv);
}
@@ -740,7 +733,8 @@ Expr * EvalState::parseStdin()
auto buffer = drainFD(0);
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foStdin, "", absPath("."), staticBaseEnv);
+ auto s = make_ref<std::string>(std::move(buffer));
+ return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv);
}
@@ -805,17 +799,28 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
std::pair<bool, std::string> res;
- if (isUri(elem.second)) {
+ if (EvalSettings::isPseudoUrl(elem.second)) {
try {
- res = { true, store->toRealPath(fetchers::downloadTarball(
- store, resolveUri(elem.second), "source", false).first.storePath) };
+ auto storePath = fetchers::downloadTarball(
+ store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath;
+ res = { true, store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
});
res = { false, "" };
}
- } else {
+ }
+
+ else if (hasPrefix(elem.second, "flake:")) {
+ settings.requireExperimentalFeature(Xp::Flakes);
+ auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false);
+ debug("fetching flake search path element '%s''", elem.second);
+ auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
+ res = { true, store->toRealPath(storePath) };
+ }
+
+ else {
auto path = absPath(elem.second);
if (pathExists(path))
res = { true, path };
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index b95f54851..5142d79e8 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -5,14 +5,15 @@
#include "globals.hh"
#include "json-to-value.hh"
#include "names.hh"
+#include "references.hh"
#include "store-api.hh"
#include "util.hh"
-#include "json.hh"
#include "value-to-json.hh"
#include "value-to-xml.hh"
#include "primops.hh"
#include <boost/container/small_vector.hpp>
+#include <nlohmann/json.hpp>
#include <sys/types.h>
#include <sys/stat.h>
@@ -361,8 +362,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
- auto base = state.positions[pos];
- parsed = state.parseExprFromString(std::move(output), base.file);
+ parsed = state.parseExprFromString(std::move(output), "/");
} catch (Error & e) {
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw;
@@ -585,7 +585,7 @@ struct CompareValues
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
}
} catch (Error & e) {
- e.addTrace(std::nullopt, errorCtx);
+ e.addTrace(nullptr, errorCtx);
throw;
}
}
@@ -788,8 +788,8 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
v = *args[1];
} catch (Error & e) {
PathSet context;
- e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context,
- "while evaluating the error message passed to builtins.addErrorContext").toOwned());
+ e.addTrace(nullptr, state.coerceToString(pos, *args[0], context,
+ "while evaluating the error message passed to builtins.addErrorContext").toOwned());
throw;
}
}
@@ -1003,6 +1003,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
derivation. */
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
+ using nlohmann::json;
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
/* Figure out the name first (for stack backtraces). */
@@ -1018,11 +1019,10 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
}
/* Check whether attributes should be passed as a JSON file. */
- std::ostringstream jsonBuf;
- std::unique_ptr<JSONObject> jsonObject;
+ std::optional<json> jsonObject;
attr = args[0]->attrs->find(state.sStructuredAttrs);
if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"))
- jsonObject = std::make_unique<JSONObject>(jsonBuf);
+ jsonObject = json::object();
/* Check whether null attributes should be ignored. */
bool ignoreNulls = false;
@@ -1128,8 +1128,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (i->name == state.sStructuredAttrs) continue;
- auto placeholder(jsonObject->placeholder(key));
- printValueAsJSON(state, true, *i->value, pos, placeholder, context);
+ (*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
if (i->name == state.sBuilder)
drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict");
@@ -1173,8 +1172,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
}
if (jsonObject) {
+ drv.env.emplace("__json", jsonObject->dump());
jsonObject.reset();
- drv.env.emplace("__json", jsonBuf.str());
}
/* Everything in the context of the strings in the derivation
@@ -1452,10 +1451,10 @@ static RegisterPrimOp primop_storePath({
static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
/* We don’t check the path right now, because we don’t want to
- throw if the path isn’t allowed, but just return false (and we
- can’t just catch the exception here because we still want to
- throw if something in the evaluation of `*args[0]` tries to
- access an unauthorized path). */
+ throw if the path isn’t allowed, but just return false (and we
+ can’t just catch the exception here because we still want to
+ throw if something in the evaluation of `*args[0]` tries to
+ access an unauthorized path). */
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try {
@@ -1533,6 +1532,10 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references;
} catch (Error &) { // FIXME: should be InvalidPathError
}
+ // Re-scan references to filter down to just the ones that actually occur in the file.
+ auto refsSink = PathRefScanSink::fromPaths(refs);
+ refsSink << s;
+ refs = refsSink.getResultPaths();
}
auto context = state.store->printStorePathSet(refs);
v.mkString(s, context);
@@ -1913,8 +1916,8 @@ static RegisterPrimOp primop_toFile({
";
```
- Note that `${configFile}` is an
- [antiquotation](language-values.md), so the result of the
+ Note that `${configFile}` is a
+ [string interpolation](language/values.md#type-string), so the result of the
expression `configFile`
(i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be
spliced into the resulting string.
@@ -2379,12 +2382,18 @@ static RegisterPrimOp primop_listToAttrs({
Construct a set from a list specifying the names and values of each
attribute. Each element of the list should be a set consisting of a
string-valued attribute `name` specifying the name of the attribute,
- and an attribute `value` specifying its value. Example:
+ and an attribute `value` specifying its value.
+
+ In case of duplicate occurrences of the same name, the first
+ takes precedence.
+
+ Example:
```nix
builtins.listToAttrs
[ { name = "foo"; value = 123; }
{ name = "bar"; value = 456; }
+ { name = "bar"; value = 420; }
]
```
@@ -2402,12 +2411,62 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * a
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.intersectAttrs");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs");
- auto attrs = state.buildBindings(std::min(args[0]->attrs->size(), args[1]->attrs->size()));
-
- for (auto & i : *args[0]->attrs) {
- Bindings::iterator j = args[1]->attrs->find(i.name);
- if (j != args[1]->attrs->end())
- attrs.insert(*j);
+ Bindings &left = *args[0]->attrs;
+ Bindings &right = *args[1]->attrs;
+
+ auto attrs = state.buildBindings(std::min(left.size(), right.size()));
+
+ // The current implementation has good asymptotic complexity and is reasonably
+ // simple. Further optimization may be possible, but does not seem productive,
+ // considering the state of eval performance in 2022.
+ //
+ // I have looked for reusable and/or standard solutions and these are my
+ // findings:
+ //
+ // STL
+ // ===
+ // std::set_intersection is not suitable, as it only performs a simultaneous
+ // linear scan; not taking advantage of random access. This is O(n + m), so
+ // linear in the largest set, which is not acceptable for callPackage in Nixpkgs.
+ //
+ // Simultaneous scan, with alternating simple binary search
+ // ===
+ // One alternative algorithm scans the attrsets simultaneously, jumping
+ // forward using `lower_bound` in case of inequality. This should perform
+ // well on very similar sets, having a local and predictable access pattern.
+ // On dissimilar sets, it seems to need more comparisons than the current
+ // algorithm, as few consecutive attrs match. `lower_bound` could take
+ // advantage of the decreasing remaining search space, but this causes
+ // the medians to move, which can mean that they don't stay in the cache
+ // like they would with the current naive `find`.
+ //
+ // Double binary search
+ // ===
+ // The optimal algorithm may be "Double binary search", which doesn't
+ // scan at all, but rather divides both sets simultaneously.
+ // See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al.
+ // https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf
+ // The only downsides I can think of are not having a linear access pattern
+ // for similar sets, and having to maintain a more intricate algorithm.
+ //
+ // Adaptive
+ // ===
+ // Finally one could run try a simultaneous scan, count misses and fall back
+ // to double binary search when the counter hit some threshold and/or ratio.
+
+ if (left.size() < right.size()) {
+ for (auto & l : left) {
+ Bindings::iterator r = right.find(l.name);
+ if (r != right.end())
+ attrs.insert(*r);
+ }
+ }
+ else {
+ for (auto & r : right) {
+ Bindings::iterator l = left.find(r.name);
+ if (l != left.end())
+ attrs.insert(r);
+ }
}
v.mkAttrs(attrs.alreadySorted());
@@ -2419,6 +2478,8 @@ static RegisterPrimOp primop_intersectAttrs({
.doc = R"(
Return a set consisting of the attributes in the set *e2* which have the
same name as some attribute in *e1*.
+
+ Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size.
)",
.fun = prim_intersectAttrs,
});
@@ -3999,7 +4060,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), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation);
+ eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
}
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index ddbaae449..1fb480089 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -218,8 +218,6 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
} else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
- url = resolveUri(*url);
-
state.checkURI(*url);
if (name == "")
diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh
index 4f6915882..03e468fbb 100644
--- a/src/libexpr/tests/libexprtests.hh
+++ b/src/libexpr/tests/libexprtests.hh
@@ -12,6 +12,7 @@ namespace nix {
class LibExprTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
+ initLibStore();
initGC();
}
@@ -123,7 +124,7 @@ namespace nix {
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
if (arg.type() != nAttrs) {
- *result_listener << "Expexted set got " << arg.type();
+ *result_listener << "Expected set got " << arg.type();
return false;
} else if (arg.attrs->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc
index a4102753c..9cdcf64a1 100644
--- a/src/libexpr/tests/primops.cc
+++ b/src/libexpr/tests/primops.cc
@@ -151,20 +151,7 @@ namespace nix {
// The `y` attribute is at position
const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
auto v = eval(expr);
- ASSERT_THAT(v, IsAttrsOfSize(3));
-
- auto file = v.attrs->find(createSymbol("file"));
- ASSERT_NE(file, nullptr);
- // FIXME: The file when running these tests is the input string?!?
- ASSERT_THAT(*file->value, IsStringEq(expr));
-
- auto line = v.attrs->find(createSymbol("line"));
- ASSERT_NE(line, nullptr);
- ASSERT_THAT(*line->value, IsIntEq(1));
-
- auto column = v.attrs->find(createSymbol("column"));
- ASSERT_NE(column, nullptr);
- ASSERT_THAT(*column->value, IsIntEq(33));
+ ASSERT_THAT(v, IsNull());
}
TEST_F(PrimOpTest, hasAttr) {
@@ -617,7 +604,7 @@ namespace nix {
TEST_F(PrimOpTest, storeDir) {
auto v = eval("builtins.storeDir");
- ASSERT_THAT(v, IsStringEq("/nix/store"));
+ ASSERT_THAT(v, IsStringEq(settings.nixStore));
}
TEST_F(PrimOpTest, nixVersion) {
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index 4d63d8b49..5dc453b2e 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -1,84 +1,82 @@
#include "value-to-json.hh"
-#include "json.hh"
#include "eval-inline.hh"
#include "util.hh"
#include <cstdlib>
#include <iomanip>
+#include <nlohmann/json.hpp>
namespace nix {
-
-void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore)
+using json = nlohmann::json;
+json printValueAsJSON(EvalState & state, bool strict,
+ Value & v, const PosIdx pos, PathSet & context, bool copyToStore)
{
checkInterrupt();
if (strict) state.forceValue(v, pos);
+ json out;
+
switch (v.type()) {
case nInt:
- out.write(v.integer);
+ out = v.integer;
break;
case nBool:
- out.write(v.boolean);
+ out = v.boolean;
break;
case nString:
copyContext(v, context);
- out.write(v.string.s);
+ out = v.string.s;
break;
case nPath:
if (copyToStore)
- out.write(state.copyPathToStore(context, v.path));
+ out = state.copyPathToStore(context, v.path);
else
- out.write(v.path);
+ out = v.path;
break;
case nNull:
- out.write(nullptr);
break;
case nAttrs: {
auto maybeString = state.tryAttrsToString(pos, v, context, false, false);
if (maybeString) {
- out.write(*maybeString);
+ out = *maybeString;
break;
}
auto i = v.attrs->find(state.sOutPath);
if (i == v.attrs->end()) {
- auto obj(out.object());
+ out = json::object();
StringSet names;
for (auto & j : *v.attrs)
names.emplace(state.symbols[j.name]);
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
- auto placeholder(obj.placeholder(j));
- printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context, copyToStore);
+ out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
}
} else
- printValueAsJSON(state, strict, *i->value, i->pos, out, context, copyToStore);
+ return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
break;
}
case nList: {
- auto list(out.list());
- for (auto elem : v.listItems()) {
- auto placeholder(list.placeholder());
- printValueAsJSON(state, strict, *elem, pos, placeholder, context, copyToStore);
- }
+ out = json::array();
+ for (auto elem : v.listItems())
+ out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
break;
}
case nExternal:
- v.external->printValueAsJSON(state, strict, out, context, copyToStore);
+ return v.external->printValueAsJSON(state, strict, context, copyToStore);
break;
case nFloat:
- out.write(v.fpoint);
+ out = v.fpoint;
break;
case nThunk:
@@ -91,17 +89,17 @@ void printValueAsJSON(EvalState & state, bool strict,
state.debugThrowLastTrace(e);
throw e;
}
+ return out;
}
void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
{
- JSONPlaceholder out(str);
- printValueAsJSON(state, strict, v, pos, out, context, copyToStore);
+ str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
}
-void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context, bool copyToStore) const
+json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
+ PathSet & context, bool copyToStore) const
{
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
}
diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh
index 7ddc8a5b1..22f26b790 100644
--- a/src/libexpr/value-to-json.hh
+++ b/src/libexpr/value-to-json.hh
@@ -5,13 +5,12 @@
#include <string>
#include <map>
+#include <nlohmann/json_fwd.hpp>
namespace nix {
-class JSONPlaceholder;
-
-void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore = true);
+nlohmann::json printValueAsJSON(EvalState & state, bool strict,
+ Value & v, const PosIdx pos, PathSet & context, bool copyToStore = true);
void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index 7c3bf9492..3f6222768 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -24,7 +24,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
- xmlAttrs["path"] = pos.file;
+ if (auto path = std::get_if<Path>(&pos.origin))
+ xmlAttrs["path"] = *path;
xmlAttrs["line"] = (format("%1%") % pos.line).str();
xmlAttrs["column"] = (format("%1%") % pos.column).str();
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index a428bc565..f57597cff 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -7,6 +7,7 @@
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
#endif
+#include <nlohmann/json_fwd.hpp>
namespace nix {
@@ -62,7 +63,6 @@ class StorePath;
class Store;
class EvalState;
class XMLWriter;
-class JSONPlaceholder;
typedef int64_t NixInt;
@@ -98,8 +98,8 @@ class ExternalValueBase
virtual bool operator ==(const ExternalValueBase & b) const;
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
- virtual void printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context, bool copyToStore = true) const;
+ virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict,
+ PathSet & context, bool copyToStore = true) const;
/* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location,