From 9102508f335044463160a09fb5f56aab03cde599 Mon Sep 17 00:00:00 2001 From: regnat Date: Wed, 9 Jun 2021 16:23:26 +0200 Subject: [PATCH] Use the cache in `nix search` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not optimal atm, possibly because we don’t cache the evaluation failures --- src/libcmd/installables.cc | 31 ++++++++------ src/libcmd/installables.hh | 5 ++- src/libexpr/eval.cc | 51 +++++++++++++++++++++++ src/libexpr/eval.hh | 6 +++ src/nix/search.cc | 85 +++++++++++++++++++++++++++++++++++++- 5 files changed, 161 insertions(+), 17 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e184de9a7..4206c70ce 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -302,6 +302,14 @@ Installable::getCursors(EvalState & state) return {{evalCache->getRoot(), ""}}; } +std::pair Installable::toValue(EvalState & state) +{ + auto values = toValues(state); + if (values.empty()) + throw Error("cannot find flake attribute '%s'", what()); + return {std::get<0>(values[0]), std::get<1>(values[0])}; +} + std::pair, std::string> Installable::getCursor(EvalState & state) { @@ -378,11 +386,11 @@ struct InstallableAttrPath : InstallableValue std::string what() override { return attrPath; } - std::pair toValue(EvalState & state) override + std::vector> toValues(EvalState & state) override { auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); state.forceValue(*vRes); - return {vRes, pos}; + return {{vRes, pos, attrPath}}; } virtual std::vector toDerivations() override; @@ -539,25 +547,22 @@ std::vector InstallableFlake::toDerivations() return res; } -std::pair InstallableFlake::toValue(EvalState & state) +std::vector> +InstallableFlake::toValues(EvalState & state) { - auto lockedFlake = getLockedFlake(); - - auto vOutputs = getFlakeOutputs(state, *lockedFlake); - + auto vOutputs = getFlakeOutputs(state, *getLockedFlake()); auto emptyArgs = state.allocBindings(0); + std::vector> res; + for (auto & attrPath : getActualAttrPaths()) { try { auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); - state.forceValue(*v); - return {v, pos}; - } catch (AttrPathNotFound & e) { - } + res.push_back({v, pos, attrPath}); + } catch (Error &) {} } - throw Error("flake '%s' does not provide attribute %s", - flakeRef, showAttrPaths(getActualAttrPaths())); + return res; } std::vector, std::string>> diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 298fd48f8..4b400e7cf 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -41,10 +41,11 @@ struct Installable UnresolvedApp toApp(EvalState & state); - virtual std::pair toValue(EvalState & state) + virtual std::vector> toValues(EvalState & state) { throw Error("argument '%s' cannot be evaluated", what()); } + std::pair toValue(EvalState & state); /* Return a value only if this installable is a store path or a symlink to it. */ @@ -109,7 +110,7 @@ struct InstallableFlake : InstallableValue std::vector toDerivations() override; - std::pair toValue(EvalState & state) override; + std::vector> toValues(EvalState & state) override; std::vector, std::string>> getCursors(EvalState & state) override; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 2643ad397..cc562328a 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1284,6 +1284,43 @@ bool EvalState::getAttrField(Value & attrs, const std::vector & selector return true; } +void EvalState::getAttrFieldThrow(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest) +{ + if (!getAttrField(attrs, selector, pos, dest)) + throw Error("Missing attribute path '%s'", "ImTooLazyToImplementThisRightNow"); +} + +std::vector EvalState::getFields(Value & attrs, const Pos & pos) +{ + auto eval_cache = attrs.getEvalCache(); + if (eval_cache.isEmpty()) { + forceValue(attrs, pos); + eval_cache = attrs.getEvalCache(); + } + if (auto attrNames = eval_cache.listChildren(symbols)) { + bool everythingCached = true; + std::vector res; + for (auto & attrName : *attrNames) { + auto newValue = allocValue(); + try { + if (lazyGetAttrField(attrs, {attrName}, pos, *newValue)) { + res.push_back(Attr(attrName, newValue)); + } else { + everythingCached = false; + break; + } + } catch (Error &) { + everythingCached = false; + } + } + if (everythingCached) return res; + } + + forceValue(attrs); + auto attrsStart = attrs.attrs->attrs; + return std::vector(attrsStart, attrsStart + attrs.attrs->size()); +} + void ExprSelect::eval(EvalState & state, Env & env, Value & v) { Value vTmp; @@ -1493,6 +1530,20 @@ std::optional ValueCache::getRawValue() return rawCache->getCachedValue(); } +std::optional> ValueCache::listChildren(SymbolTable& symbols) +{ + auto ret = std::vector(); + if (rawCache) { + auto cachedValue = rawCache->getCachedValue(); + if (std::get_if(&cachedValue)) { + for (auto & fieldStr : rawCache->getChildren()) + ret.push_back(symbols.create(fieldStr)); + } + return ret; + } + return std::nullopt; +} + void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) { if (auto evalCache = fun.getEvalCache(); !evalCache.isEmpty()) { diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index b995b3f66..f421e8dd2 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -359,6 +359,12 @@ public: // Similar to `getAttrField`, but if the cache says that the result is an // attribute set, just return a thunk to it rather than forcing it. bool lazyGetAttrField(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest); + + // Similar to `getAttrField`, but throws an `Error` if the field can’t be + // found + void getAttrFieldThrow(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest); + + std::vector getFields(Value & attrs, const Pos & pos); }; diff --git a/src/nix/search.cc b/src/nix/search.cc index c52a48d4e..0b8655117 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -82,6 +82,87 @@ struct CmdSearch : InstallableCommand, MixJSON uint64_t results = 0; std::function & attrPath, bool initialRecurse)> visit; + std::function & attrPath, bool initialRecurse)> visit2; + + Value * vTmp = state->allocValue(); + + visit2 = [&](Value & current, const std::vector & attrPath, bool initialRecurse) + { + Activity act(*logger, lvlInfo, actUnknown, + fmt("evaluating '%s'", concatStringsSep(".", attrPath))); + auto recurse = [&]() + { + for (auto & attr : state->getFields(current, noPos)) { + auto attrPath2(attrPath); + attrPath2.push_back(attr.name); + visit2(*attr.value, attrPath2, false); + } + }; + + try { + if (state->isDerivation(current)) { + size_t found = 0; + + state->getAttrFieldThrow(current, {state->sName}, noPos, *vTmp); + DrvName name(state->forceString(*vTmp)); + + auto hasDescription = state->getAttrField(current, {state->sMeta, state->sDescription}, noPos, *vTmp); + auto description = hasDescription ? state->forceString(*vTmp) : ""; + std::replace(description.begin(), description.end(), '\n', ' '); + auto attrPath2 = concatStringsSep(".", attrPath); + + std::smatch attrPathMatch; + std::smatch descriptionMatch; + std::smatch nameMatch; + + for (auto & regex : regexes) { + std::regex_search(attrPath2, attrPathMatch, regex); + std::regex_search(name.name, nameMatch, regex); + std::regex_search(description, descriptionMatch, regex); + if (!attrPathMatch.empty() + || !nameMatch.empty() + || !descriptionMatch.empty()) + found++; + } + + if (found == res.size()) { + results++; + if (json) { + auto jsonElem = jsonOut->object(attrPath2); + jsonElem.attr("pname", name.name); + jsonElem.attr("version", name.version); + jsonElem.attr("description", description); + } else { + auto name2 = hilite(name.name, nameMatch, "\e[0;2m"); + if (results > 1) logger->cout(""); + logger->cout( + "* %s%s", + wrap("\e[0;1m", hilite(attrPath2, attrPathMatch, "\e[0;1m")), + name.version != "" ? " (" + name.version + ")" : ""); + if (description != "") + logger->cout( + " %s", hilite(description, descriptionMatch, ANSI_NORMAL)); + } + } + } else if ( + attrPath.size() == 0 + || (attrPath[0] == "legacyPackages" && attrPath.size() <= 2) + || (attrPath[0] == "packages" && attrPath.size() <= 2)) + recurse(); + + else if (initialRecurse) + recurse(); + + else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) { + auto hasRecurse = state->getAttrField(current, {state->sRecurseForDerivations}, noPos, *vTmp); + if (hasRecurse && state->forceBool(*vTmp, noPos)) + recurse(); + } + } catch (EvalError & e) { + if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages")) + throw; + } + }; visit = [&](eval_cache::AttrCursor & cursor, const std::vector & attrPath, bool initialRecurse) { @@ -165,8 +246,8 @@ struct CmdSearch : InstallableCommand, MixJSON } }; - for (auto & [cursor, prefix] : installable->getCursors(*state)) - visit(*cursor, parseAttrPath(*state, prefix), true); + for (auto & [value, pos, prefix] : installable->toValues(*state)) + visit2(*value, parseAttrPath(*state, prefix), true); if (!json && !results) throw Error("no results for the given search term(s)!");