diff options
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/eval-cache.cc | 6 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 60 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 14 | ||||
-rw-r--r-- | src/libexpr/flake/config.cc | 2 | ||||
-rw-r--r-- | src/libexpr/flake/flake.cc | 53 | ||||
-rw-r--r-- | src/libexpr/flake/flake.hh | 6 | ||||
-rw-r--r-- | src/libexpr/flake/flakeref.hh | 2 | ||||
-rw-r--r-- | src/libexpr/flake/lockfile.cc | 48 | ||||
-rw-r--r-- | src/libexpr/flake/lockfile.hh | 9 | ||||
-rw-r--r-- | src/libexpr/get-drvs.cc | 2 | ||||
-rw-r--r-- | src/libexpr/nixexpr.cc | 73 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 29 | ||||
-rw-r--r-- | src/libexpr/parser.y | 65 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 15 | ||||
-rw-r--r-- | src/libexpr/primops/fetchTree.cc | 2 | ||||
-rw-r--r-- | src/libexpr/tests/libexprtests.hh | 2 | ||||
-rw-r--r-- | src/libexpr/tests/primops.cc | 15 | ||||
-rw-r--r-- | src/libexpr/value-to-xml.cc | 3 |
18 files changed, 246 insertions, 160 deletions
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index b259eec63..3e2a8665e 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -645,17 +645,17 @@ NixInt AttrCursor::getInt() cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) { if (auto i = std::get_if<int_t>(&cachedValue->second)) { - debug("using cached Integer attribute '%s'", getAttrPathStr()); + debug("using cached integer attribute '%s'", getAttrPathStr()); return i->x; } else - throw TypeError("'%s' is not an Integer", getAttrPathStr()); + throw TypeError("'%s' is not an integer", getAttrPathStr()); } } auto & v = forceValue(); if (v.type() != nInt) - throw TypeError("'%s' is not an Integer", getAttrPathStr()); + throw TypeError("'%s' is not an integer", getAttrPathStr()); return v.integer; } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 76a10b9f8..084ccbee2 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -45,7 +45,7 @@ static char * allocString(size_t size) #if HAVE_BOEHMGC t = (char *) GC_MALLOC_ATOMIC(size); #else - t = malloc(size); + t = (char *) malloc(size); #endif if (!t) throw std::bad_alloc(); return t; @@ -402,7 +402,8 @@ static Strings parseNixPath(const std::string & s) } if (*p == ':') { - if (isUri(std::string(start2, s.end()))) { + auto prefix = std::string(start2, s.end()); + if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) { ++p; while (p != s.end() && *p != ':') ++p; } @@ -470,9 +471,6 @@ EvalState::EvalState( #if HAVE_BOEHMGC , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) , env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) -#else - , valueAllocCache(std::make_shared<void *>(nullptr)) - , env1AllocCache(std::make_shared<void *>(nullptr)) #endif , baseEnv(allocEnv(128)) , staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)} @@ -822,7 +820,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & ? std::make_unique<DebugTraceStacker>( *this, DebugTrace { - .pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()], + .pos = error->info().errPos ? error->info().errPos : static_cast<std::shared_ptr<AbstractPos>>(positions[expr.getPos()]), .expr = expr, .env = env, .hint = error->info().msg, @@ -1011,7 +1009,7 @@ void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, cons void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { - e.addTrace(std::nullopt, s, s2); + e.addTrace(nullptr, s, s2); } void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const @@ -1023,13 +1021,13 @@ static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker( EvalState & state, Expr & expr, Env & env, - std::optional<ErrPos> pos, + std::shared_ptr<AbstractPos> && pos, const char * s, const std::string & s2) { return std::make_unique<DebugTraceStacker>(state, DebugTrace { - .pos = pos, + .pos = std::move(pos), .expr = expr, .env = env, .hint = hintfmt(s, s2), @@ -1135,9 +1133,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr) void EvalState::mkPos(Value & v, PosIdx p) { auto pos = positions[p]; - if (!pos.file.empty()) { + if (auto path = std::get_if<Path>(&pos.origin)) { auto attrs = buildBindings(3); - attrs.alloc(sFile).mkString(pos.file); + attrs.alloc(sFile).mkString(*path); attrs.alloc(sLine).mkInt(pos.line); attrs.alloc(sColumn).mkInt(pos.column); v.mkAttrs(attrs); @@ -1245,7 +1243,7 @@ void EvalState::cacheFile( *this, *e, this->baseEnv, - e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt, + e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr, "while evaluating the file '%1%':", resolvedPath) : nullptr; @@ -1516,10 +1514,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) ); } catch (Error & e) { - auto pos2r = state.positions[pos2]; - if (pos2 && pos2r.file != state.derivationNixPath) - state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", - showAttrPath(state, env, attrPath)); + if (pos2) { + auto pos2r = state.positions[pos2]; + auto origin = std::get_if<Path>(&pos2r.origin); + if (!(origin && *origin == state.derivationNixPath)) + state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", + showAttrPath(state, env, attrPath)); + } throw; } @@ -1659,7 +1660,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & (lambda.name ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda")); - addErrorTrace(e, pos, "from call site%s", ""); + addErrorTrace(e, pos, "while evaluating call site%s", ""); } throw; } @@ -1806,7 +1807,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name], +https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name], *fun.lambda.env, *fun.lambda.fun); } } @@ -2496,7 +2497,8 @@ void EvalState::printStats() else obj["name"] = nullptr; if (auto pos = positions[fun->pos]) { - obj["file"] = (std::string_view) pos.file; + if (auto path = std::get_if<Path>(&pos.origin)) + obj["file"] = *path; obj["line"] = pos.line; obj["column"] = pos.column; } @@ -2510,7 +2512,8 @@ void EvalState::printStats() for (auto & i : attrSelects) { json obj = json::object(); if (auto pos = positions[i.first]) { - obj["file"] = (const std::string &) pos.file; + if (auto path = std::get_if<Path>(&pos.origin)) + obj["file"] = *path; obj["line"] = pos.line; obj["column"] = pos.column; } @@ -2583,6 +2586,23 @@ Strings EvalSettings::getDefaultNixPath() return res; } +bool EvalSettings::isPseudoUrl(std::string_view s) +{ + if (s.compare(0, 8, "channel:") == 0) return true; + size_t pos = s.find("://"); + if (pos == std::string::npos) return false; + std::string scheme(s, 0, pos); + return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git" || scheme == "s3" || scheme == "ssh"; +} + +std::string EvalSettings::resolvePseudoUrl(std::string_view url) +{ + if (hasPrefix(url, "channel:")) + return "https://nixos.org/channels/" + std::string(url.substr(8)) + "/nixexprs.tar.xz"; + else + return std::string(url); +} + EvalSettings evalSettings; static GlobalConfig::Register rEvalSettings(&evalSettings); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index f07f15d43..21666339b 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -78,7 +78,7 @@ struct RegexCache; std::shared_ptr<RegexCache> makeRegexCache(); struct DebugTrace { - std::optional<ErrPos> pos; + std::shared_ptr<AbstractPos> pos; const Expr & expr; const Env & env; hintformat hint; @@ -457,8 +457,12 @@ private: friend struct ExprAttrs; friend struct ExprLet; - Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path, - const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv); + Expr * parse( + char * text, + size_t length, + Pos::Origin origin, + Path basePath, + std::shared_ptr<StaticEnv> & staticEnv); public: @@ -590,6 +594,10 @@ struct EvalSettings : Config static Strings getDefaultNixPath(); + static bool isPseudoUrl(std::string_view s); + + static std::string resolvePseudoUrl(std::string_view url); + Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", "Whether builtin functions that allow executing native code should be enabled."}; diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc index 6df95f1f0..89ddbde7e 100644 --- a/src/libexpr/flake/config.cc +++ b/src/libexpr/flake/config.cc @@ -56,7 +56,7 @@ void ConfigFile::apply() auto tlname = get(trustedList, name); if (auto saved = tlname ? get(*tlname, valueS) : nullptr) { trusted = *saved; - warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); + printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS); } else { // FIXME: filter ANSI escapes, newlines, \r, etc. if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') { diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 119c556ac..105d32467 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -143,7 +143,7 @@ static FlakeInput parseFlakeInput(EvalState & state, } catch (Error & e) { e.addTrace( state.positions[attr.pos], - hintfmt("in flake attribute '%s'", state.symbols[attr.name])); + hintfmt("while evaluating flake attribute '%s'", state.symbols[attr.name])); throw; } } @@ -152,7 +152,7 @@ static FlakeInput parseFlakeInput(EvalState & state, try { input.ref = FlakeRef::fromAttrs(attrs); } catch (Error & e) { - e.addTrace(state.positions[pos], hintfmt("in flake input")); + e.addTrace(state.positions[pos], hintfmt("while evaluating flake input")); throw; } else { @@ -220,7 +220,7 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0)); + expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1)); if (auto description = vInfo.attrs->get(state.sDescription)) { expectType(state, nString, *description->value, description->pos); @@ -353,7 +353,7 @@ LockedFlake lockFlake( std::function<void( const FlakeInputs & flakeInputs, - std::shared_ptr<Node> node, + ref<Node> node, const InputPath & inputPathPrefix, std::shared_ptr<const Node> oldNode, const InputPath & lockRootPath, @@ -362,9 +362,15 @@ LockedFlake lockFlake( computeLocks; computeLocks = [&]( + /* The inputs of this node, either from flake.nix or + flake.lock. */ const FlakeInputs & flakeInputs, - std::shared_ptr<Node> node, + /* The node whose locks are to be updated.*/ + ref<Node> node, + /* The path to this node in the lock file graph. */ const InputPath & inputPathPrefix, + /* The old node, if any, from which locks can be + copied. */ std::shared_ptr<const Node> oldNode, const InputPath & lockRootPath, const Path & parentPath, @@ -452,7 +458,7 @@ LockedFlake lockFlake( /* Copy the input from the old lock since its flakeref didn't change and there is no override from a higher level flake. */ - auto childNode = std::make_shared<LockedNode>( + auto childNode = make_ref<LockedNode>( oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake); node->inputs.insert_or_assign(id, childNode); @@ -481,7 +487,7 @@ LockedFlake lockFlake( .isFlake = (*lockedNode)->isFlake, }); } else if (auto follows = std::get_if<1>(&i.second)) { - if (! trustLock) { + if (!trustLock) { // It is possible that the flake has changed, // so we must confirm all the follows that are in the lock file are also in the flake. auto overridePath(inputPath); @@ -521,8 +527,8 @@ LockedFlake lockFlake( this input. */ debug("creating new input '%s'", inputPathS); - if (!lockFlags.allowMutable && !input.ref->input.isLocked()) - throw Error("cannot update flake input '%s' in pure mode", inputPathS); + if (!lockFlags.allowUnlocked && !input.ref->input.isLocked()) + throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS); /* Note: in case of an --override-input, we use the *original* ref (input2.ref) for the @@ -544,7 +550,7 @@ LockedFlake lockFlake( auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath); - auto childNode = std::make_shared<LockedNode>(inputFlake.lockedRef, ref); + auto childNode = make_ref<LockedNode>(inputFlake.lockedRef, ref); node->inputs.insert_or_assign(id, childNode); @@ -564,15 +570,19 @@ LockedFlake lockFlake( oldLock ? std::dynamic_pointer_cast<const Node>(oldLock) : LockFile::read( - inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root, - oldLock ? lockRootPath : inputPath, localPath, false); + inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(), + oldLock ? lockRootPath : inputPath, + localPath, + false); } else { auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( state, *input.ref, useRegistries, flakeCache); - node->inputs.insert_or_assign(id, - std::make_shared<LockedNode>(lockedRef, ref, false)); + + auto childNode = make_ref<LockedNode>(lockedRef, ref, false); + + node->inputs.insert_or_assign(id, childNode); } } @@ -587,8 +597,13 @@ LockedFlake lockFlake( auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true); computeLocks( - flake.inputs, newLockFile.root, {}, - lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false); + flake.inputs, + newLockFile.root, + {}, + lockFlags.recreateLockFile ? nullptr : oldLockFile.root.get_ptr(), + {}, + parentPath, + false); for (auto & i : lockFlags.inputOverrides) if (!overridesUsed.count(i.first)) @@ -611,9 +626,9 @@ LockedFlake lockFlake( if (lockFlags.writeLockFile) { if (auto sourcePath = topRef.input.getSourcePath()) { - if (!newLockFile.isImmutable()) { + if (auto unlockedInput = newLockFile.isUnlocked()) { if (fetchSettings.warnDirty) - warn("will not write lock file of flake '%s' because it has a mutable input", topRef); + warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput); } else { if (!lockFlags.updateLockFile) throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); @@ -737,7 +752,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V .updateLockFile = false, .writeLockFile = false, .useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries, - .allowMutable = !evalSettings.pureEval, + .allowUnlocked = !evalSettings.pureEval, }), v); } diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index 524b18af1..10301d8aa 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -108,11 +108,11 @@ struct LockFlags bool applyNixConfig = false; - /* Whether mutable flake references (i.e. those without a Git + /* Whether unlocked flake references (i.e. those without a Git revision or similar) without a corresponding lock are - allowed. Mutable flake references with a lock are always + allowed. Unlocked flake references with a lock are always allowed. */ - bool allowMutable = true; + bool allowUnlocked = true; /* Whether to commit changes to flake.lock. */ bool commitLockFile = false; diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh index fe4f67193..a36d852a8 100644 --- a/src/libexpr/flake/flakeref.hh +++ b/src/libexpr/flake/flakeref.hh @@ -35,7 +35,7 @@ typedef std::string FlakeId; struct FlakeRef { - /* fetcher-specific representation of the input, sufficient to + /* Fetcher-specific representation of the input, sufficient to perform the fetch operation. */ fetchers::Input input; diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc index 629d2e669..a3ed90e1f 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -31,7 +31,7 @@ FlakeRef getFlakeRef( } LockedNode::LockedNode(const nlohmann::json & json) - : lockedRef(getFlakeRef(json, "locked", "info")) + : lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info" , originalRef(getFlakeRef(json, "original", nullptr)) , isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true) { @@ -49,15 +49,15 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path) { auto pos = root; - if (!pos) return {}; - for (auto & elem : path) { if (auto i = get(pos->inputs, elem)) { if (auto node = std::get_if<0>(&*i)) pos = *node; else if (auto follows = std::get_if<1>(&*i)) { - pos = findInput(*follows); - if (!pos) return {}; + if (auto p = findInput(*follows)) + pos = ref(p); + else + return {}; } } else return {}; @@ -72,7 +72,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) if (version < 5 || version > 7) throw Error("lock file '%s' has unsupported version %d", path, version); - std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap; + std::map<std::string, ref<Node>> nodeMap; std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs; @@ -93,12 +93,12 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) auto jsonNode2 = nodes.find(inputKey); if (jsonNode2 == nodes.end()) throw Error("lock file references missing node '%s'", inputKey); - auto input = std::make_shared<LockedNode>(*jsonNode2); + auto input = make_ref<LockedNode>(*jsonNode2); k = nodeMap.insert_or_assign(inputKey, input).first; getInputs(*input, *jsonNode2); } - if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second)) - node.inputs.insert_or_assign(i.key(), child); + if (auto child = k->second.dynamic_pointer_cast<LockedNode>()) + node.inputs.insert_or_assign(i.key(), ref(child)); else // FIXME: replace by follows node throw Error("lock file contains cycle to root node"); @@ -122,9 +122,9 @@ nlohmann::json LockFile::toJSON() const std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys; std::unordered_set<std::string> keys; - std::function<std::string(const std::string & key, std::shared_ptr<const Node> node)> dumpNode; + std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode; - dumpNode = [&](std::string key, std::shared_ptr<const Node> node) -> std::string + dumpNode = [&](std::string key, ref<const Node> node) -> std::string { auto k = nodeKeys.find(node); if (k != nodeKeys.end()) @@ -159,10 +159,11 @@ nlohmann::json LockFile::toJSON() const n["inputs"] = std::move(inputs); } - if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) { + if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) { n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs()); n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs()); - if (!lockedNode->isFlake) n["flake"] = false; + if (!lockedNode->isFlake) + n["flake"] = false; } nodes[key] = std::move(n); @@ -201,13 +202,13 @@ void LockFile::write(const Path & path) const writeFile(path, fmt("%s\n", *this)); } -bool LockFile::isImmutable() const +std::optional<FlakeRef> LockFile::isUnlocked() const { - std::unordered_set<std::shared_ptr<const Node>> nodes; + std::set<ref<const Node>> nodes; - std::function<void(std::shared_ptr<const Node> node)> visit; + std::function<void(ref<const Node> node)> visit; - visit = [&](std::shared_ptr<const Node> node) + visit = [&](ref<const Node> node) { if (!nodes.insert(node).second) return; for (auto & i : node->inputs) @@ -219,11 +220,12 @@ bool LockFile::isImmutable() const for (auto & i : nodes) { if (i == root) continue; - auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i); - if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false; + auto node = i.dynamic_pointer_cast<const LockedNode>(); + if (node && !node->lockedRef.input.isLocked()) + return node->lockedRef; } - return true; + return {}; } bool LockFile::operator ==(const LockFile & other) const @@ -247,12 +249,12 @@ InputPath parseInputPath(std::string_view s) std::map<InputPath, Node::Edge> LockFile::getAllInputs() const { - std::unordered_set<std::shared_ptr<Node>> done; + std::set<ref<Node>> done; std::map<InputPath, Node::Edge> res; - std::function<void(const InputPath & prefix, std::shared_ptr<Node> node)> recurse; + std::function<void(const InputPath & prefix, ref<Node> node)> recurse; - recurse = [&](const InputPath & prefix, std::shared_ptr<Node> node) + recurse = [&](const InputPath & prefix, ref<Node> node) { if (!done.insert(node).second) return; diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh index 96f1edc76..02e9bdfbc 100644 --- a/src/libexpr/flake/lockfile.hh +++ b/src/libexpr/flake/lockfile.hh @@ -20,7 +20,7 @@ struct LockedNode; type LockedNode. */ struct Node : std::enable_shared_from_this<Node> { - typedef std::variant<std::shared_ptr<LockedNode>, InputPath> Edge; + typedef std::variant<ref<LockedNode>, InputPath> Edge; std::map<FlakeId, Edge> inputs; @@ -47,11 +47,13 @@ struct LockedNode : Node struct LockFile { - std::shared_ptr<Node> root = std::make_shared<Node>(); + ref<Node> root = make_ref<Node>(); LockFile() {}; LockFile(const nlohmann::json & json, const Path & path); + typedef std::map<ref<const Node>, std::string> KeyMap; + nlohmann::json toJSON() const; std::string to_string() const; @@ -60,7 +62,8 @@ struct LockFile void write(const Path & path) const; - bool isImmutable() const; + /* Check whether this lock file has any unlocked inputs. */ + std::optional<FlakeRef> isUnlocked() const; bool operator ==(const LockFile & other) const; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 346741dd5..5ad5d1fd4 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -150,7 +150,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ const Value * outTI = queryMeta("outputsToInstall"); if (!outTI) return outputs; - const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); + auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); /* ^ this shows during `nix-env -i` right under the bad derivation */ if (!outTI->isList()) throw errMsg; Outputs result; diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 7c623a07d..eb6f062b4 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -8,6 +8,58 @@ namespace nix { +struct PosAdapter : AbstractPos +{ + Pos::Origin origin; + + PosAdapter(Pos::Origin origin) + : origin(std::move(origin)) + { + } + + std::optional<std::string> getSource() const override + { + return std::visit(overloaded { + [](const Pos::none_tag &) -> std::optional<std::string> { + return std::nullopt; + }, + [](const Pos::Stdin & s) -> std::optional<std::string> { + // Get rid of the null terminators added by the parser. + return std::string(s.source->c_str()); + }, + [](const Pos::String & s) -> std::optional<std::string> { + // Get rid of the null terminators added by the parser. + return std::string(s.source->c_str()); + }, + [](const Path & path) -> std::optional<std::string> { + try { + return readFile(path); + } catch (Error &) { + return std::nullopt; + } + } + }, origin); + } + + void print(std::ostream & out) const override + { + std::visit(overloaded { + [&](const Pos::none_tag &) { out << "«none»"; }, + [&](const Pos::Stdin &) { out << "«stdin»"; }, + [&](const Pos::String & s) { out << "«string»"; }, + [&](const Path & path) { out << path; } + }, origin); + } +}; + +Pos::operator std::shared_ptr<AbstractPos>() const +{ + auto pos = std::make_shared<PosAdapter>(origin); + pos->line = line; + pos->column = column; + return pos; +} + /* Displaying abstract syntax trees. */ static void showString(std::ostream & str, std::string_view s) @@ -248,24 +300,10 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const std::ostream & operator << (std::ostream & str, const Pos & pos) { - if (!pos) + if (auto pos2 = (std::shared_ptr<AbstractPos>) pos) { + str << *pos2; + } else str << "undefined position"; - else - { - auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%"); - switch (pos.origin) { - case foFile: - f % (const std::string &) pos.file; - break; - case foStdin: - case foString: - f % "(string)"; - break; - default: - throw Error("unhandled Pos origin!"); - } - str << (f % pos.line % pos.column).str(); - } return str; } @@ -289,7 +327,6 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) } - /* Computing levels/displacements for variables. */ void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 5eb022770..ac7ce021e 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -23,15 +23,22 @@ MakeError(MissingArgumentError, EvalError); MakeError(RestrictedPathError, Error); /* Position objects. */ - struct Pos { - std::string file; - FileOrigin origin; uint32_t line; uint32_t column; + struct none_tag { }; + struct Stdin { ref<std::string> source; }; + struct String { ref<std::string> source; }; + + typedef std::variant<none_tag, Stdin, String, Path> Origin; + + Origin origin; + explicit operator bool() const { return line > 0; } + + operator std::shared_ptr<AbstractPos>() const; }; class PosIdx { @@ -47,7 +54,11 @@ public: explicit operator bool() const { return id > 0; } - bool operator<(const PosIdx other) const { return id < other.id; } + bool operator <(const PosIdx other) const { return id < other.id; } + + bool operator ==(const PosIdx other) const { return id == other.id; } + + bool operator !=(const PosIdx other) const { return id != other.id; } }; class PosTable @@ -61,13 +72,13 @@ public: // current origins.back() can be reused or not. mutable uint32_t idx = std::numeric_limits<uint32_t>::max(); - explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {} + // Used for searching in PosTable::[]. + explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {} public: - const std::string file; - const FileOrigin origin; + const Pos::Origin origin; - Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {} + Origin(Pos::Origin origin): origin(origin) {} }; struct Offset { @@ -107,7 +118,7 @@ public: [] (const auto & a, const auto & b) { return a.idx < b.idx; }); const auto origin = *std::prev(pastOrigin); const auto offset = offsets[idx]; - return {origin.file, origin.origin, offset.line, offset.column}; + return {offset.line, offset.column, origin.origin}; } }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7c9b5a2db..e07909f8e 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -34,11 +34,6 @@ namespace nix { Path basePath; PosTable::Origin origin; std::optional<ErrorInfo> error; - ParseData(EvalState & state, PosTable::Origin origin) - : state(state) - , symbols(state.symbols) - , origin(std::move(origin)) - { }; }; struct ParserFormals { @@ -643,29 +638,26 @@ formal #include "filetransfer.hh" #include "fetchers.hh" #include "store-api.hh" +#include "flake/flake.hh" namespace nix { -Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, - const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv) +Expr * EvalState::parse( + char * text, + size_t length, + Pos::Origin origin, + Path basePath, + std::shared_ptr<StaticEnv> & staticEnv) { yyscan_t scanner; - std::string file; - switch (origin) { - case foFile: - file = path; - break; - case foStdin: - case foString: - file = text; - break; - default: - assert(false); - } - ParseData data(*this, {file, origin}); - data.basePath = basePath; + ParseData data { + .state = *this, + .symbols = symbols, + .basePath = std::move(basePath), + .origin = {origin}, + }; yylex_init(&scanner); yy_scan_buffer(text, length, scanner); @@ -717,14 +709,15 @@ Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv auto buffer = readFile(path); // readFile should have left some extra space for terminators buffer.append("\0\0", 2); - return parse(buffer.data(), buffer.size(), foFile, path, dirOf(path), staticEnv); + return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv); } -Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) +Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) { - s.append("\0\0", 2); - return parse(s.data(), s.size(), foString, "", basePath, staticEnv); + auto s = make_ref<std::string>(std::move(s_)); + s->append("\0\0", 2); + return parse(s->data(), s->size(), Pos::String{.source = s}, basePath, staticEnv); } @@ -740,7 +733,8 @@ Expr * EvalState::parseStdin() auto buffer = drainFD(0); // drainFD should have left some extra space for terminators buffer.append("\0\0", 2); - return parse(buffer.data(), buffer.size(), foStdin, "", absPath("."), staticBaseEnv); + auto s = make_ref<std::string>(std::move(buffer)); + return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv); } @@ -805,17 +799,28 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl std::pair<bool, std::string> res; - if (isUri(elem.second)) { + if (EvalSettings::isPseudoUrl(elem.second)) { try { - res = { true, store->toRealPath(fetchers::downloadTarball( - store, resolveUri(elem.second), "source", false).first.storePath) }; + auto storePath = fetchers::downloadTarball( + store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath; + res = { true, store->toRealPath(storePath) }; } catch (FileTransferError & e) { logWarning({ .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) }); res = { false, "" }; } - } else { + } + + else if (hasPrefix(elem.second, "flake:")) { + settings.requireExperimentalFeature(Xp::Flakes); + auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false); + debug("fetching flake search path element '%s''", elem.second); + auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath; + res = { true, store->toRealPath(storePath) }; + } + + else { auto path = absPath(elem.second); if (pathExists(path)) res = { true, path }; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 8a4c19f7c..7efe50324 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -368,8 +368,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) auto output = runProgram(program, true, commandArgs); Expr * parsed; try { - auto base = state.positions[pos]; - parsed = state.parseExprFromString(std::move(output), base.file); + parsed = state.parseExprFromString(std::move(output), "/"); } catch (Error & e) { e.addTrace(state.positions[pos], "While parsing the output from '%1%'", program); throw; @@ -798,7 +797,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * v = *args[1]; } catch (Error & e) { PathSet context; - e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned()); + e.addTrace(nullptr, state.coerceToString(pos, *args[0], context).toOwned()); throw; } } @@ -1461,10 +1460,10 @@ static RegisterPrimOp primop_storePath({ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { /* We don’t check the path right now, because we don’t want to - throw if the path isn’t allowed, but just return false (and we - can’t just catch the exception here because we still want to - throw if something in the evaluation of `*args[0]` tries to - access an unauthorized path). */ + throw if the path isn’t allowed, but just return false (and we + can’t just catch the exception here because we still want to + throw if something in the evaluation of `*args[0]` tries to + access an unauthorized path). */ auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false }); try { @@ -4018,7 +4017,7 @@ void EvalState::createBaseEnv() // the parser needs two NUL bytes as terminators; one of them // is implied by being a C string. "\0"; - eval(parse(code, sizeof(code), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation); + eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation); } diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 84e7f5c02..680446787 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -220,8 +220,6 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v } else url = state.forceStringNoCtx(*args[0], pos); - url = resolveUri(*url); - state.checkURI(*url); if (name == "") diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh index 4f6915882..5bb5e66d3 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexprtests.hh @@ -123,7 +123,7 @@ namespace nix { MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) { if (arg.type() != nAttrs) { - *result_listener << "Expexted set got " << arg.type(); + *result_listener << "Expected set got " << arg.type(); return false; } else if (arg.attrs->size() != (size_t)n) { *result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size(); diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 16cf66d2c..49fbc5e98 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -151,20 +151,7 @@ namespace nix { // The `y` attribute is at position const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }"; auto v = eval(expr); - ASSERT_THAT(v, IsAttrsOfSize(3)); - - auto file = v.attrs->find(createSymbol("file")); - ASSERT_NE(file, nullptr); - // FIXME: The file when running these tests is the input string?!? - ASSERT_THAT(*file->value, IsStringEq(expr)); - - auto line = v.attrs->find(createSymbol("line")); - ASSERT_NE(line, nullptr); - ASSERT_THAT(*line->value, IsIntEq(1)); - - auto column = v.attrs->find(createSymbol("column")); - ASSERT_NE(column, nullptr); - ASSERT_THAT(*column->value, IsIntEq(33)); + ASSERT_THAT(v, IsNull()); } TEST_F(PrimOpTest, hasAttr) { diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index 7c3bf9492..3f6222768 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -24,7 +24,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) { - xmlAttrs["path"] = pos.file; + if (auto path = std::get_if<Path>(&pos.origin)) + xmlAttrs["path"] = *path; xmlAttrs["line"] = (format("%1%") % pos.line).str(); xmlAttrs["column"] = (format("%1%") % pos.column).str(); } |