mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 13:06:01 +01:00
Merge remote-tracking branch 'origin/master' into relative-flakes
This commit is contained in:
commit
0b00bf7c09
869 changed files with 5358 additions and 7769 deletions
|
|
@ -10,6 +10,9 @@ lockFileStr:
|
|||
# unlocked trees.
|
||||
overrides:
|
||||
|
||||
# This is `prim_fetchFinalTree`.
|
||||
fetchTreeFinal:
|
||||
|
||||
let
|
||||
|
||||
lockFile = builtins.fromJSON lockFileStr;
|
||||
|
|
@ -51,7 +54,8 @@ let
|
|||
}
|
||||
else
|
||||
# FIXME: remove obsolete node.info.
|
||||
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||
# Note: lock file entries are always final.
|
||||
fetchTreeFinal (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||
|
||||
subdir = overrides.${key}.dir or node.locked.dir or "";
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ struct AttrDb
|
|||
state->txn->commit();
|
||||
state->txn.reset();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
ignoreExceptionInDestructor();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ struct AttrDb
|
|||
try {
|
||||
return fun();
|
||||
} catch (SQLiteError &) {
|
||||
ignoreException();
|
||||
ignoreExceptionExceptInterrupt();
|
||||
failed = true;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -351,7 +351,7 @@ static std::shared_ptr<AttrDb> makeAttrDb(
|
|||
try {
|
||||
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
|
||||
} catch (SQLiteError &) {
|
||||
ignoreException();
|
||||
ignoreExceptionExceptInterrupt();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#else
|
||||
|
||||
# include <memory>
|
||||
|
||||
/* Some dummy aliases for Boehm GC definitions to reduce the number of
|
||||
#ifdefs. */
|
||||
|
||||
|
|
@ -23,7 +25,6 @@ template<typename T>
|
|||
using gc_allocator = std::allocator<T>;
|
||||
|
||||
# define GC_MALLOC_ATOMIC std::malloc
|
||||
# define GC_STRDUP strdup
|
||||
|
||||
struct gc
|
||||
{};
|
||||
|
|
|
|||
|
|
@ -87,11 +87,15 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
|||
{
|
||||
if (v.isThunk()) {
|
||||
Env * env = v.payload.thunk.env;
|
||||
assert(env || v.isBlackhole());
|
||||
Expr * expr = v.payload.thunk.expr;
|
||||
try {
|
||||
v.mkBlackhole();
|
||||
//checkInterrupt();
|
||||
expr->eval(*this, *env, v);
|
||||
if (env) [[likely]]
|
||||
expr->eval(*this, *env, v);
|
||||
else
|
||||
ExprBlackHole::throwInfiniteRecursionError(*this, v);
|
||||
} catch (...) {
|
||||
v.mkThunk(env, expr);
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
#include "config.hh"
|
||||
#include "ref.hh"
|
||||
#include "source-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
class EvalState;
|
||||
|
||||
struct EvalSettings : Config
|
||||
{
|
||||
|
|
@ -18,11 +19,8 @@ struct EvalSettings : Config
|
|||
*
|
||||
* The return value is (a) whether the entry was valid, and, if so,
|
||||
* what does it map to.
|
||||
*
|
||||
* @todo Return (`std::optional` of) `SourceAccssor` or something
|
||||
* more structured instead of mere `std::string`?
|
||||
*/
|
||||
using LookupPathHook = std::optional<std::string>(ref<Store> store, std::string_view);
|
||||
using LookupPathHook = std::optional<SourcePath>(EvalState & state, std::string_view);
|
||||
|
||||
/**
|
||||
* Map from "scheme" to a `LookupPathHook`.
|
||||
|
|
|
|||
|
|
@ -53,15 +53,6 @@ static char * allocString(size_t size)
|
|||
}
|
||||
|
||||
|
||||
static char * dupString(const char * s)
|
||||
{
|
||||
char * t;
|
||||
t = GC_STRDUP(s);
|
||||
if (!t) throw std::bad_alloc();
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
// When there's no need to write to the string, we can optimize away empty
|
||||
// string allocations.
|
||||
// This function handles makeImmutableString(std::string_view()) by returning
|
||||
|
|
@ -457,7 +448,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
|
|||
/* Install value the base environment. */
|
||||
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v));
|
||||
getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -519,16 +510,32 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
|||
|
||||
Value * v = allocValue();
|
||||
v->mkPrimOp(new PrimOp(primOp));
|
||||
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||
|
||||
if (primOp.internal)
|
||||
internalPrimOps.emplace(primOp.name, v);
|
||||
else {
|
||||
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
Value & EvalState::getBuiltins()
|
||||
{
|
||||
return *baseEnv.values[0];
|
||||
}
|
||||
|
||||
|
||||
Value & EvalState::getBuiltin(const std::string & name)
|
||||
{
|
||||
return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value;
|
||||
auto it = getBuiltins().attrs()->get(symbols.create(name));
|
||||
if (it)
|
||||
return *it->value;
|
||||
else
|
||||
error<EvalError>("builtin '%1%' not found", name).debugThrow();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -548,7 +555,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
|||
if (v.isLambda()) {
|
||||
auto exprLambda = v.payload.lambda.fun;
|
||||
|
||||
std::stringstream s(std::ios_base::out);
|
||||
std::ostringstream s;
|
||||
std::string name;
|
||||
auto pos = positions[exprLambda->getPos()];
|
||||
std::string docStr;
|
||||
|
|
@ -580,30 +587,25 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
|||
|
||||
s << docStr;
|
||||
|
||||
s << '\0'; // for making a c string below
|
||||
std::string ss = s.str();
|
||||
|
||||
return Doc {
|
||||
.pos = pos,
|
||||
.name = name,
|
||||
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
|
||||
.args = {},
|
||||
.doc =
|
||||
// FIXME: this leaks; make the field std::string?
|
||||
strdup(ss.data()),
|
||||
.doc = makeImmutableString(toView(s)), // NOTE: memory leak when compiled without GC
|
||||
};
|
||||
}
|
||||
if (isFunctor(v)) {
|
||||
try {
|
||||
Value & functor = *v.attrs()->find(sFunctor)->value;
|
||||
Value * vp = &v;
|
||||
Value * vp[] = {&v};
|
||||
Value partiallyApplied;
|
||||
// The first paramater is not user-provided, and may be
|
||||
// handled by code that is opaque to the user, like lib.const = x: y: y;
|
||||
// So preferably we show docs that are relevant to the
|
||||
// "partially applied" function returned by e.g. `const`.
|
||||
// We apply the first argument:
|
||||
callFunction(functor, 1, &vp, partiallyApplied, noPos);
|
||||
callFunction(functor, vp, partiallyApplied, noPos);
|
||||
auto _level = addCallDepth(noPos);
|
||||
return getDoc(partiallyApplied);
|
||||
}
|
||||
|
|
@ -832,9 +834,10 @@ static const char * * encodeContext(const NixStringContext & context)
|
|||
size_t n = 0;
|
||||
auto ctx = (const char * *)
|
||||
allocBytes((context.size() + 1) * sizeof(char *));
|
||||
for (auto & i : context)
|
||||
ctx[n++] = dupString(i.to_string().c_str());
|
||||
ctx[n] = 0;
|
||||
for (auto & i : context) {
|
||||
ctx[n++] = makeImmutableString({i.to_string()});
|
||||
}
|
||||
ctx[n] = nullptr;
|
||||
return ctx;
|
||||
} else
|
||||
return nullptr;
|
||||
|
|
@ -1467,7 +1470,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
|||
v.mkLambda(&env, this);
|
||||
}
|
||||
|
||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||
void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
auto _level = addCallDepth(pos);
|
||||
|
||||
|
|
@ -1482,16 +1485,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
auto makeAppChain = [&]()
|
||||
{
|
||||
vRes = vCur;
|
||||
for (size_t i = 0; i < nrArgs; ++i) {
|
||||
for (auto arg : args) {
|
||||
auto fun2 = allocValue();
|
||||
*fun2 = vRes;
|
||||
vRes.mkPrimOpApp(fun2, args[i]);
|
||||
vRes.mkPrimOpApp(fun2, arg);
|
||||
}
|
||||
};
|
||||
|
||||
const Attr * functor;
|
||||
|
||||
while (nrArgs > 0) {
|
||||
while (args.size() > 0) {
|
||||
|
||||
if (vCur.isLambda()) {
|
||||
|
||||
|
|
@ -1594,15 +1597,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
throw;
|
||||
}
|
||||
|
||||
nrArgs--;
|
||||
args += 1;
|
||||
args = args.subspan(1);
|
||||
}
|
||||
|
||||
else if (vCur.isPrimOp()) {
|
||||
|
||||
size_t argsLeft = vCur.primOp()->arity;
|
||||
|
||||
if (nrArgs < argsLeft) {
|
||||
if (args.size() < argsLeft) {
|
||||
/* We don't have enough arguments, so create a tPrimOpApp chain. */
|
||||
makeAppChain();
|
||||
return;
|
||||
|
|
@ -1614,15 +1616,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
if (countCalls) primOpCalls[fn->name]++;
|
||||
|
||||
try {
|
||||
fn->fun(*this, vCur.determinePos(noPos), args, vCur);
|
||||
fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur);
|
||||
} catch (Error & e) {
|
||||
if (fn->addTrace)
|
||||
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
|
||||
throw;
|
||||
}
|
||||
|
||||
nrArgs -= argsLeft;
|
||||
args += argsLeft;
|
||||
args = args.subspan(argsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1638,7 +1639,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
auto arity = primOp->primOp()->arity;
|
||||
auto argsLeft = arity - argsDone;
|
||||
|
||||
if (nrArgs < argsLeft) {
|
||||
if (args.size() < argsLeft) {
|
||||
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
|
||||
makeAppChain();
|
||||
return;
|
||||
|
|
@ -1670,8 +1671,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
throw;
|
||||
}
|
||||
|
||||
nrArgs -= argsLeft;
|
||||
args += argsLeft;
|
||||
args = args.subspan(argsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1682,13 +1682,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
Value * args2[] = {allocValue(), args[0]};
|
||||
*args2[0] = vCur;
|
||||
try {
|
||||
callFunction(*functor->value, 2, args2, vCur, functor->pos);
|
||||
callFunction(*functor->value, args2, vCur, functor->pos);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
|
||||
throw;
|
||||
}
|
||||
nrArgs--;
|
||||
args++;
|
||||
args = args.subspan(1);
|
||||
}
|
||||
|
||||
else
|
||||
|
|
@ -1731,7 +1730,7 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
|
|||
for (size_t i = 0; i < args.size(); ++i)
|
||||
vArgs[i] = args[i]->maybeThunk(state, env);
|
||||
|
||||
state.callFunction(vFun, args.size(), vArgs.data(), v, pos);
|
||||
state.callFunction(vFun, vArgs, v, pos);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1743,7 +1742,7 @@ void EvalState::incrFunctionCall(ExprLambda * fun)
|
|||
}
|
||||
|
||||
|
||||
void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
||||
void EvalState::autoCallFunction(const Bindings & args, Value & fun, Value & res)
|
||||
{
|
||||
auto pos = fun.determinePos(noPos);
|
||||
|
||||
|
|
@ -1813,11 +1812,9 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v)
|
|||
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
||||
auto exprStr = ({
|
||||
std::ostringstream out;
|
||||
cond->show(state.symbols, out);
|
||||
out.str();
|
||||
});
|
||||
std::ostringstream out;
|
||||
cond->show(state.symbols, out);
|
||||
auto exprStr = toView(out);
|
||||
|
||||
if (auto eq = dynamic_cast<ExprOpEq *>(cond)) {
|
||||
try {
|
||||
|
|
@ -2055,9 +2052,12 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
|
|||
state.mkPos(v, pos);
|
||||
}
|
||||
|
||||
|
||||
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
|
||||
void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value & v)
|
||||
{
|
||||
throwInfiniteRecursionError(state, v);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] [[noreturn]] void ExprBlackHole::throwInfiniteRecursionError(EvalState & state, Value &v) {
|
||||
state.error<InfiniteRecursionError>("infinite recursion encountered")
|
||||
.atPos(v.determinePos(noPos))
|
||||
.debugThrow();
|
||||
|
|
@ -2849,7 +2849,9 @@ void EvalState::printStatistics()
|
|||
#endif
|
||||
#if HAVE_BOEHMGC
|
||||
{GC_is_incremental_mode() ? "gcNonIncremental" : "gc", gcFullOnlyTime},
|
||||
#ifndef _WIN32 // TODO implement
|
||||
{GC_is_incremental_mode() ? "gcNonIncrementalFraction" : "gcFraction", gcFullOnlyTime / cpuTime},
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
topObj["envs"] = {
|
||||
|
|
@ -3036,8 +3038,8 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
|
|||
if (!rOpt) continue;
|
||||
auto r = *rOpt;
|
||||
|
||||
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
|
||||
if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
|
||||
auto res = (r / CanonPath(suffix)).resolveSymlinks();
|
||||
if (res.pathExists()) return res;
|
||||
}
|
||||
|
||||
if (hasPrefix(path, "nix/"))
|
||||
|
|
@ -3052,13 +3054,13 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
|
|||
}
|
||||
|
||||
|
||||
std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
|
||||
std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
|
||||
{
|
||||
auto & value = value0.s;
|
||||
auto i = lookupPathResolved.find(value);
|
||||
if (i != lookupPathResolved.end()) return i->second;
|
||||
|
||||
auto finish = [&](std::string res) {
|
||||
auto finish = [&](SourcePath res) {
|
||||
debug("resolved search path element '%s' to '%s'", value, res);
|
||||
lookupPathResolved.emplace(value, res);
|
||||
return res;
|
||||
|
|
@ -3071,7 +3073,7 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(store->toRealPath(storePath));
|
||||
return finish(rootPath(store->toRealPath(storePath)));
|
||||
} catch (Error & e) {
|
||||
logWarning({
|
||||
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
|
||||
|
|
@ -3083,29 +3085,29 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
auto scheme = value.substr(0, colPos);
|
||||
auto rest = value.substr(colPos + 1);
|
||||
if (auto * hook = get(settings.lookupPathHooks, scheme)) {
|
||||
auto res = (*hook)(store, rest);
|
||||
auto res = (*hook)(*this, rest);
|
||||
if (res)
|
||||
return finish(std::move(*res));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto path = absPath(value);
|
||||
auto path = rootPath(value);
|
||||
|
||||
/* Allow access to paths in the search path. */
|
||||
if (initAccessControl) {
|
||||
allowPath(path);
|
||||
if (store->isInStore(path)) {
|
||||
allowPath(path.path.abs());
|
||||
if (store->isInStore(path.path.abs())) {
|
||||
try {
|
||||
StorePathSet closure;
|
||||
store->computeFSClosure(store->toStorePath(path).first, closure);
|
||||
store->computeFSClosure(store->toStorePath(path.path.abs()).first, closure);
|
||||
for (auto & p : closure)
|
||||
allowPath(p);
|
||||
} catch (InvalidPath &) { }
|
||||
}
|
||||
}
|
||||
|
||||
if (pathExists(path))
|
||||
if (path.pathExists())
|
||||
return finish(std::move(path));
|
||||
else {
|
||||
logWarning({
|
||||
|
|
@ -3116,7 +3118,6 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
|
||||
debug("failed to resolve search path element '%s'", value);
|
||||
return std::nullopt;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ struct PrimOp
|
|||
const char * doc = nullptr;
|
||||
|
||||
/**
|
||||
* Add a trace item, `while calling the '<name>' builtin`
|
||||
* Add a trace item, while calling the `<name>` builtin.
|
||||
*
|
||||
* This is used to remove the redundant item for `builtins.addErrorContext`.
|
||||
*/
|
||||
|
|
@ -107,6 +107,11 @@ struct PrimOp
|
|||
*/
|
||||
std::optional<ExperimentalFeature> experimentalFeature;
|
||||
|
||||
/**
|
||||
* If true, this primop is not exposed to the user.
|
||||
*/
|
||||
bool internal = false;
|
||||
|
||||
/**
|
||||
* Validity check to be performed by functions that introduce primops,
|
||||
* such as RegisterPrimOp() and Value::mkPrimOp().
|
||||
|
|
@ -342,7 +347,7 @@ private:
|
|||
|
||||
LookupPath lookupPath;
|
||||
|
||||
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
||||
std::map<std::string, std::optional<SourcePath>> lookupPathResolved;
|
||||
|
||||
/**
|
||||
* Cache used by prim_match().
|
||||
|
|
@ -447,9 +452,9 @@ public:
|
|||
*
|
||||
* If the specified search path element is a URI, download it.
|
||||
*
|
||||
* If it is not found, return `std::nullopt`
|
||||
* If it is not found, return `std::nullopt`.
|
||||
*/
|
||||
std::optional<std::string> resolveLookupPathPath(
|
||||
std::optional<SourcePath> resolveLookupPathPath(
|
||||
const LookupPath::Path & elem,
|
||||
bool initAccessControl = false);
|
||||
|
||||
|
|
@ -591,6 +596,11 @@ public:
|
|||
*/
|
||||
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
|
||||
|
||||
/**
|
||||
* Internal primops not exposed to the user.
|
||||
*/
|
||||
std::unordered_map<std::string, Value *, std::hash<std::string>, std::equal_to<std::string>, traceable_allocator<std::pair<const std::string, Value *>>> internalPrimOps;
|
||||
|
||||
/**
|
||||
* Name and documentation about every constant.
|
||||
*
|
||||
|
|
@ -613,8 +623,19 @@ private:
|
|||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Retrieve a specific builtin, equivalent to evaluating `builtins.${name}`.
|
||||
* @param name The attribute name of the builtin to retrieve.
|
||||
* @throws EvalError if the builtin does not exist.
|
||||
*/
|
||||
Value & getBuiltin(const std::string & name);
|
||||
|
||||
/**
|
||||
* Retrieve the `builtins` attrset, equivalent to evaluating the reference `builtins`.
|
||||
* Always returns an attribute set value.
|
||||
*/
|
||||
Value & getBuiltins();
|
||||
|
||||
struct Doc
|
||||
{
|
||||
Pos pos;
|
||||
|
|
@ -680,20 +701,19 @@ public:
|
|||
|
||||
bool isFunctor(Value & fun);
|
||||
|
||||
// FIXME: use std::span
|
||||
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
|
||||
void callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos);
|
||||
|
||||
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
Value * args[] = {&arg};
|
||||
callFunction(fun, 1, args, vRes, pos);
|
||||
callFunction(fun, args, vRes, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically call a function for which each argument has a
|
||||
* default value or has a binding in the `args` map.
|
||||
*/
|
||||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/**
|
||||
* Allocation primitives.
|
||||
|
|
@ -799,7 +819,6 @@ public:
|
|||
bool callPathFilter(
|
||||
Value * filterFun,
|
||||
const SourcePath & path,
|
||||
std::string_view pathArg,
|
||||
PosIdx pos);
|
||||
|
||||
DocComment getDocCommentForPos(PosIdx pos);
|
||||
|
|
|
|||
|
|
@ -374,11 +374,12 @@ static void getDerivations(EvalState & state, Value & vIn,
|
|||
bound to the attribute with the "lower" name should take
|
||||
precedence). */
|
||||
for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) {
|
||||
std::string_view symbol{state.symbols[i->name]};
|
||||
try {
|
||||
debug("evaluating attribute '%1%'", state.symbols[i->name]);
|
||||
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
|
||||
debug("evaluating attribute '%1%'", symbol);
|
||||
if (!std::regex_match(symbol.begin(), symbol.end(), attrRegex))
|
||||
continue;
|
||||
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
|
||||
std::string pathPrefix2 = addToPath(pathPrefix, symbol);
|
||||
if (combineChannels)
|
||||
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
|
||||
|
|
@ -392,7 +393,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
|||
}
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", state.symbols[i->name]);
|
||||
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", symbol);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
libraries += libexpr
|
||||
|
||||
libexpr_NAME = libnixexpr
|
||||
|
||||
libexpr_DIR := $(d)
|
||||
|
||||
libexpr_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
$(wildcard $(d)/value/*.cc) \
|
||||
$(wildcard $(d)/primops/*.cc) \
|
||||
$(d)/lexer-tab.cc \
|
||||
$(d)/parser-tab.cc
|
||||
# Not just for this library itself, but also for downstream libraries using this library
|
||||
|
||||
INCLUDE_libexpr := -I $(d)
|
||||
|
||||
libexpr_CXXFLAGS += \
|
||||
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) \
|
||||
-DGC_THREADS
|
||||
|
||||
libexpr_LIBS = libutil libstore libfetchers
|
||||
|
||||
libexpr_LDFLAGS += -lboost_context $(THREAD_LDFLAGS)
|
||||
ifdef HOST_LINUX
|
||||
libexpr_LDFLAGS += -ldl
|
||||
endif
|
||||
|
||||
# The dependency on libgc must be propagated (i.e. meaning that
|
||||
# programs/libraries that use libexpr must explicitly pass -lgc),
|
||||
# because inline functions in libexpr's header files call libgc.
|
||||
libexpr_LDFLAGS_PROPAGATED = $(BDW_GC_LIBS)
|
||||
|
||||
libexpr_ORDER_AFTER := $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
|
||||
|
||||
$(d)/parser-tab.cc $(d)/parser-tab.hh: $(d)/parser.y
|
||||
$(trace-gen) bison -v -o $(libexpr_DIR)/parser-tab.cc $< -d
|
||||
|
||||
$(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l
|
||||
$(trace-gen) flex --outfile $(libexpr_DIR)/lexer-tab.cc --header-file=$(libexpr_DIR)/lexer-tab.hh $<
|
||||
|
||||
clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
|
||||
|
||||
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))
|
||||
|
||||
$(foreach i, $(wildcard src/libexpr/value/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
|
||||
|
||||
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
|
||||
|
||||
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/call-flake.nix.gen.hh
|
||||
|
|
@ -27,8 +27,6 @@ deps_public_maybe_subproject = [
|
|||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
boost = dependency(
|
||||
'boost',
|
||||
modules : ['container', 'context'],
|
||||
|
|
@ -79,7 +77,7 @@ add_project_arguments(
|
|||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
parser_tab = custom_target(
|
||||
input : 'parser.y',
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
prefix=@prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Nix
|
||||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Requires: nix-store bdw-gc
|
||||
Libs: -L${libdir} -lnixexpr
|
||||
Cflags: -I${includedir}/nix -std=c++2a
|
||||
|
|
@ -663,4 +663,32 @@ std::string DocComment::getInnerText(const PosTable & positions) const {
|
|||
return docStr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ‘Cursed or’ handling.
|
||||
*
|
||||
* In parser.y, every use of expr_select in a production must call one of the
|
||||
* two below functions.
|
||||
*
|
||||
* To be removed by https://github.com/NixOS/nix/pull/11121
|
||||
*/
|
||||
|
||||
void ExprCall::resetCursedOr()
|
||||
{
|
||||
cursedOrEndPos.reset();
|
||||
}
|
||||
|
||||
void ExprCall::warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions)
|
||||
{
|
||||
if (cursedOrEndPos.has_value()) {
|
||||
std::ostringstream out;
|
||||
out << "at " << positions[pos] << ": "
|
||||
"This expression uses `or` as an identifier in a way that will change in a future Nix release.\n"
|
||||
"Wrap this entire expression in parentheses to preserve its current meaning:\n"
|
||||
" (" << positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression") << ")\n"
|
||||
"Give feedback at https://github.com/NixOS/nix/pull/11121";
|
||||
warn(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ struct Expr
|
|||
virtual void setName(Symbol name);
|
||||
virtual void setDocComment(DocComment docComment) { };
|
||||
virtual PosIdx getPos() const { return noPos; }
|
||||
|
||||
// These are temporary methods to be used only in parser.y
|
||||
virtual void resetCursedOr() { };
|
||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) { };
|
||||
};
|
||||
|
||||
#define COMMON_METHODS \
|
||||
|
|
@ -202,7 +206,7 @@ struct ExprSelect : Expr
|
|||
/**
|
||||
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
|
||||
*
|
||||
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
|
||||
* @param[out] attrs The attribute set that should contain the last attribute name (if it exists).
|
||||
* @return The last attribute name in `attrPath`
|
||||
*
|
||||
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
|
||||
|
|
@ -354,10 +358,16 @@ struct ExprCall : Expr
|
|||
Expr * fun;
|
||||
std::vector<Expr *> args;
|
||||
PosIdx pos;
|
||||
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
|
||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
||||
: fun(fun), args(args), pos(pos)
|
||||
: fun(fun), args(args), pos(pos), cursedOrEndPos({})
|
||||
{ }
|
||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos)
|
||||
: fun(fun), args(args), pos(pos), cursedOrEndPos(cursedOrEndPos)
|
||||
{ }
|
||||
PosIdx getPos() const override { return pos; }
|
||||
virtual void resetCursedOr() override;
|
||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override;
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
|
@ -458,6 +468,7 @@ struct ExprBlackHole : Expr
|
|||
void show(const SymbolTable & symbols, std::ostream & str) const override {}
|
||||
void eval(EvalState & state, Env & env, Value & v) override;
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {}
|
||||
[[noreturn]] static void throwInfiniteRecursionError(EvalState & state, Value & v);
|
||||
};
|
||||
|
||||
extern ExprBlackHole eBlackHole;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
, releaseTools
|
||||
, mkMesonLibrary
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
, bison
|
||||
, flex
|
||||
, cmake # for resolving toml11 dep
|
||||
|
|
@ -38,7 +34,7 @@ let
|
|||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-expr";
|
||||
inherit version;
|
||||
|
||||
|
|
@ -55,15 +51,13 @@ mkMesonDerivation (finalAttrs: {
|
|||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
./lexer.l
|
||||
./parser.y
|
||||
(fileset.fileFilter (file: file.hasExt "nix") ./.)
|
||||
(fileset.difference
|
||||
(fileset.fileFilter (file: file.hasExt "nix") ./.)
|
||||
./package.nix
|
||||
)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
bison
|
||||
flex
|
||||
cmake
|
||||
|
|
@ -77,6 +71,10 @@ mkMesonDerivation (finalAttrs: {
|
|||
nix-util
|
||||
nix-store
|
||||
nix-fetchers
|
||||
] ++ finalAttrs.passthru.externalPropagatedBuildInputs;
|
||||
|
||||
# Hack for sake of the dev shell
|
||||
passthru.externalPropagatedBuildInputs = [
|
||||
boost
|
||||
nlohmann_json
|
||||
] ++ lib.optional enableGC boehmgc;
|
||||
|
|
@ -102,10 +100,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -264,19 +264,28 @@ expr_op
|
|||
;
|
||||
|
||||
expr_app
|
||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
|
||||
| expr_select
|
||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); $2->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| /* Once a ‘cursed or’ reaches this nonterminal, it is no longer cursed,
|
||||
because the uncursed parse would also produce an expr_app. But we need
|
||||
to remove the cursed status in order to prevent valid things like
|
||||
`f (g or)` from triggering the warning. */
|
||||
expr_select { $$ = $1; $$->resetCursedOr(); }
|
||||
;
|
||||
|
||||
expr_select
|
||||
: expr_simple '.' attrpath
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
|
||||
| expr_simple '.' attrpath OR_KW expr_select
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
|
||||
| /* Backwards compatibility: because Nixpkgs has a rarely used
|
||||
function named ‘or’, allow stuff like ‘map or [...]’. */
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| /* Backwards compatibility: because Nixpkgs has a function named ‘or’,
|
||||
allow stuff like ‘map or [...]’. This production is problematic (see
|
||||
https://github.com/NixOS/nix/issues/11118) and will be refactored in the
|
||||
future by treating `or` as a regular identifier. The refactor will (in
|
||||
very rare cases, we think) change the meaning of expressions, so we mark
|
||||
the ExprCall with data (establishing that it is a ‘cursed or’) that can
|
||||
be used to emit a warning when an affected expression is parsed. */
|
||||
expr_simple OR_KW
|
||||
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}); }
|
||||
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}, state->positions.add(state->origin, @$.endOffset)); }
|
||||
| expr_simple
|
||||
;
|
||||
|
||||
|
|
@ -472,7 +481,7 @@ string_attr
|
|||
;
|
||||
|
||||
expr_list
|
||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
|
||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */; $2->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| { $$ = new ExprList; }
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ namespace nix {
|
|||
* Miscellaneous
|
||||
*************************************************************/
|
||||
|
||||
static inline Value * mkString(EvalState & state, const std::csub_match & match)
|
||||
{
|
||||
Value * v = state.allocValue();
|
||||
v->mkString({match.first, match.second});
|
||||
return v;
|
||||
}
|
||||
|
||||
StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
|
||||
{
|
||||
std::vector<DerivedPath::Built> drvs;
|
||||
|
|
@ -84,6 +91,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
|
|||
|
||||
/* Build/substitute the context. */
|
||||
std::vector<DerivedPath> buildReqs;
|
||||
buildReqs.reserve(drvs.size());
|
||||
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
|
||||
buildStore->buildPaths(buildReqs, bmNormal, store);
|
||||
|
||||
|
|
@ -171,6 +179,78 @@ static void mkOutputString(
|
|||
o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first));
|
||||
}
|
||||
|
||||
/**
|
||||
* `import` will parse a derivation when it imports a `.drv` file from the store.
|
||||
*
|
||||
* @param state The evaluation state.
|
||||
* @param pos The position of the `import` call.
|
||||
* @param path The path to the `.drv` to import.
|
||||
* @param storePath The path to the `.drv` to import.
|
||||
* @param v Return value
|
||||
*/
|
||||
void derivationToValue(EvalState & state, const PosIdx pos, const SourcePath & path, const StorePath & storePath, Value & v) {
|
||||
auto path2 = path.path.abs();
|
||||
Derivation drv = state.store->readDerivation(storePath);
|
||||
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(path2, {
|
||||
NixStringContextElem::DrvDeep { .drvPath = storePath },
|
||||
});
|
||||
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||
|
||||
auto list = state.buildList(drv.outputs.size());
|
||||
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||
mkOutputString(state, attrs, storePath, o);
|
||||
(list[i] = state.allocValue())->mkString(o.first);
|
||||
}
|
||||
attrs.alloc(state.sOutputs).mkList(list);
|
||||
|
||||
auto w = state.allocValue();
|
||||
w->mkAttrs(attrs);
|
||||
|
||||
if (!state.vImportedDrvToDerivation) {
|
||||
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||
state.eval(state.parseExprFromString(
|
||||
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
|
||||
}
|
||||
|
||||
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
||||
v.mkApp(*state.vImportedDrvToDerivation, w);
|
||||
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a Nix file with an alternate base scope, as `builtins.scopedImport` does.
|
||||
*
|
||||
* @param state The evaluation state.
|
||||
* @param pos The position of the import call.
|
||||
* @param path The path to the file to import.
|
||||
* @param vScope The base scope to use for the import.
|
||||
* @param v Return value
|
||||
*/
|
||||
static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path, Value * vScope, Value & v) {
|
||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||
|
||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
|
||||
|
||||
unsigned int displ = 0;
|
||||
for (auto & attr : *vScope->attrs()) {
|
||||
staticEnv->vars.emplace_back(attr.name, displ);
|
||||
env->values[displ++] = attr.value;
|
||||
}
|
||||
|
||||
// No need to call staticEnv.sort(), because
|
||||
// args[0]->attrs is already sorted.
|
||||
|
||||
printTalkative("evaluating file '%1%'", path);
|
||||
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
|
||||
|
||||
e->eval(state, *env, v);
|
||||
}
|
||||
|
||||
/* Load and evaluate an expression from path specified by the
|
||||
argument. */
|
||||
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
|
||||
|
|
@ -189,60 +269,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
|||
};
|
||||
|
||||
if (auto storePath = isValidDerivationInStore()) {
|
||||
Derivation drv = state.store->readDerivation(*storePath);
|
||||
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(path2, {
|
||||
NixStringContextElem::DrvDeep { .drvPath = *storePath },
|
||||
});
|
||||
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||
|
||||
auto list = state.buildList(drv.outputs.size());
|
||||
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||
mkOutputString(state, attrs, *storePath, o);
|
||||
(list[i] = state.allocValue())->mkString(o.first);
|
||||
}
|
||||
attrs.alloc(state.sOutputs).mkList(list);
|
||||
|
||||
auto w = state.allocValue();
|
||||
w->mkAttrs(attrs);
|
||||
|
||||
if (!state.vImportedDrvToDerivation) {
|
||||
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||
state.eval(state.parseExprFromString(
|
||||
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
|
||||
}
|
||||
|
||||
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
||||
v.mkApp(*state.vImportedDrvToDerivation, w);
|
||||
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||
derivationToValue(state, pos, path, *storePath, v);
|
||||
}
|
||||
else if (vScope) {
|
||||
scopedImport(state, pos, path, vScope, v);
|
||||
}
|
||||
|
||||
else {
|
||||
if (!vScope)
|
||||
state.evalFile(path, v);
|
||||
else {
|
||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||
|
||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
|
||||
|
||||
unsigned int displ = 0;
|
||||
for (auto & attr : *vScope->attrs()) {
|
||||
staticEnv->vars.emplace_back(attr.name, displ);
|
||||
env->values[displ++] = attr.value;
|
||||
}
|
||||
|
||||
// No need to call staticEnv.sort(), because
|
||||
// args[0]->attrs is already sorted.
|
||||
|
||||
printTalkative("evaluating file '%1%'", path);
|
||||
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
|
||||
|
||||
e->eval(state, *env, v);
|
||||
}
|
||||
state.evalFile(path, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -691,7 +724,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
|
|||
|
||||
/* Call the `operator' function with `e' as argument. */
|
||||
Value newElements;
|
||||
state.callFunction(*op->value, 1, &e, newElements, noPos);
|
||||
state.callFunction(*op->value, {&e, 1}, newElements, noPos);
|
||||
state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure");
|
||||
|
||||
/* Add the values returned by the operator to the work set. */
|
||||
|
|
@ -937,6 +970,9 @@ static RegisterPrimOp primop_tryEval({
|
|||
`let e = { x = throw ""; }; in
|
||||
(builtins.tryEval (builtins.deepSeq e e)).success` will be
|
||||
`false`.
|
||||
|
||||
`tryEval` intentionally does not return the error message, because that risks bringing non-determinism into the evaluation result, and it would become very difficult to improve error reporting without breaking existing expressions.
|
||||
Instead, use [`builtins.addErrorContext`](@docroot@/language/builtins.md#builtins-addErrorContext) to add context to the error message, and use a Nix unit testing tool for testing.
|
||||
)",
|
||||
.fun = prim_tryEval,
|
||||
});
|
||||
|
|
@ -1567,7 +1603,8 @@ static RegisterPrimOp primop_placeholder({
|
|||
*************************************************************/
|
||||
|
||||
|
||||
/* Convert the argument to a path. !!! obsolete? */
|
||||
/* Convert the argument to a path and then to a string (confusing,
|
||||
eh?). !!! obsolete? */
|
||||
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
|
|
@ -2129,7 +2166,7 @@ static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Valu
|
|||
std::ostringstream out;
|
||||
NixStringContext context;
|
||||
printValueAsXML(state, true, false, *args[0], out, context, pos);
|
||||
v.mkString(out.str(), context);
|
||||
v.mkString(toView(out), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_toXML({
|
||||
|
|
@ -2237,7 +2274,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
std::ostringstream out;
|
||||
NixStringContext context;
|
||||
printValueAsJSON(state, true, *args[0], pos, out, context);
|
||||
v.mkString(out.str(), context);
|
||||
v.mkString(toView(out), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_toJSON({
|
||||
|
|
@ -2401,7 +2438,6 @@ static RegisterPrimOp primop_toFile({
|
|||
bool EvalState::callPathFilter(
|
||||
Value * filterFun,
|
||||
const SourcePath & path,
|
||||
std::string_view pathArg,
|
||||
PosIdx pos)
|
||||
{
|
||||
auto st = path.lstat();
|
||||
|
|
@ -2409,12 +2445,12 @@ bool EvalState::callPathFilter(
|
|||
/* Call the filter function. The first argument is the path, the
|
||||
second is a string indicating the type of the file. */
|
||||
Value arg1;
|
||||
arg1.mkString(pathArg);
|
||||
arg1.mkString(path.path.abs());
|
||||
|
||||
// assert that type is not "unknown"
|
||||
Value * args []{&arg1, fileTypeToString(*this, st.type)};
|
||||
Value res;
|
||||
callFunction(*filterFun, 2, args, res, pos);
|
||||
callFunction(*filterFun, args, res, pos);
|
||||
|
||||
return forceBool(res, pos, "while evaluating the return value of the path filter function");
|
||||
}
|
||||
|
|
@ -2452,7 +2488,7 @@ static void addPath(
|
|||
if (filterFun)
|
||||
filter = std::make_unique<PathFilter>([&](const Path & p) {
|
||||
auto p2 = CanonPath(p);
|
||||
return state.callPathFilter(filterFun, {path.accessor, p2}, p2.abs(), pos);
|
||||
return state.callPathFilter(filterFun, {path.accessor, p2}, pos);
|
||||
});
|
||||
|
||||
std::optional<StorePath> expectedStorePath;
|
||||
|
|
@ -2578,13 +2614,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
|
||||
else
|
||||
state.error<EvalError>(
|
||||
"unsupported argument '%1%' to 'addPath'",
|
||||
"unsupported argument '%1%' to 'builtins.path'",
|
||||
state.symbols[attr.name]
|
||||
).atPos(attr.pos).debugThrow();
|
||||
}
|
||||
if (!path)
|
||||
state.error<EvalError>(
|
||||
"missing required 'path' attribute in the first argument to builtins.path"
|
||||
"missing required 'path' attribute in the first argument to 'builtins.path'"
|
||||
).atPos(pos).debugThrow();
|
||||
if (name.empty())
|
||||
name = path->baseName();
|
||||
|
|
@ -3451,7 +3487,7 @@ static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args
|
|||
for (auto [n, elem] : enumerate(args[2]->listItems())) {
|
||||
Value * vs []{vCur, elem};
|
||||
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||
state.callFunction(*args[0], 2, vs, *vCur, pos);
|
||||
state.callFunction(*args[0], vs, *vCur, pos);
|
||||
}
|
||||
state.forceValue(v, pos);
|
||||
} else {
|
||||
|
|
@ -3601,7 +3637,7 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
|
||||
Value * vs[] = {a, b};
|
||||
Value vBool;
|
||||
state.callFunction(*args[0], 2, vs, vBool, noPos);
|
||||
state.callFunction(*args[0], vs, vBool, noPos);
|
||||
return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
|
||||
};
|
||||
|
||||
|
|
@ -4268,7 +4304,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
if (!match[i + 1].matched)
|
||||
v2 = &state.vNull;
|
||||
else
|
||||
(v2 = state.allocValue())->mkString(match[i + 1].str());
|
||||
v2 = mkString(state, match[i + 1]);
|
||||
v.mkList(list);
|
||||
|
||||
} catch (std::regex_error & e) {
|
||||
|
|
@ -4352,7 +4388,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
auto match = *i;
|
||||
|
||||
// Add a string for non-matched characters.
|
||||
(list[idx++] = state.allocValue())->mkString(match.prefix().str());
|
||||
list[idx++] = mkString(state, match.prefix());
|
||||
|
||||
// Add a list for matched substrings.
|
||||
const size_t slen = match.size() - 1;
|
||||
|
|
@ -4363,14 +4399,14 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
if (!match[si + 1].matched)
|
||||
v2 = &state.vNull;
|
||||
else
|
||||
(v2 = state.allocValue())->mkString(match[si + 1].str());
|
||||
v2 = mkString(state, match[si + 1]);
|
||||
}
|
||||
|
||||
(list[idx++] = state.allocValue())->mkList(list2);
|
||||
|
||||
// Add a string for non-matched suffix characters.
|
||||
if (idx == 2 * len)
|
||||
(list[idx++] = state.allocValue())->mkString(match.suffix().str());
|
||||
list[idx++] = mkString(state, match.suffix());
|
||||
}
|
||||
|
||||
assert(idx == 2 * len + 1);
|
||||
|
|
@ -4901,7 +4937,7 @@ void EvalState::createBaseEnv()
|
|||
|
||||
/* Now that we've added all primops, sort the `builtins' set,
|
||||
because attribute lookups expect it to be sorted. */
|
||||
baseEnv.values[0]->payload.attrs->sort();
|
||||
getBuiltins().payload.attrs->sort();
|
||||
|
||||
staticBaseEnv->sort();
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency({
|
|||
|
||||
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
|
||||
|
||||
This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context,
|
||||
This is unsafe because it allows us to "forget" store objects we would have otherwise referred to with the string context,
|
||||
whereas Nix normally tracks all dependencies consistently.
|
||||
Safe operations "grow" but never "shrink" string contexts.
|
||||
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "value-to-json.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
|
@ -75,6 +77,7 @@ struct FetchTreeParams {
|
|||
bool emptyRevFallback = false;
|
||||
bool allowNameArgument = false;
|
||||
bool isFetchGit = false;
|
||||
bool isFinal = false;
|
||||
};
|
||||
|
||||
static void fetchTree(
|
||||
|
|
@ -192,6 +195,13 @@ static void fetchTree(
|
|||
|
||||
state.checkURI(input.toURLString());
|
||||
|
||||
if (params.isFinal) {
|
||||
input.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
} else {
|
||||
if (input.isFinal())
|
||||
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
|
||||
}
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||
|
||||
state.allowPath(storePath);
|
||||
|
|
@ -245,7 +255,7 @@ static RegisterPrimOp primop_fetchTree({
|
|||
The following source types and associated input attributes are supported.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first paramter for
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
|
||||
- `"file"`
|
||||
|
|
@ -428,6 +438,18 @@ static RegisterPrimOp primop_fetchTree({
|
|||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
|
||||
void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetchTree(state, pos, args, v, {.isFinal = true});
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchFinalTree({
|
||||
.name = "fetchFinalTree",
|
||||
.args = {"input"},
|
||||
.fun = prim_fetchFinalTree,
|
||||
.internal = true,
|
||||
});
|
||||
|
||||
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
|
||||
const std::string & who, bool unpack, std::string name)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
attrs.alloc("_type").mkString("timestamp");
|
||||
std::ostringstream s;
|
||||
s << t;
|
||||
attrs.alloc("value").mkString(s.str());
|
||||
attrs.alloc("value").mkString(toView(s));
|
||||
v.mkAttrs(attrs);
|
||||
} else {
|
||||
throw std::runtime_error("Dates and times are not supported");
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ private:
|
|||
|
||||
std::ostringstream s;
|
||||
s << state.positions[v.payload.lambda.fun->pos];
|
||||
output << " @ " << filterANSIEscapes(s.str());
|
||||
output << " @ " << filterANSIEscapes(toView(s));
|
||||
}
|
||||
} else if (v.isPrimOp()) {
|
||||
if (v.primOp())
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ public:
|
|||
Value * * elems;
|
||||
ListBuilder(EvalState & state, size_t size);
|
||||
|
||||
ListBuilder(ListBuilder && x)
|
||||
// NOTE: Can be noexcept because we are just copying integral values and
|
||||
// raw pointers.
|
||||
ListBuilder(ListBuilder && x) noexcept
|
||||
: size(x.size)
|
||||
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
|
||||
, elems(size <= 2 ? inlineElems : x.elems)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ struct NixStringContextElem {
|
|||
/**
|
||||
* Plain opaque path to some store object.
|
||||
*
|
||||
* Encoded as just the path: ‘<path>’.
|
||||
* Encoded as just the path: `<path>`.
|
||||
*/
|
||||
using Opaque = SingleDerivedPath::Opaque;
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ struct NixStringContextElem {
|
|||
* also all outputs of all derivations in that closure (including the
|
||||
* root derivation).
|
||||
*
|
||||
* Encoded in the form ‘=<drvPath>’.
|
||||
* Encoded in the form `=<drvPath>`.
|
||||
*/
|
||||
struct DrvDeep {
|
||||
StorePath drvPath;
|
||||
|
|
@ -50,7 +50,7 @@ struct NixStringContextElem {
|
|||
/**
|
||||
* Derivation output.
|
||||
*
|
||||
* Encoded in the form ‘!<output>!<drvPath>’.
|
||||
* Encoded in the form `!<output>!<drvPath>`.
|
||||
*/
|
||||
using Built = SingleDerivedPath::Built;
|
||||
|
||||
|
|
@ -68,9 +68,9 @@ struct NixStringContextElem {
|
|||
|
||||
/**
|
||||
* Decode a context string, one of:
|
||||
* - ‘<path>’
|
||||
* - ‘=<path>’
|
||||
* - ‘!<name>!<path>’
|
||||
* - `<path>`
|
||||
* - `=<path>`
|
||||
* - `!<name>!<path>`
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue