diff options
Diffstat (limited to 'src/libexpr')
29 files changed, 1218 insertions, 778 deletions
diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 83854df49..9dd557205 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -52,9 +52,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr for (auto & attr : tokens) { /* Is i an index (integer) or a normal attribute name? */ - enum { apAttr, apIndex } apType = apAttr; - unsigned int attrIndex; - if (string2Int(attr, attrIndex)) apType = apIndex; + auto attrIndex = string2Int<unsigned int>(attr); /* Evaluate the expression. */ Value * vNew = state.allocValue(); @@ -65,9 +63,9 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr /* It should evaluate to either a set or an expression, according to what is specified in the attrPath. */ - if (apType == apAttr) { + if (!attrIndex) { - if (v->type != tAttrs) + if (v->type() != nAttrs) throw TypeError( "the expression selected by the selection path '%1%' should be a set but is %2%", attrPath, @@ -82,17 +80,17 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr pos = *a->pos; } - else if (apType == apIndex) { + else { if (!v->isList()) throw TypeError( "the expression selected by the selection path '%1%' should be a list but is %2%", attrPath, showType(*v)); - if (attrIndex >= v->listSize()) - throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath); + if (*attrIndex >= v->listSize()) + throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath); - v = v->listElems()[attrIndex]; + v = v->listElems()[*attrIndex]; pos = noPos; } diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index b1d61a285..b6091c955 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -24,9 +24,7 @@ void EvalState::mkAttrs(Value & v, size_t capacity) v = vEmptySet; return; } - clearValue(v); - v.type = tAttrs; - v.attrs = allocBindings(capacity); + v.mkAttrs(allocBindings(capacity)); nrAttrsets++; nrAttrsInAttrsets += capacity; } diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 7eaa16c59..6d68e5df3 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -77,7 +77,7 @@ public: auto a = get(name); if (!a) throw Error({ - .hint = hintfmt("attribute '%s' missing", name), + .msg = hintfmt("attribute '%s' missing", name), .errPos = pos }); diff --git a/src/libexpr/common-eval-args.cc b/src/libexpr/common-eval-args.cc index 10c1a6975..aa14bf79b 100644 --- a/src/libexpr/common-eval-args.cc +++ b/src/libexpr/common-eval-args.cc @@ -12,16 +12,20 @@ namespace nix { MixEvalArgs::MixEvalArgs() { + auto category = "Common evaluation options"; + addFlag({ .longName = "arg", - .description = "argument to be passed to Nix functions", + .description = "Pass the value *expr* as the argument *name* to Nix functions.", + .category = category, .labels = {"name", "expr"}, .handler = {[&](std::string name, std::string expr) { autoArgs[name] = 'E' + expr; }} }); addFlag({ .longName = "argstr", - .description = "string-valued argument to be passed to Nix functions", + .description = "Pass the string *string* as the argument *name* to Nix functions.", + .category = category, .labels = {"name", "string"}, .handler = {[&](std::string name, std::string s) { autoArgs[name] = 'S' + s; }}, }); @@ -29,14 +33,16 @@ MixEvalArgs::MixEvalArgs() addFlag({ .longName = "include", .shortName = 'I', - .description = "add a path to the list of locations used to look up `<...>` file names", + .description = "Add *path* to the list of locations used to look up `<...>` file names.", + .category = category, .labels = {"path"}, .handler = {[&](std::string s) { searchPath.push_back(s); }} }); addFlag({ .longName = "impure", - .description = "allow access to mutable paths and repositories", + .description = "Allow access to mutable paths and repositories.", + .category = category, .handler = {[&]() { evalSettings.pureEval = false; }}, @@ -44,7 +50,8 @@ MixEvalArgs::MixEvalArgs() addFlag({ .longName = "override-flake", - .description = "override a flake registry value", + .description = "Override the flake registries, redirecting *original-ref* to *resolved-ref*.", + .category = category, .labels = {"original-ref", "resolved-ref"}, .handler = {[&](std::string _from, std::string _to) { auto from = parseFlakeRef(_from, absPath(".")); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 381344b40..98d91c905 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -390,14 +390,14 @@ Value & AttrCursor::forceValue() } if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) { - if (v.type == tString) + if (v.type() == nString) cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context), string_t{v.string.s, {}}}; - else if (v.type == tPath) - cachedValue = {root->db->setString(getKey(), v.path), v.path}; - else if (v.type == tBool) + else if (v.type() == nPath) + cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}}; + else if (v.type() == nBool) cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean}; - else if (v.type == tAttrs) + else if (v.type() == nAttrs) ; // FIXME: do something? else cachedValue = {root->db->setMisc(getKey()), misc_t()}; @@ -442,7 +442,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro auto & v = forceValue(); - if (v.type != tAttrs) + if (v.type() != nAttrs) return nullptr; //throw TypeError("'%s' is not an attribute set", getAttrPathStr()); @@ -512,10 +512,10 @@ std::string AttrCursor::getString() auto & v = forceValue(); - if (v.type != tString && v.type != tPath) - throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type)); + if (v.type() != nString && v.type() != nPath) + throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())); - return v.type == tString ? v.string.s : v.path; + return v.type() == nString ? v.string.s : v.path; } string_t AttrCursor::getStringWithContext() @@ -525,8 +525,17 @@ string_t AttrCursor::getStringWithContext() cachedValue = root->db->getAttr(getKey(), root->state.symbols); if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&cachedValue->second)) { - debug("using cached string attribute '%s'", getAttrPathStr()); - return *s; + bool valid = true; + for (auto & c : s->second) { + if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) { + valid = false; + break; + } + } + if (valid) { + debug("using cached string attribute '%s'", getAttrPathStr()); + return *s; + } } else throw TypeError("'%s' is not a string", getAttrPathStr()); } @@ -534,12 +543,12 @@ string_t AttrCursor::getStringWithContext() auto & v = forceValue(); - if (v.type == tString) + if (v.type() == nString) return {v.string.s, v.getContext()}; - else if (v.type == tPath) + else if (v.type() == nPath) return {v.path, {}}; else - throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type)); + throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())); } bool AttrCursor::getBool() @@ -558,7 +567,7 @@ bool AttrCursor::getBool() auto & v = forceValue(); - if (v.type != tBool) + if (v.type() != nBool) throw TypeError("'%s' is not a Boolean", getAttrPathStr()); return v.boolean; @@ -580,7 +589,7 @@ std::vector<Symbol> AttrCursor::getAttrs() auto & v = forceValue(); - if (v.type != tAttrs) + if (v.type() != nAttrs) throw TypeError("'%s' is not an attribute set", getAttrPathStr()); std::vector<Symbol> attrs; diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 30f6ec7db..655408cd3 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -10,7 +10,7 @@ namespace nix { LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s)) { throw EvalError({ - .hint = hintfmt(s), + .msg = hintfmt(s), .errPos = pos }); } @@ -24,7 +24,7 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) { throw TypeError({ - .hint = hintfmt(s, showType(v)), + .msg = hintfmt(s, showType(v)), .errPos = pos }); } @@ -32,23 +32,21 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const void EvalState::forceValue(Value & v, const Pos & pos) { - if (v.type == tThunk) { + if (v.isThunk()) { Env * env = v.thunk.env; Expr * expr = v.thunk.expr; try { - v.type = tBlackhole; + v.mkBlackhole(); //checkInterrupt(); expr->eval(*this, *env, v); } catch (...) { - v.type = tThunk; - v.thunk.env = env; - v.thunk.expr = expr; + v.mkThunk(env, expr); throw; } } - else if (v.type == tApp) + else if (v.isApp()) callFunction(*v.app.left, *v.app.right, v, noPos); - else if (v.type == tBlackhole) + else if (v.isBlackhole()) throwEvalError(pos, "infinite recursion encountered"); } @@ -56,7 +54,7 @@ void EvalState::forceValue(Value & v, const Pos & pos) inline void EvalState::forceAttrs(Value & v) { forceValue(v); - if (v.type != tAttrs) + if (v.type() != nAttrs) throwTypeError("value is %1% while a set was expected", v); } @@ -64,7 +62,7 @@ inline void EvalState::forceAttrs(Value & v) inline void EvalState::forceAttrs(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type != tAttrs) + if (v.type() != nAttrs) throwTypeError(pos, "value is %1% while a set was expected", v); } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d6366050c..3afe2e47b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -27,6 +27,10 @@ #include <gc/gc.h> #include <gc/gc_cpp.h> +#include <boost/coroutine2/coroutine.hpp> +#include <boost/coroutine2/protected_fixedsize_stack.hpp> +#include <boost/context/stack_context.hpp> + #endif namespace nix { @@ -64,7 +68,7 @@ RootValue allocRootValue(Value * v) } -static void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v) +void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v) { checkInterrupt(); @@ -73,7 +77,7 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con return; } - switch (v.type) { + switch (v.internalType) { case tInt: str << v.integer; break; @@ -154,32 +158,27 @@ std::ostream & operator << (std::ostream & str, const Value & v) const Value *getPrimOp(const Value &v) { const Value * primOp = &v; - while (primOp->type == tPrimOpApp) { + while (primOp->isPrimOpApp()) { primOp = primOp->primOpApp.left; } - assert(primOp->type == tPrimOp); + assert(primOp->isPrimOp()); return primOp; } - string showType(ValueType type) { switch (type) { - case tInt: return "an integer"; - case tBool: return "a Boolean"; - case tString: return "a string"; - case tPath: return "a path"; - case tNull: return "null"; - case tAttrs: return "a set"; - case tList1: case tList2: case tListN: return "a list"; - case tThunk: return "a thunk"; - case tApp: return "a function application"; - case tLambda: return "a function"; - case tBlackhole: return "a black hole"; - case tPrimOp: return "a built-in function"; - case tPrimOpApp: return "a partially applied built-in function"; - case tExternal: return "an external value"; - case tFloat: return "a float"; + case nInt: return "an integer"; + case nBool: return "a Boolean"; + case nString: return "a string"; + case nPath: return "a path"; + case nNull: return "null"; + case nAttrs: return "a set"; + case nList: return "a list"; + case nFunction: return "a function"; + case nExternal: return "an external value"; + case nFloat: return "a float"; + case nThunk: return "a thunk"; } abort(); } @@ -187,15 +186,18 @@ string showType(ValueType type) string showType(const Value & v) { - switch (v.type) { + switch (v.internalType) { case tString: return v.string.context ? "a string with context" : "a string"; case tPrimOp: return fmt("the built-in function '%s'", string(v.primOp->name)); case tPrimOpApp: return fmt("the partially applied built-in function '%s'", string(getPrimOp(v)->primOp->name)); case tExternal: return v.external->showType(); + case tThunk: return "a thunk"; + case tApp: return "a function application"; + case tBlackhole: return "a black hole"; default: - return showType(v.type); + return showType(v.type()); } } @@ -203,12 +205,13 @@ string showType(const Value & v) bool Value::isTrivial() const { return - type != tApp - && type != tPrimOpApp - && (type != tThunk + internalType != tApp + && internalType != tPrimOpApp + && (internalType != tThunk || (dynamic_cast<ExprAttrs *>(thunk.expr) && ((ExprAttrs *) thunk.expr)->dynamicAttrs.empty()) - || dynamic_cast<ExprLambda *>(thunk.expr)); + || dynamic_cast<ExprLambda *>(thunk.expr) + || dynamic_cast<ExprList *>(thunk.expr)); } @@ -219,6 +222,31 @@ static void * oomHandler(size_t requested) /* Convert this to a proper C++ exception. */ throw std::bad_alloc(); } + +class BoehmGCStackAllocator : public StackAllocator { + boost::coroutines2::protected_fixedsize_stack stack { + // We allocate 8 MB, the default max stack size on NixOS. + // A smaller stack might be quicker to allocate but reduces the stack + // depth available for source filter expressions etc. + std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024)) + }; + + public: + boost::context::stack_context allocate() override { + auto sctx = stack.allocate(); + GC_add_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp); + return sctx; + } + + void deallocate(boost::context::stack_context sctx) override { + GC_remove_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp); + stack.deallocate(sctx); + } + +}; + +static BoehmGCStackAllocator boehmGCStackAllocator; + #endif @@ -256,6 +284,8 @@ void initGC() GC_set_oom_fn(oomHandler); + StackAllocator::defaultAllocator = &boehmGCStackAllocator; + /* Set the initial heap size to something fairly big (25% of physical RAM, up to a maximum of 384 MiB) so that in most cases we don't need to garbage collect at all. (Collection has a @@ -372,11 +402,6 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store) for (auto & i : evalSettings.nixPath.get()) addToSearchPath(i); } - try { - addToSearchPath("nix=" + canonPath(settings.nixDataDir + "/nix/corepkgs", true)); - } catch (Error &) { - } - if (evalSettings.restrictEval || evalSettings.pureEval) { allowedPaths = PathSet(); @@ -400,9 +425,7 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store) } } - clearValue(vEmptySet); - vEmptySet.type = tAttrs; - vEmptySet.attrs = allocBindings(0); + vEmptySet.mkAttrs(allocBindings(0)); createBaseEnv(); } @@ -429,6 +452,8 @@ Path EvalState::checkSourcePath(const Path & path_) */ Path abspath = canonPath(path_); + if (hasPrefix(abspath, corepkgsPrefix)) return abspath; + for (auto & i : *allowedPaths) { if (isDirOrInDir(abspath, i)) { found = true; @@ -518,16 +543,14 @@ Value * EvalState::addPrimOp(const string & name, the primop to a dummy value. */ if (arity == 0) { auto vPrimOp = allocValue(); - vPrimOp->type = tPrimOp; - vPrimOp->primOp = new PrimOp { .fun = primOp, .arity = 1, .name = sym }; + vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym }); Value v; mkApp(v, *vPrimOp, *vPrimOp); return addConstant(name, v); } Value * v = allocValue(); - v->type = tPrimOp; - v->primOp = new PrimOp { .fun = primOp, .arity = arity, .name = sym }; + v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl; baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(sym, v)); @@ -542,8 +565,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp) if (primOp.arity == 0) { primOp.arity = 1; auto vPrimOp = allocValue(); - vPrimOp->type = tPrimOp; - vPrimOp->primOp = new PrimOp(std::move(primOp)); + vPrimOp->mkPrimOp(new PrimOp(std::move(primOp))); Value v; mkApp(v, *vPrimOp, *vPrimOp); return addConstant(primOp.name, v); @@ -554,8 +576,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp) primOp.name = symbols.create(std::string(primOp.name, 2)); Value * v = allocValue(); - v->type = tPrimOp; - v->primOp = new PrimOp(std::move(primOp)); + v->mkPrimOp(new PrimOp(std::move(primOp))); staticBaseEnv.vars[envName] = baseEnvDispl; baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); @@ -571,10 +592,8 @@ Value & EvalState::getBuiltin(const string & name) std::optional<EvalState::Doc> EvalState::getDoc(Value & v) { - if (v.type == tPrimOp || v.type == tPrimOpApp) { + if (v.isPrimOp()) { auto v2 = &v; - while (v2->type == tPrimOpApp) - v2 = v2->primOpApp.left; if (v2->primOp->doc) return Doc { .pos = noPos, @@ -601,7 +620,7 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2)) LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2)) { throw EvalError({ - .hint = hintfmt(s, s2), + .msg = hintfmt(s, s2), .errPos = pos }); } @@ -614,7 +633,7 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3)) { throw EvalError({ - .hint = hintfmt(s, s2, s3), + .msg = hintfmt(s, s2, s3), .errPos = pos }); } @@ -623,7 +642,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const { // p1 is where the error occurred; p2 is a position mentioned in the message. throw EvalError({ - .hint = hintfmt(s, sym, p2), + .msg = hintfmt(s, sym, p2), .errPos = p1 }); } @@ -631,20 +650,15 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) { throw TypeError({ - .hint = hintfmt(s), + .msg = hintfmt(s), .errPos = pos }); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1)) -{ - throw TypeError(s, s1); -} - LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) { throw TypeError({ - .hint = hintfmt(s, fun.showNamePos(), s2), + .msg = hintfmt(s, fun.showNamePos(), s2), .errPos = pos }); } @@ -652,7 +666,7 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1)) { throw AssertionError({ - .hint = hintfmt(s, s1), + .msg = hintfmt(s, s1), .errPos = pos }); } @@ -660,7 +674,15 @@ LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1)) { throw UndefinedVarError({ - .hint = hintfmt(s, s1), + .msg = hintfmt(s, s1), + .errPos = pos + }); +} + +LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1)) +{ + throw MissingArgumentError({ + .msg = hintfmt(s, s1), .errPos = pos }); } @@ -678,15 +700,13 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con void mkString(Value & v, const char * s) { - mkStringNoCopy(v, dupString(s)); + v.mkString(dupString(s)); } Value & mkString(Value & v, std::string_view s, const PathSet & context) { - v.type = tString; - v.string.s = dupStringWithLen(s.data(), s.size()); - v.string.context = 0; + v.mkString(dupStringWithLen(s.data(), s.size())); if (!context.empty()) { size_t n = 0; v.string.context = (const char * *) @@ -701,7 +721,7 @@ Value & mkString(Value & v, std::string_view s, const PathSet & context) void mkPath(Value & v, const char * s) { - mkPathNoCopy(v, dupString(s)); + v.mkPath(dupString(s)); } @@ -762,16 +782,9 @@ Env & EvalState::allocEnv(size_t size) void EvalState::mkList(Value & v, size_t size) { - clearValue(v); - if (size == 1) - v.type = tList1; - else if (size == 2) - v.type = tList2; - else { - v.type = tListN; - v.bigList.size = size; - v.bigList.elems = size ? (Value * *) allocBytes(size * sizeof(Value *)) : 0; - } + v.mkList(size); + if (size > 2) + v.bigList.elems = (Value * *) allocBytes(size * sizeof(Value *)); nrListElems += size; } @@ -780,9 +793,7 @@ unsigned long nrThunks = 0; static inline void mkThunk(Value & v, Env & env, Expr * expr) { - v.type = tThunk; - v.thunk.env = &env; - v.thunk.expr = expr; + v.mkThunk(&env, expr); nrThunks++; } @@ -917,7 +928,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e) { Value v; e->eval(*this, env, v); - if (v.type != tBool) + if (v.type() != nBool) throwTypeError("value is %1% while a Boolean was expected", v); return v.boolean; } @@ -927,7 +938,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) { Value v; e->eval(*this, env, v); - if (v.type != tBool) + if (v.type() != nBool) throwTypeError(pos, "value is %1% while a Boolean was expected", v); return v.boolean; } @@ -936,7 +947,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v) { e->eval(*this, env, v); - if (v.type != tAttrs) + if (v.type() != nAttrs) throwTypeError("value is %1% while a set was expected", v); } @@ -1036,7 +1047,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Value nameVal; i.nameExpr->eval(state, *dynamicEnv, nameVal); state.forceValue(nameVal, i.pos); - if (nameVal.type == tNull) + if (nameVal.type() == nNull) continue; state.forceStringNoCtx(nameVal); Symbol nameSym = state.symbols.create(nameVal.string.s); @@ -1121,7 +1132,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) Symbol name = getName(i, state, env); if (def) { state.forceValue(*vAttrs, pos); - if (vAttrs->type != tAttrs || + if (vAttrs->type() != nAttrs || (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { def->eval(state, env, v); @@ -1161,7 +1172,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) state.forceValue(*vAttrs); Bindings::iterator j; Symbol name = getName(i, state, env); - if (vAttrs->type != tAttrs || + if (vAttrs->type() != nAttrs || (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { mkBool(v, false); @@ -1177,9 +1188,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) void ExprLambda::eval(EvalState & state, Env & env, Value & v) { - v.type = tLambda; - v.lambda.env = &env; - v.lambda.fun = this; + v.mkLambda(&env, this); } @@ -1197,11 +1206,11 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos) /* Figure out the number of arguments still needed. */ size_t argsDone = 0; Value * primOp = &fun; - while (primOp->type == tPrimOpApp) { + while (primOp->isPrimOpApp()) { argsDone++; primOp = primOp->primOpApp.left; } - assert(primOp->type == tPrimOp); + assert(primOp->isPrimOp()); auto arity = primOp->primOp->arity; auto argsLeft = arity - argsDone; @@ -1212,7 +1221,7 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos) Value * vArgs[arity]; auto n = arity - 1; vArgs[n--] = &arg; - for (Value * arg = &fun; arg->type == tPrimOpApp; arg = arg->primOpApp.left) + for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left) vArgs[n--] = arg->primOpApp.right; /* And call the primop. */ @@ -1222,9 +1231,7 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos) } else { Value * fun2 = allocValue(); *fun2 = fun; - v.type = tPrimOpApp; - v.primOpApp.left = fun2; - v.primOpApp.right = &arg; + v.mkPrimOpApp(fun2, &arg); } } @@ -1234,12 +1241,12 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po forceValue(fun, pos); - if (fun.type == tPrimOp || fun.type == tPrimOpApp) { + if (fun.isPrimOp() || fun.isPrimOpApp()) { callPrimOp(fun, arg, v, pos); return; } - if (fun.type == tAttrs) { + if (fun.type() == nAttrs) { auto found = fun.attrs->find(sFunctor); if (found != fun.attrs->end()) { /* fun may be allocated on the stack of the calling function, @@ -1255,7 +1262,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po } } - if (fun.type != tLambda) + if (!fun.isLambda()) throwTypeError(pos, "attempt to call something which is not a function but %1%", fun); ExprLambda & lambda(*fun.lambda.fun); @@ -1338,7 +1345,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) { forceValue(fun); - if (fun.type == tAttrs) { + if (fun.type() == nAttrs) { auto found = fun.attrs->find(sFunctor); if (found != fun.attrs->end()) { Value * v = allocValue(); @@ -1348,7 +1355,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) } } - if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) { + if (!fun.isLambda() || !fun.lambda.fun->matchAttrs) { res = fun; return; } @@ -1370,7 +1377,13 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { actualArgs->attrs->push_back(*j); } else if (!i.def) { - throwTypeError("cannot auto-call a function that has an argument without a default value ('%1%')", i.name); + throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') + +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/#ss-functions.)", i.name); + } } } @@ -1404,7 +1417,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos)) { std::ostringstream out; cond->show(out); - throwAssertionError(pos, "assertion '%1%' failed at %2%", out.str()); + throwAssertionError(pos, "assertion '%1%' failed", out.str()); } body->eval(state, env, v); } @@ -1532,7 +1545,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) NixFloat nf = 0; bool first = !forceString; - ValueType firstType = tString; + ValueType firstType = nString; for (auto & i : *es) { Value vTmp; @@ -1543,36 +1556,36 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) since paths are copied when they are used in a derivation), and none of the strings are allowed to have contexts. */ if (first) { - firstType = vTmp.type; + firstType = vTmp.type(); first = false; } - if (firstType == tInt) { - if (vTmp.type == tInt) { + if (firstType == nInt) { + if (vTmp.type() == nInt) { n += vTmp.integer; - } else if (vTmp.type == tFloat) { + } else if (vTmp.type() == nFloat) { // Upgrade the type from int to float; - firstType = tFloat; + firstType = nFloat; nf = n; nf += vTmp.fpoint; } else throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp)); - } else if (firstType == tFloat) { - if (vTmp.type == tInt) { + } else if (firstType == nFloat) { + if (vTmp.type() == nInt) { nf += vTmp.integer; - } else if (vTmp.type == tFloat) { + } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else throwEvalError(pos, "cannot add %1% to a float", showType(vTmp)); } else - s << state.coerceToString(pos, vTmp, context, false, firstType == tString); + s << state.coerceToString(pos, vTmp, context, false, firstType == nString); } - if (firstType == tInt) + if (firstType == nInt) mkInt(v, n); - else if (firstType == tFloat) + else if (firstType == nFloat) mkFloat(v, nf); - else if (firstType == tPath) { + else if (firstType == nPath) { if (!context.empty()) throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); auto path = canonPath(s.str()); @@ -1599,7 +1612,7 @@ void EvalState::forceValueDeep(Value & v) forceValue(v); - if (v.type == tAttrs) { + if (v.type() == nAttrs) { for (auto & i : *v.attrs) try { recurse(*i.value); @@ -1622,7 +1635,7 @@ void EvalState::forceValueDeep(Value & v) NixInt EvalState::forceInt(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type != tInt) + if (v.type() != nInt) throwTypeError(pos, "value is %1% while an integer was expected", v); return v.integer; } @@ -1631,9 +1644,9 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos) NixFloat EvalState::forceFloat(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type == tInt) + if (v.type() == nInt) return v.integer; - else if (v.type != tFloat) + else if (v.type() != nFloat) throwTypeError(pos, "value is %1% while a float was expected", v); return v.fpoint; } @@ -1642,7 +1655,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) bool EvalState::forceBool(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type != tBool) + if (v.type() != nBool) throwTypeError(pos, "value is %1% while a Boolean was expected", v); return v.boolean; } @@ -1650,14 +1663,14 @@ bool EvalState::forceBool(Value & v, const Pos & pos) bool EvalState::isFunctor(Value & fun) { - return fun.type == tAttrs && fun.attrs->find(sFunctor) != fun.attrs->end(); + return fun.type() == nAttrs && fun.attrs->find(sFunctor) != fun.attrs->end(); } void EvalState::forceFunction(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v)) + if (v.type() != nFunction && !isFunctor(v)) throwTypeError(pos, "value is %1% while a function was expected", v); } @@ -1665,7 +1678,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos) string EvalState::forceString(Value & v, const Pos & pos) { forceValue(v, pos); - if (v.type != tString) { + if (v.type() != nString) { if (pos) throwTypeError(pos, "value is %1% while a string was expected", v); else @@ -1698,7 +1711,7 @@ void copyContext(const Value & v, PathSet & context) std::vector<std::pair<Path, std::string>> Value::getContext() { std::vector<std::pair<Path, std::string>> res; - assert(type == tString); + assert(internalType == tString); if (string.context) for (const char * * p = string.context; *p; ++p) res.push_back(decodeContext(*p)); @@ -1731,11 +1744,11 @@ string EvalState::forceStringNoCtx(Value & v, const Pos & pos) bool EvalState::isDerivation(Value & v) { - if (v.type != tAttrs) return false; + if (v.type() != nAttrs) return false; Bindings::iterator i = v.attrs->find(sType); if (i == v.attrs->end()) return false; forceValue(*i->value); - if (i->value->type != tString) return false; + if (i->value->type() != nString) return false; return strcmp(i->value->string.s, "derivation") == 0; } @@ -1760,17 +1773,17 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, string s; - if (v.type == tString) { + if (v.type() == nString) { copyContext(v, context); return v.string.s; } - if (v.type == tPath) { + if (v.type() == nPath) { Path path(canonPath(v.path)); return copyToStore ? copyPathToStore(context, path) : path; } - if (v.type == tAttrs) { + if (v.type() == nAttrs) { auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore); if (maybeString) { return *maybeString; @@ -1780,18 +1793,18 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, return coerceToString(pos, *i->value, context, coerceMore, copyToStore); } - if (v.type == tExternal) + if (v.type() == nExternal) return v.external->coerceToString(pos, context, coerceMore, copyToStore); if (coerceMore) { /* Note that `false' is represented as an empty string for shell scripting convenience, just like `null'. */ - if (v.type == tBool && v.boolean) return "1"; - if (v.type == tBool && !v.boolean) return ""; - if (v.type == tInt) return std::to_string(v.integer); - if (v.type == tFloat) return std::to_string(v.fpoint); - if (v.type == tNull) return ""; + if (v.type() == nBool && v.boolean) return "1"; + if (v.type() == nBool && !v.boolean) return ""; + if (v.type() == nInt) return std::to_string(v.integer); + if (v.type() == nFloat) return std::to_string(v.fpoint); + if (v.type() == nNull) return ""; if (v.isList()) { string result; @@ -1854,40 +1867,38 @@ bool EvalState::eqValues(Value & v1, Value & v2) if (&v1 == &v2) return true; // Special case type-compatibility between float and int - if (v1.type == tInt && v2.type == tFloat) + if (v1.type() == nInt && v2.type() == nFloat) return v1.integer == v2.fpoint; - if (v1.type == tFloat && v2.type == tInt) + if (v1.type() == nFloat && v2.type() == nInt) return v1.fpoint == v2.integer; // All other types are not compatible with each other. - if (v1.type != v2.type) return false; + if (v1.type() != v2.type()) return false; - switch (v1.type) { + switch (v1.type()) { - case tInt: + case nInt: return v1.integer == v2.integer; - case tBool: + case nBool: return v1.boolean == v2.boolean; - case tString: + case nString: return strcmp(v1.string.s, v2.string.s) == 0; - case tPath: + case nPath: return strcmp(v1.path, v2.path) == 0; - case tNull: + case nNull: return true; - case tList1: - case tList2: - case tListN: + case nList: if (v1.listSize() != v2.listSize()) return false; for (size_t n = 0; n < v1.listSize(); ++n) if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; return true; - case tAttrs: { + case nAttrs: { /* If both sets denote a derivation (type = "derivation"), then compare their outPaths. */ if (isDerivation(v1) && isDerivation(v2)) { @@ -1909,15 +1920,13 @@ bool EvalState::eqValues(Value & v1, Value & v2) } /* Functions are incomparable. */ - case tLambda: - case tPrimOp: - case tPrimOpApp: + case nFunction: return false; - case tExternal: + case nExternal: return *v1.external == *v2.external; - case tFloat: + case nFloat: return v1.fpoint == v2.fpoint; default: @@ -2046,7 +2055,7 @@ void EvalState::printStats() string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const { throw TypeError({ - .hint = hintfmt("cannot coerce %1% to a string", showType()), + .msg = hintfmt("cannot coerce %1% to a string", showType()), .errPos = pos }); } @@ -2072,10 +2081,19 @@ EvalSettings::EvalSettings() Strings EvalSettings::getDefaultNixPath() { Strings res; - auto add = [&](const Path & p) { if (pathExists(p)) { res.push_back(p); } }; + auto add = [&](const Path & p, const std::string & s = std::string()) { + if (pathExists(p)) { + if (s.empty()) { + res.push_back(p); + } else { + res.push_back(s + "=" + p); + } + } + }; + add(getHome() + "/.nix-defexpr/channels"); - add("nixpkgs=" + settings.nixStateDir + "/nix/profiles/per-user/root/channels/nixpkgs"); - add(settings.nixStateDir + "/nix/profiles/per-user/root/channels"); + add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs"); + add(settings.nixStateDir + "/profiles/per-user/root/channels"); return res; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 0e1f61baa..e3eaed6d3 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -432,4 +432,6 @@ struct EvalSettings : Config extern EvalSettings evalSettings; +static const std::string corepkgsPrefix{"/__corepkgs__/"}; + } diff --git a/src/libexpr/fetchurl.nix b/src/libexpr/fetchurl.nix new file mode 100644 index 000000000..02531103b --- /dev/null +++ b/src/libexpr/fetchurl.nix @@ -0,0 +1,41 @@ +{ system ? "" # obsolete +, url +, hash ? "" # an SRI hash + +# Legacy hash specification +, md5 ? "", sha1 ? "", sha256 ? "", sha512 ? "" +, outputHash ? + if hash != "" then hash else if sha512 != "" then sha512 else if sha1 != "" then sha1 else if md5 != "" then md5 else sha256 +, outputHashAlgo ? + if hash != "" then "" else if sha512 != "" then "sha512" else if sha1 != "" then "sha1" else if md5 != "" then "md5" else "sha256" + +, executable ? false +, unpack ? false +, name ? baseNameOf (toString url) +}: + +derivation { + builder = "builtin:fetchurl"; + + # New-style output content requirements. + inherit outputHashAlgo outputHash; + outputHashMode = if unpack || executable then "recursive" else "flat"; + + inherit name url executable unpack; + + system = "builtin"; + + # No need to double the amount of network traffic + preferLocalBuild = true; + + impureEnvVars = [ + # We borrow these environment variables from the caller to allow + # easy proxy configuration. This is impure, but a fixed-output + # derivation like fetchurl is allowed to do so since its result is + # by definition pure. + "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy" + ]; + + # To make "nix-prefetch-url" work. + urls = [ url ]; +} diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc new file mode 100644 index 000000000..63566131e --- /dev/null +++ b/src/libexpr/flake/config.cc @@ -0,0 +1,81 @@ +#include "flake.hh" + +#include <nlohmann/json.hpp> + +namespace nix::flake { + +// setting name -> setting value -> allow or ignore. +typedef std::map<std::string, std::map<std::string, bool>> TrustedList; + +Path trustedListPath() +{ + return getDataDir() + "/nix/trusted-settings.json"; +} + +static TrustedList readTrustedList() +{ + auto path = trustedListPath(); + if (!pathExists(path)) return {}; + auto json = nlohmann::json::parse(readFile(path)); + return json; +} + +static void writeTrustedList(const TrustedList & trustedList) +{ + writeFile(trustedListPath(), nlohmann::json(trustedList).dump()); +} + +void ConfigFile::apply() +{ + std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix"}; + + for (auto & [name, value] : settings) { + + auto baseName = hasPrefix(name, "extra-") ? std::string(name, 6) : name; + + // FIXME: Move into libutil/config.cc. + std::string valueS; + if (auto s = std::get_if<std::string>(&value)) + valueS = *s; + else if (auto n = std::get_if<int64_t>(&value)) + valueS = fmt("%d", n); + else if (auto b = std::get_if<Explicit<bool>>(&value)) + valueS = b->t ? "true" : "false"; + else if (auto ss = std::get_if<std::vector<std::string>>(&value)) + valueS = concatStringsSep(" ", *ss); // FIXME: evil + else + assert(false); + + if (!whitelist.count(baseName)) { + auto trustedList = readTrustedList(); + + bool trusted = false; + + if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) { + trusted = *saved; + } 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') { + if (std::tolower(logger->ask("do you want to permanently mark this value as untrusted (y/N)?").value_or('n')) == 'y') { + trustedList[name][valueS] = false; + writeTrustedList(trustedList); + } + } else { + if (std::tolower(logger->ask("do you want to permanently mark this value as trusted (y/N)?").value_or('n')) == 'y') { + trustedList[name][valueS] = trusted = true; + writeTrustedList(trustedList); + } + } + } + + if (!trusted) { + warn("ignoring untrusted flake configuration setting '%s'", name); + continue; + } + } + + globalConfig.set(name, valueS); + } +} + +} diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index bae4d65e5..2e94490d4 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -71,14 +71,20 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree( return {std::move(tree), resolvedRef, lockedRef}; } +static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) +{ + if (value.isThunk() && value.isTrivial()) + state.forceValue(value, pos); +} + + static void expectType(EvalState & state, ValueType type, Value & value, const Pos & pos) { - if (value.type == tThunk && value.isTrivial()) - state.forceValue(value, pos); - if (value.type != type) + forceTrivialValue(state, value, pos); + if (value.type() != type) throw Error("expected %s but got %s at %s", - showType(type), showType(value.type), pos); + showType(type), showType(value.type()), pos); } static std::map<FlakeId, FlakeInput> parseFlakeInputs( @@ -87,7 +93,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs( static FlakeInput parseFlakeInput(EvalState & state, const std::string & inputName, Value * value, const Pos & pos) { - expectType(state, tAttrs, *value, pos); + expectType(state, nAttrs, *value, pos); FlakeInput input; @@ -102,24 +108,32 @@ static FlakeInput parseFlakeInput(EvalState & state, for (nix::Attr attr : *(value->attrs)) { try { if (attr.name == sUrl) { - expectType(state, tString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, *attr.pos); url = attr.value->string.s; attrs.emplace("url", *url); } else if (attr.name == sFlake) { - expectType(state, tBool, *attr.value, *attr.pos); + expectType(state, nBool, *attr.value, *attr.pos); input.isFlake = attr.value->boolean; } else if (attr.name == sInputs) { input.overrides = parseFlakeInputs(state, attr.value, *attr.pos); } else if (attr.name == sFollows) { - expectType(state, tString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, *attr.pos); input.follows = parseInputPath(attr.value->string.s); } else { - state.forceValue(*attr.value); - if (attr.value->type == tString) - attrs.emplace(attr.name, attr.value->string.s); - else - throw TypeError("flake input attribute '%s' is %s while a string is expected", - attr.name, showType(*attr.value)); + switch (attr.value->type()) { + case nString: + attrs.emplace(attr.name, attr.value->string.s); + break; + case nBool: + attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean }); + break; + case nInt: + attrs.emplace(attr.name, (long unsigned int)attr.value->integer); + break; + default: + throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", + attr.name, showType(*attr.value)); + } } } catch (Error & e) { e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name)); @@ -153,7 +167,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs( { std::map<FlakeId, FlakeInput> inputs; - expectType(state, tAttrs, *value, pos); + expectType(state, nAttrs, *value, pos); for (nix::Attr & inputAttr : *(*value).attrs) { inputs.emplace(inputAttr.name, @@ -194,15 +208,10 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0)); - - auto sEdition = state.symbols.create("edition"); // FIXME: remove soon - - if (vInfo.attrs->get(sEdition)) - warn("flake '%s' has deprecated attribute 'edition'", lockedRef); + expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0)); if (auto description = vInfo.attrs->get(state.sDescription)) { - expectType(state, tString, *description->value, *description->pos); + expectType(state, nString, *description->value, *description->pos); flake.description = description->value->string.s; } @@ -214,9 +223,9 @@ static Flake getFlake( auto sOutputs = state.symbols.create("outputs"); if (auto outputs = vInfo.attrs->get(sOutputs)) { - expectType(state, tLambda, *outputs->value, *outputs->pos); + expectType(state, nFunction, *outputs->value, *outputs->pos); - if (outputs->value->lambda.fun->matchAttrs) { + if (outputs->value->isLambda() && outputs->value->lambda.fun->matchAttrs) { for (auto & formal : outputs->value->lambda.fun->formals->formals) { if (formal.name != state.sSelf) flake.inputs.emplace(formal.name, FlakeInput { @@ -228,11 +237,41 @@ static Flake getFlake( } else throw Error("flake '%s' lacks attribute 'outputs'", lockedRef); + auto sNixConfig = state.symbols.create("nixConfig"); + + if (auto nixConfig = vInfo.attrs->get(sNixConfig)) { + expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos); + + for (auto & setting : *nixConfig->value->attrs) { + forceTrivialValue(state, *setting.value, *setting.pos); + if (setting.value->type() == nString) + flake.config.settings.insert({setting.name, state.forceStringNoCtx(*setting.value, *setting.pos)}); + else if (setting.value->type() == nInt) + flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)}); + else if (setting.value->type() == nBool) + flake.config.settings.insert({setting.name, state.forceBool(*setting.value, *setting.pos)}); + else if (setting.value->type() == nList) { + std::vector<std::string> ss; + for (unsigned int n = 0; n < setting.value->listSize(); ++n) { + auto elem = setting.value->listElems()[n]; + if (elem->type() != nString) + throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", + setting.name, showType(*setting.value)); + ss.push_back(state.forceStringNoCtx(*elem, *setting.pos)); + } + flake.config.settings.insert({setting.name, ss}); + } + else + throw TypeError("flake configuration setting '%s' is %s", + setting.name, showType(*setting.value)); + } + } + for (auto & attr : *vInfo.attrs) { - if (attr.name != sEdition && - attr.name != state.sDescription && + if (attr.name != state.sDescription && attr.name != sInputs && - attr.name != sOutputs) + attr.name != sOutputs && + attr.name != sNixConfig) throw Error("flake '%s' has an unsupported attribute '%s', at %s", lockedRef, attr.name, *attr.pos); } @@ -259,284 +298,298 @@ LockedFlake lockFlake( auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache); - // FIXME: symlink attack - auto oldLockFile = LockFile::read( - flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock"); - - debug("old lock file: %s", oldLockFile); - - // FIXME: check whether all overrides are used. - std::map<InputPath, FlakeInput> overrides; - std::set<InputPath> overridesUsed, updatesUsed; - - for (auto & i : lockFlags.inputOverrides) - overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second }); - - LockFile newLockFile; - - std::vector<FlakeRef> parents; - - std::function<void( - const FlakeInputs & flakeInputs, - std::shared_ptr<Node> node, - const InputPath & inputPathPrefix, - std::shared_ptr<const Node> oldNode)> - computeLocks; - - computeLocks = [&]( - const FlakeInputs & flakeInputs, - std::shared_ptr<Node> node, - const InputPath & inputPathPrefix, - std::shared_ptr<const Node> oldNode) - { - debug("computing lock file node '%s'", printInputPath(inputPathPrefix)); - - /* Get the overrides (i.e. attributes of the form - 'inputs.nixops.inputs.nixpkgs.url = ...'). */ - // FIXME: check this - for (auto & [id, input] : flake.inputs) { - for (auto & [idOverride, inputOverride] : input.overrides) { - auto inputPath(inputPathPrefix); - inputPath.push_back(id); - inputPath.push_back(idOverride); - overrides.insert_or_assign(inputPath, inputOverride); - } - } - - /* Go over the flake inputs, resolve/fetch them if - necessary (i.e. if they're new or the flakeref changed - from what's in the lock file). */ - for (auto & [id, input2] : flakeInputs) { - auto inputPath(inputPathPrefix); - inputPath.push_back(id); - auto inputPathS = printInputPath(inputPath); - debug("computing input '%s'", inputPathS); - - /* Do we have an override for this input from one of the - ancestors? */ - auto i = overrides.find(inputPath); - bool hasOverride = i != overrides.end(); - if (hasOverride) overridesUsed.insert(inputPath); - auto & input = hasOverride ? i->second : input2; - - /* Resolve 'follows' later (since it may refer to an input - path we haven't processed yet. */ - if (input.follows) { - InputPath target; - if (hasOverride || input.absolute) - /* 'follows' from an override is relative to the - root of the graph. */ - target = *input.follows; - else { - /* Otherwise, it's relative to the current flake. */ - target = inputPathPrefix; - for (auto & i : *input.follows) target.push_back(i); + try { + + // FIXME: symlink attack + auto oldLockFile = LockFile::read( + flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock"); + + debug("old lock file: %s", oldLockFile); + + // FIXME: check whether all overrides are used. + std::map<InputPath, FlakeInput> overrides; + std::set<InputPath> overridesUsed, updatesUsed; + + for (auto & i : lockFlags.inputOverrides) + overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second }); + + LockFile newLockFile; + + std::vector<FlakeRef> parents; + + std::function<void( + const FlakeInputs & flakeInputs, + std::shared_ptr<Node> node, + const InputPath & inputPathPrefix, + std::shared_ptr<const Node> oldNode)> + computeLocks; + + computeLocks = [&]( + const FlakeInputs & flakeInputs, + std::shared_ptr<Node> node, + const InputPath & inputPathPrefix, + std::shared_ptr<const Node> oldNode) + { + debug("computing lock file node '%s'", printInputPath(inputPathPrefix)); + + /* Get the overrides (i.e. attributes of the form + 'inputs.nixops.inputs.nixpkgs.url = ...'). */ + // FIXME: check this + for (auto & [id, input] : flake.inputs) { + for (auto & [idOverride, inputOverride] : input.overrides) { + auto inputPath(inputPathPrefix); + inputPath.push_back(id); + inputPath.push_back(idOverride); + overrides.insert_or_assign(inputPath, inputOverride); } - debug("input '%s' follows '%s'", inputPathS, printInputPath(target)); - node->inputs.insert_or_assign(id, target); - continue; } - assert(input.ref); - - /* Do we have an entry in the existing lock file? And we - don't have a --update-input flag for this input? */ - std::shared_ptr<LockedNode> oldLock; - - updatesUsed.insert(inputPath); - - if (oldNode && !lockFlags.inputUpdates.count(inputPath)) - if (auto oldLock2 = get(oldNode->inputs, id)) - if (auto oldLock3 = std::get_if<0>(&*oldLock2)) - oldLock = *oldLock3; - - if (oldLock - && oldLock->originalRef == *input.ref - && !hasOverride) - { - debug("keeping existing input '%s'", inputPathS); - - /* 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>( - oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake); - - node->inputs.insert_or_assign(id, childNode); - - /* If we have an --update-input flag for an input - of this input, then we must fetch the flake to - update it. */ - auto lb = lockFlags.inputUpdates.lower_bound(inputPath); - - auto hasChildUpdate = - lb != lockFlags.inputUpdates.end() - && lb->size() > inputPath.size() - && std::equal(inputPath.begin(), inputPath.end(), lb->begin()); - - if (hasChildUpdate) { - auto inputFlake = getFlake( - state, oldLock->lockedRef, false, flakeCache); - computeLocks(inputFlake.inputs, childNode, inputPath, oldLock); - } else { - /* No need to fetch this flake, we can be - lazy. However there may be new overrides on the - inputs of this flake, so we need to check - those. */ - FlakeInputs fakeInputs; - - for (auto & i : oldLock->inputs) { - if (auto lockedNode = std::get_if<0>(&i.second)) { - fakeInputs.emplace(i.first, FlakeInput { - .ref = (*lockedNode)->originalRef, - .isFlake = (*lockedNode)->isFlake, - }); - } else if (auto follows = std::get_if<1>(&i.second)) { - fakeInputs.emplace(i.first, FlakeInput { - .follows = *follows, - .absolute = true - }); + /* Go over the flake inputs, resolve/fetch them if + necessary (i.e. if they're new or the flakeref changed + from what's in the lock file). */ + for (auto & [id, input2] : flakeInputs) { + auto inputPath(inputPathPrefix); + inputPath.push_back(id); + auto inputPathS = printInputPath(inputPath); + debug("computing input '%s'", inputPathS); + + try { + + /* Do we have an override for this input from one of the + ancestors? */ + auto i = overrides.find(inputPath); + bool hasOverride = i != overrides.end(); + if (hasOverride) overridesUsed.insert(inputPath); + auto & input = hasOverride ? i->second : input2; + + /* Resolve 'follows' later (since it may refer to an input + path we haven't processed yet. */ + if (input.follows) { + InputPath target; + if (hasOverride || input.absolute) + /* 'follows' from an override is relative to the + root of the graph. */ + target = *input.follows; + else { + /* Otherwise, it's relative to the current flake. */ + target = inputPathPrefix; + for (auto & i : *input.follows) target.push_back(i); } + debug("input '%s' follows '%s'", inputPathS, printInputPath(target)); + node->inputs.insert_or_assign(id, target); + continue; } - computeLocks(fakeInputs, childNode, inputPath, oldLock); - } + assert(input.ref); + + /* Do we have an entry in the existing lock file? And we + don't have a --update-input flag for this input? */ + std::shared_ptr<LockedNode> oldLock; + + updatesUsed.insert(inputPath); + + if (oldNode && !lockFlags.inputUpdates.count(inputPath)) + if (auto oldLock2 = get(oldNode->inputs, id)) + if (auto oldLock3 = std::get_if<0>(&*oldLock2)) + oldLock = *oldLock3; + + if (oldLock + && oldLock->originalRef == *input.ref + && !hasOverride) + { + debug("keeping existing input '%s'", inputPathS); + + /* 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>( + oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake); + + node->inputs.insert_or_assign(id, childNode); + + /* If we have an --update-input flag for an input + of this input, then we must fetch the flake to + update it. */ + auto lb = lockFlags.inputUpdates.lower_bound(inputPath); + + auto hasChildUpdate = + lb != lockFlags.inputUpdates.end() + && lb->size() > inputPath.size() + && std::equal(inputPath.begin(), inputPath.end(), lb->begin()); + + if (hasChildUpdate) { + auto inputFlake = getFlake( + state, oldLock->lockedRef, false, flakeCache); + computeLocks(inputFlake.inputs, childNode, inputPath, oldLock); + } else { + /* No need to fetch this flake, we can be + lazy. However there may be new overrides on the + inputs of this flake, so we need to check + those. */ + FlakeInputs fakeInputs; + + for (auto & i : oldLock->inputs) { + if (auto lockedNode = std::get_if<0>(&i.second)) { + fakeInputs.emplace(i.first, FlakeInput { + .ref = (*lockedNode)->originalRef, + .isFlake = (*lockedNode)->isFlake, + }); + } else if (auto follows = std::get_if<1>(&i.second)) { + fakeInputs.emplace(i.first, FlakeInput { + .follows = *follows, + .absolute = true + }); + } + } + + computeLocks(fakeInputs, childNode, inputPath, oldLock); + } - } else { - /* We need to create a new lock file entry. So fetch - this input. */ - debug("creating new input '%s'", inputPathS); - - if (!lockFlags.allowMutable && !input.ref->input.isImmutable()) - throw Error("cannot update flake input '%s' in pure mode", inputPathS); - - if (input.isFlake) { - auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache); - - /* Note: in case of an --override-input, we use - the *original* ref (input2.ref) for the - "original" field, rather than the - override. This ensures that the override isn't - nuked the next time we update the lock - file. That is, overrides are sticky unless you - use --no-write-lock-file. */ - auto childNode = std::make_shared<LockedNode>( - inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref); - - node->inputs.insert_or_assign(id, childNode); - - /* Guard against circular flake imports. */ - for (auto & parent : parents) - if (parent == *input.ref) - throw Error("found circular import of flake '%s'", parent); - parents.push_back(*input.ref); - Finally cleanup([&]() { parents.pop_back(); }); - - /* Recursively process the inputs of this - flake. Also, unless we already have this flake - in the top-level lock file, use this flake's - own lock file. */ - computeLocks( - inputFlake.inputs, childNode, inputPath, - oldLock - ? std::dynamic_pointer_cast<const Node>(oldLock) - : LockFile::read( - inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root); - } + } else { + /* We need to create a new lock file entry. So fetch + this input. */ + debug("creating new input '%s'", inputPathS); + + if (!lockFlags.allowMutable && !input.ref->input.isImmutable()) + throw Error("cannot update flake input '%s' in pure mode", inputPathS); + + if (input.isFlake) { + auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache); + + /* Note: in case of an --override-input, we use + the *original* ref (input2.ref) for the + "original" field, rather than the + override. This ensures that the override isn't + nuked the next time we update the lock + file. That is, overrides are sticky unless you + use --no-write-lock-file. */ + auto childNode = std::make_shared<LockedNode>( + inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref); + + node->inputs.insert_or_assign(id, childNode); + + /* Guard against circular flake imports. */ + for (auto & parent : parents) + if (parent == *input.ref) + throw Error("found circular import of flake '%s'", parent); + parents.push_back(*input.ref); + Finally cleanup([&]() { parents.pop_back(); }); + + /* Recursively process the inputs of this + flake. Also, unless we already have this flake + in the top-level lock file, use this flake's + own lock file. */ + computeLocks( + inputFlake.inputs, childNode, inputPath, + oldLock + ? std::dynamic_pointer_cast<const Node>(oldLock) + : LockFile::read( + inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root); + } + + else { + auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( + state, *input.ref, lockFlags.useRegistries, flakeCache); + node->inputs.insert_or_assign(id, + std::make_shared<LockedNode>(lockedRef, *input.ref, false)); + } + } - else { - auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( - state, *input.ref, lockFlags.useRegistries, flakeCache); - node->inputs.insert_or_assign(id, - std::make_shared<LockedNode>(lockedRef, *input.ref, false)); + } catch (Error & e) { + e.addTrace({}, "while updating the flake input '%s'", inputPathS); + throw; } } + }; + + computeLocks( + flake.inputs, newLockFile.root, {}, + lockFlags.recreateLockFile ? nullptr : oldLockFile.root); + + for (auto & i : lockFlags.inputOverrides) + if (!overridesUsed.count(i.first)) + warn("the flag '--override-input %s %s' does not match any input", + printInputPath(i.first), i.second); + + for (auto & i : lockFlags.inputUpdates) + if (!updatesUsed.count(i)) + warn("the flag '--update-input %s' does not match any input", printInputPath(i)); + + /* Check 'follows' inputs. */ + newLockFile.check(); + + debug("new lock file: %s", newLockFile); + + /* Check whether we need to / can write the new lock file. */ + if (!(newLockFile == oldLockFile)) { + + auto diff = LockFile::diff(oldLockFile, newLockFile); + + if (lockFlags.writeLockFile) { + if (auto sourcePath = topRef.input.getSourcePath()) { + if (!newLockFile.isImmutable()) { + if (settings.warnDirty) + warn("will not write lock file of flake '%s' because it has a mutable input", topRef); + } else { + if (!lockFlags.updateLockFile) + throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); + + auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"; + + auto path = *sourcePath + "/" + relPath; + + bool lockFileExists = pathExists(path); + + if (lockFileExists) { + auto s = chomp(diff); + if (s.empty()) + warn("updating lock file '%s'", path); + else + warn("updating lock file '%s':\n%s", path, s); + } else + warn("creating lock file '%s'", path); + + newLockFile.write(path); + + topRef.input.markChangedFile( + (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock", + lockFlags.commitLockFile + ? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s", + relPath, lockFileExists ? "Update" : "Add", diff)) + : std::nullopt); + + /* Rewriting the lockfile changed the top-level + repo, so we should re-read it. FIXME: we could + also just clear the 'rev' field... */ + auto prevLockedRef = flake.lockedRef; + FlakeCache dummyCache; + flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache); + + if (lockFlags.commitLockFile && + flake.lockedRef.input.getRev() && + prevLockedRef.input.getRev() != flake.lockedRef.input.getRev()) + warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev()); + + /* Make sure that we picked up the change, + i.e. the tree should usually be dirty + now. Corner case: we could have reverted from a + dirty to a clean tree! */ + if (flake.lockedRef.input == prevLockedRef.input + && !flake.lockedRef.input.isImmutable()) + throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef); + } + } else + throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef); + } else + warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff)); } - }; - computeLocks( - flake.inputs, newLockFile.root, {}, - lockFlags.recreateLockFile ? nullptr : oldLockFile.root); - - for (auto & i : lockFlags.inputOverrides) - if (!overridesUsed.count(i.first)) - warn("the flag '--override-input %s %s' does not match any input", - printInputPath(i.first), i.second); - - for (auto & i : lockFlags.inputUpdates) - if (!updatesUsed.count(i)) - warn("the flag '--update-input %s' does not match any input", printInputPath(i)); - - /* Check 'follows' inputs. */ - newLockFile.check(); - - debug("new lock file: %s", newLockFile); - - /* Check whether we need to / can write the new lock file. */ - if (!(newLockFile == oldLockFile)) { - - auto diff = LockFile::diff(oldLockFile, newLockFile); - - if (lockFlags.writeLockFile) { - if (auto sourcePath = topRef.input.getSourcePath()) { - if (!newLockFile.isImmutable()) { - if (settings.warnDirty) - warn("will not write lock file of flake '%s' because it has a mutable input", topRef); - } else { - if (!lockFlags.updateLockFile) - throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); - - auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"; - - auto path = *sourcePath + "/" + relPath; - - bool lockFileExists = pathExists(path); - - if (lockFileExists) { - auto s = chomp(diff); - if (s.empty()) - warn("updating lock file '%s'", path); - else - warn("updating lock file '%s':\n%s", path, s); - } else - warn("creating lock file '%s'", path); - - newLockFile.write(path); - - topRef.input.markChangedFile( - (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock", - lockFlags.commitLockFile - ? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s", - relPath, lockFileExists ? "Update" : "Add", diff)) - : std::nullopt); - - /* Rewriting the lockfile changed the top-level - repo, so we should re-read it. FIXME: we could - also just clear the 'rev' field... */ - auto prevLockedRef = flake.lockedRef; - FlakeCache dummyCache; - flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache); - - if (lockFlags.commitLockFile && - flake.lockedRef.input.getRev() && - prevLockedRef.input.getRev() != flake.lockedRef.input.getRev()) - warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev()); - - /* Make sure that we picked up the change, - i.e. the tree should usually be dirty - now. Corner case: we could have reverted from a - dirty to a clean tree! */ - if (flake.lockedRef.input == prevLockedRef.input - && !flake.lockedRef.input.isImmutable()) - throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef); - } - } else - throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef); - } else - warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff)); - } + return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) }; - return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) }; + } catch (Error & e) { + e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string()); + throw; + } } void callFlake(EvalState & state, diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index 69c779af8..d17d5e183 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -17,23 +17,55 @@ struct FlakeInput; typedef std::map<FlakeId, FlakeInput> FlakeInputs; +/* FlakeInput is the 'Flake'-level parsed form of the "input" entries + * in the flake file. + * + * A FlakeInput is normally constructed by the 'parseFlakeInput' + * function which parses the input specification in the '.flake' file + * to create a 'FlakeRef' (a fetcher, the fetcher-specific + * representation of the input specification, and possibly the fetched + * local store path result) and then creating this FlakeInput to hold + * that FlakeRef, along with anything that might override that + * FlakeRef (like command-line overrides or "follows" specifications). + * + * A FlakeInput is also sometimes constructed directly from a FlakeRef + * instead of starting at the flake-file input specification + * (e.g. overrides, follows, and implicit inputs). + * + * A FlakeInput will usually have one of either "ref" or "follows" + * set. If not otherwise specified, a "ref" will be generated to a + * 'type="indirect"' flake, which is treated as simply the name of a + * flake to be resolved in the registry. + */ + struct FlakeInput { std::optional<FlakeRef> ref; - bool isFlake = true; + bool isFlake = true; // true = process flake to get outputs, false = (fetched) static source path std::optional<InputPath> follows; bool absolute = false; // whether 'follows' is relative to the flake root FlakeInputs overrides; }; +struct ConfigFile +{ + using ConfigValue = std::variant<std::string, int64_t, Explicit<bool>, std::vector<std::string>>; + + std::map<std::string, ConfigValue> settings; + + void apply(); +}; + +/* The contents of a flake.nix file. */ struct Flake { - FlakeRef originalRef; - FlakeRef resolvedRef; - FlakeRef lockedRef; + FlakeRef originalRef; // the original flake specification (by the user) + FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake + FlakeRef lockedRef; // the specific local store result of invoking the fetcher std::optional<std::string> description; std::shared_ptr<const fetchers::Tree> sourceInfo; FlakeInputs inputs; + ConfigFile config; // 'nixConfig' attribute ~Flake(); }; @@ -81,7 +113,7 @@ struct LockFlags /* Whether to commit changes to flake.lock. */ bool commitLockFile = false; - /* Flake inputs to be overriden. */ + /* Flake inputs to be overridden. */ std::map<InputPath, FlakeRef> inputOverrides; /* Flake inputs to be updated. This means that any existing lock diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh index f4eb825a6..0292eb210 100644 --- a/src/libexpr/flake/flakeref.hh +++ b/src/libexpr/flake/flakeref.hh @@ -12,10 +12,33 @@ class Store; typedef std::string FlakeId; +/* A flake reference specifies how to fetch a flake or raw source + * (e.g. from a Git repository). It is created from a URL-like syntax + * (e.g. 'github:NixOS/patchelf'), an attrset representation (e.g. '{ + * type="github"; owner = "NixOS"; repo = "patchelf"; }'), or a local + * path. + * + * Each flake will have a number of FlakeRef objects: one for each + * input to the flake. + * + * The normal method of constructing a FlakeRef is by starting with an + * input description (usually the attrs or a url from the flake file), + * locating a fetcher for that input, and then capturing the Input + * object that fetcher generates (usually via + * FlakeRef::fromAttrs(attrs) or parseFlakeRef(url) calls). + * + * The actual fetch not have been performed yet (i.e. a FlakeRef may + * be lazy), but the fetcher can be invoked at any time via the + * FlakeRef to ensure the store is populated with this input. + */ + struct FlakeRef { + /* fetcher-specific representation of the input, sufficient to + perform the fetch operation. */ fetchers::Input input; + /* sub-path within the fetched input that represents this input */ Path subdir; bool operator==(const FlakeRef & other) const; diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc index bb46e1bb4..6089d1363 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -34,7 +34,8 @@ LockedNode::LockedNode(const nlohmann::json & json) , isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true) { if (!lockedRef.input.isImmutable()) - throw Error("lockfile contains mutable lock '%s'", attrsToJson(lockedRef.input.toAttrs())); + throw Error("lockfile contains mutable lock '%s'", + fetchers::attrsToJSON(lockedRef.input.toAttrs())); } StorePath LockedNode::computeStorePath(Store & store) const @@ -77,7 +78,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) { if (jsonNode.find("inputs") == jsonNode.end()) return; for (auto & i : jsonNode["inputs"].items()) { - if (i.value().is_array()) { + if (i.value().is_array()) { // FIXME: remove, obsolete InputPath path; for (auto & j : i.value()) path.push_back(j); @@ -86,10 +87,13 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) std::string inputKey = i.value(); auto k = nodeMap.find(inputKey); if (k == nodeMap.end()) { - auto jsonNode2 = json["nodes"][inputKey]; - auto input = std::make_shared<LockedNode>(jsonNode2); + auto nodes = json["nodes"]; + 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); k = nodeMap.insert_or_assign(inputKey, input).first; - getInputs(*input, jsonNode2); + getInputs(*input, *jsonNode2); } if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second)) node.inputs.insert_or_assign(i.key(), child); @@ -110,7 +114,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) // a bit since we don't need to worry about cycles. } -nlohmann::json LockFile::toJson() const +nlohmann::json LockFile::toJSON() const { nlohmann::json nodes; std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys; @@ -154,8 +158,8 @@ nlohmann::json LockFile::toJson() const } if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) { - n["original"] = fetchers::attrsToJson(lockedNode->originalRef.toAttrs()); - n["locked"] = fetchers::attrsToJson(lockedNode->lockedRef.toAttrs()); + n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs()); + n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs()); if (!lockedNode->isFlake) n["flake"] = false; } @@ -174,7 +178,7 @@ nlohmann::json LockFile::toJson() const std::string LockFile::to_string() const { - return toJson().dump(2); + return toJSON().dump(2); } LockFile LockFile::read(const Path & path) @@ -185,7 +189,7 @@ LockFile LockFile::read(const Path & path) std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile) { - stream << lockFile.toJson().dump(2); + stream << lockFile.toJSON().dump(2); return stream; } @@ -223,7 +227,7 @@ bool LockFile::isImmutable() const bool LockFile::operator ==(const LockFile & other) const { // FIXME: slow - return toJson() == other.toJson(); + return toJSON() == other.toJSON(); } InputPath parseInputPath(std::string_view s) diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh index 627794d8c..96f1edc76 100644 --- a/src/libexpr/flake/lockfile.hh +++ b/src/libexpr/flake/lockfile.hh @@ -52,7 +52,7 @@ struct LockFile LockFile() {}; LockFile(const nlohmann::json & json, const Path & path); - nlohmann::json toJson() const; + nlohmann::json toJSON() const; std::string to_string() const; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 91916e8bf..f774e6493 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -2,6 +2,7 @@ #include "util.hh" #include "eval-inline.hh" #include "store-api.hh" +#include "path-with-outputs.hh" #include <cstring> #include <regex> @@ -19,7 +20,7 @@ DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs) DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs) : state(&state), attrs(nullptr), attrPath("") { - auto [drvPath, selectedOutputs] = store->parsePathWithOutputs(drvPathWithOutputs); + auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs); this->drvPath = store->printStorePath(drvPath); @@ -128,7 +129,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) if (!outTI->isList()) throw errMsg; Outputs result; for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) { - if ((*i)->type != tString) throw errMsg; + if ((*i)->type() != nString) throw errMsg; auto out = outputs.find((*i)->string.s); if (out == outputs.end()) throw errMsg; result.insert(*out); @@ -172,20 +173,20 @@ StringSet DrvInfo::queryMetaNames() bool DrvInfo::checkMeta(Value & v) { state->forceValue(v); - if (v.isList()) { + if (v.type() == nList) { for (unsigned int n = 0; n < v.listSize(); ++n) if (!checkMeta(*v.listElems()[n])) return false; return true; } - else if (v.type == tAttrs) { + else if (v.type() == nAttrs) { Bindings::iterator i = v.attrs->find(state->sOutPath); if (i != v.attrs->end()) return false; for (auto & i : *v.attrs) if (!checkMeta(*i.value)) return false; return true; } - else return v.type == tInt || v.type == tBool || v.type == tString || - v.type == tFloat; + else return v.type() == nInt || v.type() == nBool || v.type() == nString || + v.type() == nFloat; } @@ -201,7 +202,7 @@ Value * DrvInfo::queryMeta(const string & name) string DrvInfo::queryMetaString(const string & name) { Value * v = queryMeta(name); - if (!v || v->type != tString) return ""; + if (!v || v->type() != nString) return ""; return v->string.s; } @@ -210,12 +211,12 @@ NixInt DrvInfo::queryMetaInt(const string & name, NixInt def) { Value * v = queryMeta(name); if (!v) return def; - if (v->type == tInt) return v->integer; - if (v->type == tString) { + if (v->type() == nInt) return v->integer; + if (v->type() == nString) { /* Backwards compatibility with before we had support for integer meta fields. */ - NixInt n; - if (string2Int(v->string.s, n)) return n; + if (auto n = string2Int<NixInt>(v->string.s)) + return *n; } return def; } @@ -224,12 +225,12 @@ NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def) { Value * v = queryMeta(name); if (!v) return def; - if (v->type == tFloat) return v->fpoint; - if (v->type == tString) { + if (v->type() == nFloat) return v->fpoint; + if (v->type() == nString) { /* Backwards compatibility with before we had support for float meta fields. */ - NixFloat n; - if (string2Float(v->string.s, n)) return n; + if (auto n = string2Float<NixFloat>(v->string.s)) + return *n; } return def; } @@ -239,8 +240,8 @@ bool DrvInfo::queryMetaBool(const string & name, bool def) { Value * v = queryMeta(name); if (!v) return def; - if (v->type == tBool) return v->boolean; - if (v->type == tString) { + if (v->type() == nBool) return v->boolean; + if (v->type() == nString) { /* Backwards compatibility with before we had support for Boolean meta fields. */ if (strcmp(v->string.s, "true") == 0) return true; @@ -331,7 +332,7 @@ static void getDerivations(EvalState & state, Value & vIn, /* Process the expression. */ if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ; - else if (v.type == tAttrs) { + else if (v.type() == nAttrs) { /* !!! undocumented hackery to support combining channels in nix-env.cc. */ @@ -353,7 +354,7 @@ static void getDerivations(EvalState & state, Value & vIn, /* If the value of this attribute is itself a set, should we recurse into it? => Only if it has a `recurseForDerivations = true' attribute. */ - if (i->value->type == tAttrs) { + if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos)) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); @@ -362,7 +363,7 @@ static void getDerivations(EvalState & state, Value & vIn, } } - else if (v.isList()) { + else if (v.type() == nList) { for (unsigned int n = 0; n < v.listSize(); ++n) { string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str()); if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures)) diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index f6e83926b..7298419d9 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -12,6 +12,10 @@ %{ +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunneeded-internal-declaration" +#endif + #include <boost/lexical_cast.hpp> #include "nixexpr.hh" diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk index 687a8ccda..26c53d301 100644 --- a/src/libexpr/local.mk +++ b/src/libexpr/local.mk @@ -15,7 +15,7 @@ libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/lib libexpr_LIBS = libutil libstore libfetchers -libexpr_LDFLAGS = +libexpr_LDFLAGS = -lboost_context ifneq ($(OS), FreeBSD) libexpr_LDFLAGS += -ldl endif @@ -35,13 +35,11 @@ $(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh -dist-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh - $(eval $(call install-file-in, $(d)/nix-expr.pc, $(prefix)/lib/pkgconfig, 0644)) $(foreach i, $(wildcard src/libexpr/flake/*.hh), \ $(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644))) -$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh +$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index d5698011f..492b819e7 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -284,7 +284,7 @@ void ExprVar::bindVars(const StaticEnv & env) "undefined variable" error now. */ if (withLevel == -1) throw UndefinedVarError({ - .hint = hintfmt("undefined variable '%1%'", name), + .msg = hintfmt("undefined variable '%1%'", name), .errPos = pos }); fromWith = true; diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index e4cbc660f..8df8055b3 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -17,6 +17,7 @@ MakeError(ThrownError, AssertionError); MakeError(Abort, EvalError); MakeError(TypeError, EvalError); MakeError(UndefinedVarError, Error); +MakeError(MissingArgumentError, EvalError); MakeError(RestrictedPathError, Error); @@ -129,7 +130,7 @@ struct ExprPath : Expr { string s; Value v; - ExprPath(const string & s) : s(s) { mkPathNoCopy(v, this->s.c_str()); }; + ExprPath(const string & s) : s(s) { v.mkPath(this->s.c_str()); }; COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); }; @@ -238,7 +239,7 @@ struct ExprLambda : Expr { if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end()) throw ParseError({ - .hint = hintfmt("duplicate formal function argument '%1%'", arg), + .msg = hintfmt("duplicate formal function argument '%1%'", arg), .errPos = pos }); }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index a4c84c526..49d995bb9 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -32,7 +32,7 @@ namespace nix { Path basePath; Symbol file; FileOrigin origin; - ErrorInfo error; + std::optional<ErrorInfo> error; Symbol sLetBody; ParseData(EvalState & state) : state(state) @@ -66,8 +66,8 @@ namespace nix { static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos) { throw ParseError({ - .hint = hintfmt("attribute '%1%' already defined at %2%", - showAttrPath(attrPath), prevPos), + .msg = hintfmt("attribute '%1%' already defined at %2%", + showAttrPath(attrPath), prevPos), .errPos = pos }); } @@ -75,7 +75,7 @@ static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prev static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) { throw ParseError({ - .hint = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos), + .msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos), .errPos = pos }); } @@ -146,7 +146,7 @@ static void addFormal(const Pos & pos, Formals * formals, const Formal & formal) { if (!formals->argNames.insert(formal.name).second) throw ParseError({ - .hint = hintfmt("duplicate formal function argument '%1%'", + .msg = hintfmt("duplicate formal function argument '%1%'", formal.name), .errPos = pos }); @@ -258,7 +258,7 @@ static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error) { data->error = { - .hint = hintfmt(error), + .msg = hintfmt(error), .errPos = makeCurPos(*loc, data) }; } @@ -338,7 +338,7 @@ expr_function | LET binds IN expr_function { if (!$2->dynamicAttrs.empty()) throw ParseError({ - .hint = hintfmt("dynamic attributes not allowed in let"), + .msg = hintfmt("dynamic attributes not allowed in let"), .errPos = CUR_POS }); $$ = new ExprLet($2, $4); @@ -418,7 +418,7 @@ expr_simple static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals"); if (noURLLiterals) throw ParseError({ - .hint = hintfmt("URL literals are disabled"), + .msg = hintfmt("URL literals are disabled"), .errPos = CUR_POS }); $$ = new ExprString(data->symbols.create($1)); @@ -491,7 +491,7 @@ attrs delete str; } else throw ParseError({ - .hint = hintfmt("dynamic attributes not allowed in inherit"), + .msg = hintfmt("dynamic attributes not allowed in inherit"), .errPos = makeCurPos(@2, data) }); } @@ -576,7 +576,7 @@ Expr * EvalState::parse(const char * text, FileOrigin origin, ParseData data(*this); data.origin = origin; switch (origin) { - case foFile: + case foFile: data.file = data.symbols.create(path); break; case foStdin: @@ -593,7 +593,7 @@ Expr * EvalState::parse(const char * text, FileOrigin origin, int res = yyparse(scanner, &data); yylex_destroy(scanner); - if (res) throw ParseError(data.error); + if (res) throw ParseError(data.error.value()); data.result->bindVars(staticEnv); @@ -698,8 +698,12 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos Path res = r.second + suffix; if (pathExists(res)) return canonPath(res); } + + if (hasPrefix(path, "nix/")) + return corepkgsPrefix + path.substr(4); + throw ThrownError({ - .hint = hintfmt(evalSettings.pureEval + .msg = hintfmt(evalSettings.pureEval ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", path), @@ -721,8 +725,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl store, resolveUri(elem.second), "source", false).first.storePath) }; } catch (FileTransferError & e) { logWarning({ - .name = "Entry download", - .hint = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) + .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) }); res = { false, "" }; } @@ -732,8 +735,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl res = { true, path }; else { logWarning({ - .name = "Entry not found", - .hint = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second) + .msg = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second) }); res = { false, "" }; } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 2b304aab0..428adf4c2 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -35,7 +35,7 @@ InvalidPathError::InvalidPathError(const Path & path) : void EvalState::realiseContext(const PathSet & context) { - std::vector<StorePathWithOutputs> drvs; + std::vector<DerivedPath::Built> drvs; for (auto & i : context) { auto [ctxS, outputName] = decodeContext(i); @@ -43,7 +43,7 @@ void EvalState::realiseContext(const PathSet & context) if (!store->isValidPath(ctx)) throw InvalidPathError(store->printStorePath(ctx)); if (!outputName.empty() && ctx.isDerivation()) { - drvs.push_back(StorePathWithOutputs{ctx, {outputName}}); + drvs.push_back({ctx, {outputName}}); } } @@ -51,14 +51,16 @@ void EvalState::realiseContext(const PathSet & context) if (!evalSettings.enableImportFromDerivation) throw EvalError("attempted to realize '%1%' during evaluation but 'allow-import-from-derivation' is false", - store->printStorePath(drvs.begin()->path)); + store->printStorePath(drvs.begin()->drvPath)); /* For performance, prefetch all substitute info. */ StorePathSet willBuild, willSubstitute, unknown; uint64_t downloadSize, narSize; - store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize); + std::vector<DerivedPath> buildReqs; + for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d }); + store->queryMissing(buildReqs, willBuild, willSubstitute, unknown, downloadSize, narSize); - store->buildPaths(drvs); + store->buildPaths(buildReqs); /* Add the output of this derivations to the allowed paths. */ @@ -115,9 +117,12 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path), + .msg = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos }); + } catch (Error & e) { + e.addTrace(pos, "while importing '%s'", path); + throw e; } Path realPath = state.checkSourcePath(state.toRealPath(path, context)); @@ -164,7 +169,15 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS state.forceFunction(**fun, pos); mkApp(v, **fun, w); state.forceAttrs(v, pos); - } else { + } + + else if (path == corepkgsPrefix + "fetchurl.nix") { + state.eval(state.parseExprFromString( + #include "fetchurl.nix.gen.hh" + , "/"), v); + } + + else { if (!vScope) state.evalFile(realPath, v); else { @@ -274,7 +287,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt( + .msg = hintfmt( "cannot import '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos @@ -314,7 +327,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) auto count = args[0]->listSize(); if (count == 0) { throw EvalError({ - .hint = hintfmt("at least one argument to 'exec' required"), + .msg = hintfmt("at least one argument to 'exec' required"), .errPos = pos }); } @@ -328,7 +341,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt("cannot execute '%1%', since path '%2%' is not valid", + .msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid", program, e.path), .errPos = pos }); @@ -356,24 +369,20 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu { state.forceValue(*args[0], pos); string t; - switch (args[0]->type) { - case tInt: t = "int"; break; - case tBool: t = "bool"; break; - case tString: t = "string"; break; - case tPath: t = "path"; break; - case tNull: t = "null"; break; - case tAttrs: t = "set"; break; - case tList1: case tList2: case tListN: t = "list"; break; - case tLambda: - case tPrimOp: - case tPrimOpApp: - t = "lambda"; - break; - case tExternal: + switch (args[0]->type()) { + case nInt: t = "int"; break; + case nBool: t = "bool"; break; + case nString: t = "string"; break; + case nPath: t = "path"; break; + case nNull: t = "null"; break; + case nAttrs: t = "set"; break; + case nList: t = "list"; break; + case nFunction: t = "lambda"; break; + case nExternal: t = args[0]->external->typeOf(); break; - case tFloat: t = "float"; break; - default: abort(); + case nFloat: t = "float"; break; + case nThunk: abort(); } mkString(v, state.symbols.create(t)); } @@ -393,7 +402,7 @@ static RegisterPrimOp primop_typeOf({ static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tNull); + mkBool(v, args[0]->type() == nNull); } static RegisterPrimOp primop_isNull({ @@ -413,18 +422,7 @@ static RegisterPrimOp primop_isNull({ static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - bool res; - switch (args[0]->type) { - case tLambda: - case tPrimOp: - case tPrimOpApp: - res = true; - break; - default: - res = false; - break; - } - mkBool(v, res); + mkBool(v, args[0]->type() == nFunction); } static RegisterPrimOp primop_isFunction({ @@ -440,7 +438,7 @@ static RegisterPrimOp primop_isFunction({ static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tInt); + mkBool(v, args[0]->type() == nInt); } static RegisterPrimOp primop_isInt({ @@ -456,7 +454,7 @@ static RegisterPrimOp primop_isInt({ static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tFloat); + mkBool(v, args[0]->type() == nFloat); } static RegisterPrimOp primop_isFloat({ @@ -472,7 +470,7 @@ static RegisterPrimOp primop_isFloat({ static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tString); + mkBool(v, args[0]->type() == nString); } static RegisterPrimOp primop_isString({ @@ -488,7 +486,7 @@ static RegisterPrimOp primop_isString({ static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tBool); + mkBool(v, args[0]->type() == nBool); } static RegisterPrimOp primop_isBool({ @@ -504,7 +502,7 @@ static RegisterPrimOp primop_isBool({ static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tPath); + mkBool(v, args[0]->type() == nPath); } static RegisterPrimOp primop_isPath({ @@ -520,20 +518,20 @@ struct CompareValues { bool operator () (const Value * v1, const Value * v2) const { - if (v1->type == tFloat && v2->type == tInt) + if (v1->type() == nFloat && v2->type() == nInt) return v1->fpoint < v2->integer; - if (v1->type == tInt && v2->type == tFloat) + if (v1->type() == nInt && v2->type() == nFloat) return v1->integer < v2->fpoint; - if (v1->type != v2->type) + if (v1->type() != v2->type()) throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); - switch (v1->type) { - case tInt: + switch (v1->type()) { + case nInt: return v1->integer < v2->integer; - case tFloat: + case nFloat: return v1->fpoint < v2->fpoint; - case tString: + case nString: return strcmp(v1->string.s, v2->string.s) < 0; - case tPath: + case nPath: return strcmp(v1->path, v2->path) < 0; default: throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); @@ -558,7 +556,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar args[0]->attrs->find(state.symbols.create("startSet")); if (startSet == args[0]->attrs->end()) throw EvalError({ - .hint = hintfmt("attribute 'startSet' required"), + .msg = hintfmt("attribute 'startSet' required"), .errPos = pos }); state.forceList(*startSet->value, pos); @@ -572,7 +570,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar args[0]->attrs->find(state.symbols.create("operator")); if (op == args[0]->attrs->end()) throw EvalError({ - .hint = hintfmt("attribute 'operator' required"), + .msg = hintfmt("attribute 'operator' required"), .errPos = pos }); state.forceValue(*op->value, pos); @@ -594,7 +592,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar e->attrs->find(state.symbols.create("key")); if (key == e->attrs->end()) throw EvalError({ - .hint = hintfmt("attribute 'key' required"), + .msg = hintfmt("attribute 'key' required"), .errPos = pos }); state.forceValue(*key->value, pos); @@ -700,10 +698,14 @@ static RegisterPrimOp primop_tryEval({ Try to shallowly evaluate *e*. Return a set containing the attributes `success` (`true` if *e* evaluated successfully, `false` if an error was thrown) and `value`, equalling *e* if - successful and `false` otherwise. Note that this doesn't evaluate - *e* deeply, so ` let e = { x = throw ""; }; in (builtins.tryEval - e).success ` will be `true`. Using ` builtins.deepSeq ` one can - get the expected result: `let e = { x = throw ""; }; in + successful and `false` otherwise. `tryEval` will only prevent + errors created by `throw` or `assert` from being thrown. + Errors `tryEval` will not catch are for example those created + by `abort` and type errors generated by builtins. Also note that + this doesn't evaluate *e* deeply, so `let e = { x = throw ""; }; + in (builtins.tryEval e).success` will be `true`. Using + `builtins.deepSeq` one can get the expected result: + `let e = { x = throw ""; }; in (builtins.tryEval (builtins.deepSeq e e)).success` will be `false`. )", @@ -777,7 +779,7 @@ static RegisterPrimOp primop_deepSeq({ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - if (args[0]->type == tString) + if (args[0]->type() == nString) printError("trace: %1%", args[0]->string.s); else printError("trace: %1%", *args[0]); @@ -817,7 +819,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * Bindings::iterator attr = args[0]->attrs->find(state.sName); if (attr == args[0]->attrs->end()) throw EvalError({ - .hint = hintfmt("required attribute 'name' missing"), + .msg = hintfmt("required attribute 'name' missing"), .errPos = pos }); string drvName; @@ -866,7 +868,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat; else throw EvalError({ - .hint = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), + .msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), .errPos = posDrvName }); }; @@ -876,7 +878,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * for (auto & j : ss) { if (outputs.find(j) != outputs.end()) throw EvalError({ - .hint = hintfmt("duplicate derivation output '%1%'", j), + .msg = hintfmt("duplicate derivation output '%1%'", j), .errPos = posDrvName }); /* !!! Check whether j is a valid attribute @@ -886,14 +888,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * the resulting set. */ if (j == "drv") throw EvalError({ - .hint = hintfmt("invalid derivation output name 'drv'" ), + .msg = hintfmt("invalid derivation output name 'drv'" ), .errPos = posDrvName }); outputs.insert(j); } if (outputs.empty()) throw EvalError({ - .hint = hintfmt("derivation cannot have an empty set of outputs"), + .msg = hintfmt("derivation cannot have an empty set of outputs"), .errPos = posDrvName }); }; @@ -902,7 +904,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (ignoreNulls) { state.forceValue(*i->value, pos); - if (i->value->type == tNull) continue; + if (i->value->type() == nNull) continue; } if (i->name == state.sContentAddressed) { @@ -1014,20 +1016,20 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* Do we have all required attributes? */ if (drv.builder == "") throw EvalError({ - .hint = hintfmt("required attribute 'builder' missing"), + .msg = hintfmt("required attribute 'builder' missing"), .errPos = posDrvName }); if (drv.platform == "") throw EvalError({ - .hint = hintfmt("required attribute 'system' missing"), + .msg = hintfmt("required attribute 'system' missing"), .errPos = posDrvName }); /* Check whether the derivation name is valid. */ if (isDerivation(drvName)) throw EvalError({ - .hint = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), + .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), .errPos = posDrvName }); @@ -1038,7 +1040,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * already content addressed. */ if (outputs.size() != 1 || *(outputs.begin()) != "out") throw Error({ - .hint = hintfmt("multiple outputs are not supported in fixed-output derivations"), + .msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), .errPos = posDrvName }); @@ -1089,18 +1091,35 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * // Regular, non-CA derivation should always return a single hash and not // hash per output. - Hash h = std::get<0>(hashDerivationModulo(*state.store, Derivation(drv), true)); + auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true); + std::visit(overloaded { + [&](Hash h) { + for (auto & i : outputs) { + auto outPath = state.store->makeOutputPath(i, h, drvName); + drv.env[i] = state.store->printStorePath(outPath); + drv.outputs.insert_or_assign(i, + DerivationOutput { + .output = DerivationOutputInputAddressed { + .path = std::move(outPath), + }, + }); + } + }, + [&](CaOutputHashes) { + // Shouldn't happen as the toplevel derivation is not CA. + assert(false); + }, + [&](DeferredHash _) { + for (auto & i : outputs) { + drv.outputs.insert_or_assign(i, + DerivationOutput { + .output = DerivationOutputDeferred{}, + }); + } + }, + }, + hashModulo); - for (auto & i : outputs) { - auto outPath = state.store->makeOutputPath(i, h, drvName); - drv.env[i] = state.store->printStorePath(outPath); - drv.outputs.insert_or_assign(i, - DerivationOutput { - .output = DerivationOutputInputAddressed { - .path = std::move(outPath), - }, - }); - } } /* Write the resulting term into the Nix store directory. */ @@ -1115,9 +1134,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * However, we don't bother doing this for floating CA derivations because their "hash modulo" is indeterminate until built. */ - if (drv.type() != DerivationType::CAFloating) - drvHashes.insert_or_assign(drvPath, - hashDerivationModulo(*state.store, Derivation(drv), false)); + if (drv.type() != DerivationType::CAFloating) { + auto h = hashDerivationModulo(*state.store, Derivation(drv), false); + drvHashes.lock()->insert_or_assign(drvPath, h); + } state.mkAttrs(v, 1 + drv.outputs.size()); mkString(*state.allocAttr(v, state.sDrvPath), drvPathS, {"=" + drvPathS}); @@ -1200,7 +1220,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V if (!state.store->isStorePath(path)) path = canonPath(path, true); if (!state.store->isInStore(path)) throw EvalError({ - .hint = hintfmt("path '%1%' is not in the Nix store", path), + .msg = hintfmt("path '%1%' is not in the Nix store", path), .errPos = pos }); auto path2 = state.store->toStorePath(path).first; @@ -1236,7 +1256,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt( + .msg = hintfmt( "cannot check the existence of '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos @@ -1290,7 +1310,7 @@ static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value { PathSet context; Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false)); - if (args[0]->type == tPath) mkPath(v, dir.c_str()); else mkString(v, dir, context); + if (args[0]->type() == nPath) mkPath(v, dir.c_str()); else mkString(v, dir, context); } static RegisterPrimOp primop_dirOf({ @@ -1313,7 +1333,7 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path), + .msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos }); } @@ -1352,7 +1372,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va i = v2.attrs->find(state.symbols.create("path")); if (i == v2.attrs->end()) throw EvalError({ - .hint = hintfmt("attribute 'path' missing"), + .msg = hintfmt("attribute 'path' missing"), .errPos = pos }); @@ -1363,7 +1383,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va state.realiseContext(context); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), + .msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos }); } @@ -1389,7 +1409,7 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va std::optional<HashType> ht = parseHashType(type); if (!ht) throw Error({ - .hint = hintfmt("unknown hash type '%1%'", type), + .msg = hintfmt("unknown hash type '%1%'", type), .errPos = pos }); @@ -1419,7 +1439,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val state.realiseContext(ctx); } catch (InvalidPathError & e) { throw EvalError({ - .hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path), + .msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path), .errPos = pos }); } @@ -1431,7 +1451,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name)); if (ent.type == DT_UNKNOWN) ent.type = getFileType(path + "/" + ent.name); - mkStringNoCopy(*ent_val, + ent_val->mkString( ent.type == DT_REG ? "regular" : ent.type == DT_DIR ? "directory" : ent.type == DT_LNK ? "symlink" : @@ -1603,7 +1623,12 @@ static RegisterPrimOp primop_toJSON({ static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) { string s = state.forceStringNoCtx(*args[0], pos); - parseJSON(state, s, v); + try { + parseJSON(state, s, v); + } catch (JSONParseError &e) { + e.addTrace(pos, "while decoding a JSON string"); + throw e; + } } static RegisterPrimOp primop_fromJSON({ @@ -1634,7 +1659,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu for (auto path : context) { if (path.at(0) != '/') throw EvalError( { - .hint = hintfmt( + .msg = hintfmt( "in 'toFile': the file named '%1%' must not contain a reference " "to a derivation but contains (%2%)", name, path), @@ -1785,14 +1810,14 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args Path path = state.coerceToPath(pos, *args[1], context); if (!context.empty()) throw EvalError({ - .hint = hintfmt("string '%1%' cannot refer to other paths", path), + .msg = hintfmt("string '%1%' cannot refer to other paths", path), .errPos = pos }); state.forceValue(*args[0], pos); - if (args[0]->type != tLambda) + if (args[0]->type() != nFunction) throw TypeError({ - .hint = hintfmt( + .msg = hintfmt( "first argument in call to 'filterSource' is not a function but %1%", showType(*args[0])), .errPos = pos @@ -1859,7 +1884,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value path = state.coerceToPath(*attr.pos, *attr.value, context); if (!context.empty()) throw EvalError({ - .hint = hintfmt("string '%1%' cannot refer to other paths", path), + .msg = hintfmt("string '%1%' cannot refer to other paths", path), .errPos = *attr.pos }); } else if (attr.name == state.sName) @@ -1873,13 +1898,13 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); else throw EvalError({ - .hint = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), + .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), .errPos = *attr.pos }); } if (path.empty()) throw EvalError({ - .hint = hintfmt("'path' required"), + .msg = hintfmt("'path' required"), .errPos = pos }); if (name.empty()) @@ -1994,7 +2019,7 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); if (i == args[1]->attrs->end()) throw EvalError({ - .hint = hintfmt("attribute '%1%' missing", attr), + .msg = hintfmt("attribute '%1%' missing", attr), .errPos = pos }); // !!! add to stack trace? @@ -2056,7 +2081,7 @@ static RegisterPrimOp primop_hasAttr({ static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->type == tAttrs); + mkBool(v, args[0]->type() == nAttrs); } static RegisterPrimOp primop_isAttrs({ @@ -2126,7 +2151,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Bindings::iterator j = v2.attrs->find(state.sName); if (j == v2.attrs->end()) throw TypeError({ - .hint = hintfmt("'name' attribute missing in a call to 'listToAttrs'"), + .msg = hintfmt("'name' attribute missing in a call to 'listToAttrs'"), .errPos = pos }); string name = state.forceStringNoCtx(*j->value, pos); @@ -2136,7 +2161,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue)); if (j2 == v2.attrs->end()) throw TypeError({ - .hint = hintfmt("'value' attribute missing in a call to 'listToAttrs'"), + .msg = hintfmt("'value' attribute missing in a call to 'listToAttrs'"), .errPos = pos }); v.attrs->push_back(Attr(sym, j2->value, j2->pos)); @@ -2236,13 +2261,13 @@ static RegisterPrimOp primop_catAttrs({ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - if (args[0]->type == tPrimOpApp || args[0]->type == tPrimOp) { + if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { state.mkAttrs(v, 0); return; } - if (args[0]->type != tLambda) + if (!args[0]->isLambda()) throw TypeError({ - .hint = hintfmt("'functionArgs' requires a function"), + .msg = hintfmt("'functionArgs' requires a function"), .errPos = pos }); @@ -2319,7 +2344,7 @@ static RegisterPrimOp primop_mapAttrs({ static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - mkBool(v, args[0]->isList()); + mkBool(v, args[0]->type() == nList); } static RegisterPrimOp primop_isList({ @@ -2336,7 +2361,7 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu state.forceList(list, pos); if (n < 0 || (unsigned int) n >= list.listSize()) throw Error({ - .hint = hintfmt("list index %1% is out of bounds", n), + .msg = hintfmt("list index %1% is out of bounds", n), .errPos = pos }); state.forceValue(*list.listElems()[n], pos); @@ -2384,7 +2409,7 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value state.forceList(*args[0], pos); if (args[0]->listSize() == 0) throw Error({ - .hint = hintfmt("'tail' called on an empty list"), + .msg = hintfmt("'tail' called on an empty list"), .errPos = pos }); @@ -2623,7 +2648,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val if (len < 0) throw EvalError({ - .hint = hintfmt("cannot create list of size %1%", len), + .msg = hintfmt("cannot create list of size %1%", len), .errPos = pos }); @@ -2671,7 +2696,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value auto comparator = [&](Value * a, Value * b) { /* Optimization: if the comparator is lessThan, bypass callFunction. */ - if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan) + if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) return CompareValues()(a, b); Value vTmp1, vTmp2; @@ -2813,7 +2838,7 @@ static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) + if (args[0]->type() == nFloat || args[1]->type() == nFloat) mkFloat(v, state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos)); else mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos)); @@ -2832,7 +2857,7 @@ static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) + if (args[0]->type() == nFloat || args[1]->type() == nFloat) mkFloat(v, state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos)); else mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos)); @@ -2851,7 +2876,7 @@ static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) + if (args[0]->type() == nFloat || args[1]->type() == nFloat) mkFloat(v, state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos)); else mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos)); @@ -2874,11 +2899,11 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & NixFloat f2 = state.forceFloat(*args[1], pos); if (f2 == 0) throw EvalError({ - .hint = hintfmt("division by zero"), + .msg = hintfmt("division by zero"), .errPos = pos }); - if (args[0]->type == tFloat || args[1]->type == tFloat) { + if (args[0]->type() == nFloat || args[1]->type() == nFloat) { mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); } else { NixInt i1 = state.forceInt(*args[0], pos); @@ -2886,7 +2911,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & /* Avoid division overflow as it might raise SIGFPE. */ if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) throw EvalError({ - .hint = hintfmt("overflow in integer division"), + .msg = hintfmt("overflow in integer division"), .errPos = pos }); @@ -3017,7 +3042,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V if (start < 0) throw EvalError({ - .hint = hintfmt("negative start position in 'substring'"), + .msg = hintfmt("negative start position in 'substring'"), .errPos = pos }); @@ -3068,7 +3093,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, std::optional<HashType> ht = parseHashType(type); if (!ht) throw Error({ - .hint = hintfmt("unknown hash type '%1%'", type), + .msg = hintfmt("unknown hash type '%1%'", type), .errPos = pos }); @@ -3132,12 +3157,12 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) if (e.code() == std::regex_constants::error_space) { // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ throw EvalError({ - .hint = hintfmt("memory limit exceeded by regular expression '%s'", re), + .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .errPos = pos }); } else { throw EvalError({ - .hint = hintfmt("invalid regular expression '%s'", re), + .msg = hintfmt("invalid regular expression '%s'", re), .errPos = pos }); } @@ -3240,12 +3265,12 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value if (e.code() == std::regex_constants::error_space) { // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ throw EvalError({ - .hint = hintfmt("memory limit exceeded by regular expression '%s'", re), + .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .errPos = pos }); } else { throw EvalError({ - .hint = hintfmt("invalid regular expression '%s'", re), + .msg = hintfmt("invalid regular expression '%s'", re), .errPos = pos }); } @@ -3325,7 +3350,7 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar state.forceList(*args[1], pos); if (args[0]->listSize() != args[1]->listSize()) throw EvalError({ - .hint = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), + .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), .errPos = pos }); diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index b570fca31..31cf812b4 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -147,7 +147,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg for (auto & i : *args[1]->attrs) { if (!state.store->isStorePath(i.name)) throw EvalError({ - .hint = hintfmt("Context key '%s' is not a store path", i.name), + .msg = hintfmt("Context key '%s' is not a store path", i.name), .errPos = *i.pos }); if (!settings.readOnlyMode) @@ -164,7 +164,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg if (state.forceBool(*iter->value, *iter->pos)) { if (!isDerivation(i.name)) { throw EvalError({ - .hint = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), .errPos = *i.pos }); } @@ -177,7 +177,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg state.forceList(*iter->value, *iter->pos); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ - .hint = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), .errPos = *i.pos }); } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index a77035c16..4830ebec3 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -17,7 +17,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar state.forceValue(*args[0]); - if (args[0]->type == tAttrs) { + if (args[0]->type() == nAttrs) { state.forceAttrs(*args[0], pos); @@ -38,14 +38,14 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar name = state.forceStringNoCtx(*attr.value, *attr.pos); else throw EvalError({ - .hint = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), + .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), .errPos = *attr.pos }); } if (url.empty()) throw EvalError({ - .hint = hintfmt("'url' argument required"), + .msg = hintfmt("'url' argument required"), .errPos = pos }); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 7cd4d0fbf..27d8ddf35 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -39,11 +39,12 @@ void emitTreeAttrs( // Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev auto emptyHash = Hash(htSHA1); mkString(*state.allocAttr(v, state.symbols.create("rev")), emptyHash.gitRev()); - mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitRev()); + mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitShortRev()); } if (input.getType() == "git") - mkBool(*state.allocAttr(v, state.symbols.create("submodules")), maybeGetBoolAttr(input.attrs, "submodules").value_or(false)); + mkBool(*state.allocAttr(v, state.symbols.create("submodules")), + fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)); if (auto revCount = input.getRevCount()) mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount); @@ -84,26 +85,26 @@ static void fetchTree( state.forceValue(*args[0]); - if (args[0]->type == tAttrs) { + if (args[0]->type() == nAttrs) { state.forceAttrs(*args[0], pos); fetchers::Attrs attrs; for (auto & attr : *args[0]->attrs) { state.forceValue(*attr.value); - if (attr.value->type == tPath || attr.value->type == tString) + if (attr.value->type() == nPath || attr.value->type() == nString) addURI( state, attrs, attr.name, state.coerceToString(*attr.pos, *attr.value, context, false, false) ); - else if (attr.value->type == tString) + else if (attr.value->type() == nString) addURI(state, attrs, attr.name, attr.value->string.s); - else if (attr.value->type == tBool) - attrs.emplace(attr.name, fetchers::Explicit<bool>{attr.value->boolean}); - else if (attr.value->type == tInt) - attrs.emplace(attr.name, attr.value->integer); + else if (attr.value->type() == nBool) + attrs.emplace(attr.name, Explicit<bool>{attr.value->boolean}); + else if (attr.value->type() == nInt) + attrs.emplace(attr.name, uint64_t(attr.value->integer)); else throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", attr.name, showType(*attr.value)); @@ -114,7 +115,7 @@ static void fetchTree( if (!attrs.count("type")) throw Error({ - .hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), + .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), .errPos = pos }); @@ -152,6 +153,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V fetchTree(state, pos, args, v, std::nullopt); } +// FIXME: document static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree); static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, @@ -162,7 +164,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.forceValue(*args[0]); - if (args[0]->type == tAttrs) { + if (args[0]->type() == nAttrs) { state.forceAttrs(*args[0], pos); @@ -176,14 +178,14 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, name = state.forceStringNoCtx(*attr.value, *attr.pos); else throw EvalError({ - .hint = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), + .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), .errPos = *attr.pos }); } if (!url) throw EvalError({ - .hint = hintfmt("'url' argument required"), + .msg = hintfmt("'url' argument required"), .errPos = pos }); } else @@ -211,7 +213,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, ? state.store->queryPathInfo(storePath)->narHash : hashFile(htSHA256, path); if (hash != *expectedHash) - throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n wanted: %s\n got: %s", + throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", *url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)); } @@ -323,6 +325,11 @@ static RegisterPrimOp primop_fetchGit({ A Boolean parameter that specifies whether submodules should be checked out. Defaults to `false`. + - allRefs + Whether to fetch all refs of the repository. With this argument being + true, it's possible to load a `rev` from *any* `ref` (by default only + `rev`s from the specified `ref` are supported). + Here are some examples of how to use `fetchGit`. - To fetch a private repository over SSH: diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 77bff44ae..4c6682dfd 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -82,7 +82,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va visit(v, parser(tomlStream).parse()); } catch (std::runtime_error & e) { throw EvalError({ - .hint = hintfmt("while parsing a TOML string: %s", e.what()), + .msg = hintfmt("while parsing a TOML string: %s", e.what()), .errPos = pos }); } diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 6ec8315ba..bfea24d40 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -16,30 +16,30 @@ void printValueAsJSON(EvalState & state, bool strict, if (strict) state.forceValue(v); - switch (v.type) { + switch (v.type()) { - case tInt: + case nInt: out.write(v.integer); break; - case tBool: + case nBool: out.write(v.boolean); break; - case tString: + case nString: copyContext(v, context); out.write(v.string.s); break; - case tPath: + case nPath: out.write(state.copyPathToStore(context, v.path)); break; - case tNull: + case nNull: out.write(nullptr); break; - case tAttrs: { + case nAttrs: { auto maybeString = state.tryAttrsToString(noPos, v, context, false, false); if (maybeString) { out.write(*maybeString); @@ -61,7 +61,7 @@ void printValueAsJSON(EvalState & state, bool strict, break; } - case tList1: case tList2: case tListN: { + case nList: { auto list(out.list()); for (unsigned int n = 0; n < v.listSize(); ++n) { auto placeholder(list.placeholder()); @@ -70,15 +70,18 @@ void printValueAsJSON(EvalState & state, bool strict, break; } - case tExternal: + case nExternal: v.external->printValueAsJSON(state, strict, out, context); break; - case tFloat: + case nFloat: out.write(v.fpoint); break; - default: + case nThunk: + throw TypeError("cannot convert %1% to JSON", showType(v)); + + case nFunction: throw TypeError("cannot convert %1% to JSON", showType(v)); } } diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index 1f0b1541d..7464455d8 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -58,31 +58,31 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, if (strict) state.forceValue(v); - switch (v.type) { + switch (v.type()) { - case tInt: + case nInt: doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % v.integer).str())); break; - case tBool: + case nBool: doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false")); break; - case tString: + case nString: /* !!! show the context? */ copyContext(v, context); doc.writeEmptyElement("string", singletonAttrs("value", v.string.s)); break; - case tPath: + case nPath: doc.writeEmptyElement("path", singletonAttrs("value", v.path)); break; - case tNull: + case nNull: doc.writeEmptyElement("null"); break; - case tAttrs: + case nAttrs: if (state.isDerivation(v)) { XMLAttrs xmlAttrs; @@ -92,14 +92,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, a = v.attrs->find(state.sDrvPath); if (a != v.attrs->end()) { if (strict) state.forceValue(*a->value); - if (a->value->type == tString) + if (a->value->type() == nString) xmlAttrs["drvPath"] = drvPath = a->value->string.s; } a = v.attrs->find(state.sOutPath); if (a != v.attrs->end()) { if (strict) state.forceValue(*a->value); - if (a->value->type == tString) + if (a->value->type() == nString) xmlAttrs["outPath"] = a->value->string.s; } @@ -118,14 +118,19 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; - case tList1: case tList2: case tListN: { + case nList: { XMLOpenElement _(doc, "list"); for (unsigned int n = 0; n < v.listSize(); ++n) printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen); break; } - case tLambda: { + case nFunction: { + if (!v.isLambda()) { + // FIXME: Serialize primops and primopapps + doc.writeEmptyElement("unevaluated"); + break; + } XMLAttrs xmlAttrs; if (location) posToXML(xmlAttrs, v.lambda.fun->pos); XMLOpenElement _(doc, "function", xmlAttrs); @@ -143,15 +148,15 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; } - case tExternal: + case nExternal: v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen); break; - case tFloat: + case nFloat: doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str())); break; - default: + case nThunk: doc.writeEmptyElement("unevaluated"); } } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index fe11bb2ed..b317c1898 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -27,8 +27,24 @@ typedef enum { tPrimOpApp, tExternal, tFloat -} ValueType; +} InternalType; +// This type abstracts over all actual value types in the language, +// grouping together implementation details like tList*, different function +// types, and types in non-normal form (so thunks and co.) +typedef enum { + nThunk, + nInt, + nFloat, + nBool, + nString, + nPath, + nNull, + nAttrs, + nList, + nFunction, + nExternal +} ValueType; class Bindings; struct Env; @@ -90,7 +106,28 @@ std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); struct Value { - ValueType type; +private: + InternalType internalType; + +friend std::string showType(const Value & v); +friend void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v); + +public: + + // Functions needed to distinguish the type + // These should be removed eventually, by putting the functionality that's + // needed by callers into methods of this type + + // type() == nThunk + inline bool isThunk() const { return internalType == tThunk; }; + inline bool isApp() const { return internalType == tApp; }; + inline bool isBlackhole() const { return internalType == tBlackhole; }; + + // type() == nFunction + inline bool isLambda() const { return internalType == tLambda; }; + inline bool isPrimOp() const { return internalType == tPrimOp; }; + inline bool isPrimOpApp() const { return internalType == tPrimOpApp; }; + union { NixInt integer; @@ -147,24 +184,161 @@ struct Value NixFloat fpoint; }; + // Returns the normal type of a Value. This only returns nThunk if the + // Value hasn't been forceValue'd + inline ValueType type() const + { + switch (internalType) { + case tInt: return nInt; + case tBool: return nBool; + case tString: return nString; + case tPath: return nPath; + case tNull: return nNull; + case tAttrs: return nAttrs; + case tList1: case tList2: case tListN: return nList; + case tLambda: case tPrimOp: case tPrimOpApp: return nFunction; + case tExternal: return nExternal; + case tFloat: return nFloat; + case tThunk: case tApp: case tBlackhole: return nThunk; + } + abort(); + } + + /* After overwriting an app node, be sure to clear pointers in the + Value to ensure that the target isn't kept alive unnecessarily. */ + inline void clearValue() + { + app.left = app.right = 0; + } + + inline void mkInt(NixInt n) + { + clearValue(); + internalType = tInt; + integer = n; + } + + inline void mkBool(bool b) + { + clearValue(); + internalType = tBool; + boolean = b; + } + + inline void mkString(const char * s, const char * * context = 0) + { + internalType = tString; + string.s = s; + string.context = context; + } + + inline void mkPath(const char * s) + { + clearValue(); + internalType = tPath; + path = s; + } + + inline void mkNull() + { + clearValue(); + internalType = tNull; + } + + inline void mkAttrs(Bindings * a) + { + clearValue(); + internalType = tAttrs; + attrs = a; + } + + inline void mkList(size_t size) + { + clearValue(); + if (size == 1) + internalType = tList1; + else if (size == 2) + internalType = tList2; + else { + internalType = tListN; + bigList.size = size; + } + } + + inline void mkThunk(Env * e, Expr * ex) + { + internalType = tThunk; + thunk.env = e; + thunk.expr = ex; + } + + inline void mkApp(Value * l, Value * r) + { + internalType = tApp; + app.left = l; + app.right = r; + } + + inline void mkLambda(Env * e, ExprLambda * f) + { + internalType = tLambda; + lambda.env = e; + lambda.fun = f; + } + + inline void mkBlackhole() + { + internalType = tBlackhole; + // Value will be overridden anyways + } + + inline void mkPrimOp(PrimOp * p) + { + clearValue(); + internalType = tPrimOp; + primOp = p; + } + + + inline void mkPrimOpApp(Value * l, Value * r) + { + internalType = tPrimOpApp; + app.left = l; + app.right = r; + } + + inline void mkExternal(ExternalValueBase * e) + { + clearValue(); + internalType = tExternal; + external = e; + } + + inline void mkFloat(NixFloat n) + { + clearValue(); + internalType = tFloat; + fpoint = n; + } + bool isList() const { - return type == tList1 || type == tList2 || type == tListN; + return internalType == tList1 || internalType == tList2 || internalType == tListN; } Value * * listElems() { - return type == tList1 || type == tList2 ? smallList : bigList.elems; + return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems; } const Value * const * listElems() const { - return type == tList1 || type == tList2 ? smallList : bigList.elems; + return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems; } size_t listSize() const { - return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size; + return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size; } /* Check whether forcing this value requires a trivial amount of @@ -176,86 +350,42 @@ struct Value }; -/* After overwriting an app node, be sure to clear pointers in the - Value to ensure that the target isn't kept alive unnecessarily. */ -static inline void clearValue(Value & v) -{ - v.app.left = v.app.right = 0; -} - +// TODO: Remove these static functions, replace call sites with v.mk* instead static inline void mkInt(Value & v, NixInt n) { - clearValue(v); - v.type = tInt; - v.integer = n; + v.mkInt(n); } - static inline void mkFloat(Value & v, NixFloat n) { - clearValue(v); - v.type = tFloat; - v.fpoint = n; + v.mkFloat(n); } - static inline void mkBool(Value & v, bool b) { - clearValue(v); - v.type = tBool; - v.boolean = b; + v.mkBool(b); } - static inline void mkNull(Value & v) { - clearValue(v); - v.type = tNull; + v.mkNull(); } - static inline void mkApp(Value & v, Value & left, Value & right) { - v.type = tApp; - v.app.left = &left; - v.app.right = &right; -} - - -static inline void mkPrimOpApp(Value & v, Value & left, Value & right) -{ - v.type = tPrimOpApp; - v.app.left = &left; - v.app.right = &right; -} - - -static inline void mkStringNoCopy(Value & v, const char * s) -{ - v.type = tString; - v.string.s = s; - v.string.context = 0; + v.mkApp(&left, &right); } - static inline void mkString(Value & v, const Symbol & s) { - mkStringNoCopy(v, ((const string &) s).c_str()); + v.mkString(((const string &) s).c_str()); } void mkString(Value & v, const char * s); -static inline void mkPathNoCopy(Value & v, const char * s) -{ - clearValue(v); - v.type = tPath; - v.path = s; -} - - void mkPath(Value & v, const char * s); |