From 90825dea518ea078f0783a72cc471a5b3716d198 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Jul 2017 19:02:56 +0200 Subject: [PATCH] Add "nix search" command --- src/libexpr/get-drvs.cc | 44 ++++++++++---- src/libexpr/get-drvs.hh | 39 ++++++------ src/libutil/args.cc | 1 + src/libutil/args.hh | 4 +- src/nix-env/nix-env.cc | 90 ++++++++++++++-------------- src/nix-env/user-env.cc | 7 ++- src/nix/command.hh | 34 +++++++---- src/nix/installables.cc | 16 ++--- src/nix/search.cc | 130 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 263 insertions(+), 102 deletions(-) create mode 100644 src/nix/search.cc diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 4200e8fd6..b7e16de7f 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -9,7 +9,34 @@ namespace nix { -string DrvInfo::queryDrvPath() +DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs) + : state(&state), attrs(attrs), attrPath(attrPath) +{ +} + + +string DrvInfo::queryName() const +{ + if (name == "" && attrs) { + auto i = attrs->find(state->sName); + if (i == attrs->end()) throw TypeError("derivation name missing"); + name = state->forceStringNoCtx(*i->value); + } + return name; +} + + +string DrvInfo::querySystem() const +{ + if (system == "" && attrs) { + auto i = attrs->find(state->sSystem); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); + } + return system; +} + + +string DrvInfo::queryDrvPath() const { if (drvPath == "" && attrs) { Bindings::iterator i = attrs->find(state->sDrvPath); @@ -20,7 +47,7 @@ string DrvInfo::queryDrvPath() } -string DrvInfo::queryOutPath() +string DrvInfo::queryOutPath() const { if (outPath == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutPath); @@ -76,7 +103,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) } -string DrvInfo::queryOutputName() +string DrvInfo::queryOutputName() const { if (outputName == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutputName); @@ -225,17 +252,12 @@ static bool getDerivation(EvalState & state, Value & v, if (done.find(v.attrs) != done.end()) return false; done.insert(v.attrs); - Bindings::iterator i = v.attrs->find(state.sName); - /* !!! We really would like to have a decent back trace here. */ - if (i == v.attrs->end()) throw TypeError("derivation name missing"); + DrvInfo drv(state, attrPath, v.attrs); - Bindings::iterator i2 = v.attrs->find(state.sSystem); - - DrvInfo drv(state, state.forceStringNoCtx(*i->value), attrPath, - i2 == v.attrs->end() ? "unknown" : state.forceStringNoCtx(*i2->value, *i2->pos), - v.attrs); + drv.queryName(); drvs.push_back(drv); + return false; } catch (AssertionError & e) { diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh index 37fcbe829..82fb8a3ac 100644 --- a/src/libexpr/get-drvs.hh +++ b/src/libexpr/get-drvs.hh @@ -17,31 +17,32 @@ public: private: EvalState * state; - string drvPath; - string outPath; - string outputName; + mutable string name; + mutable string system; + mutable string drvPath; + mutable string outPath; + mutable string outputName; Outputs outputs; - bool failed; // set if we get an AssertionError + bool failed = false; // set if we get an AssertionError - Bindings * attrs, * meta; + Bindings * attrs = nullptr, * meta = nullptr; Bindings * getMeta(); bool checkMeta(Value & v); public: - string name; string attrPath; /* path towards the derivation */ - string system; - DrvInfo(EvalState & state) : state(&state), failed(false), attrs(0), meta(0) { }; - DrvInfo(EvalState & state, const string & name, const string & attrPath, const string & system, Bindings * attrs) - : state(&state), failed(false), attrs(attrs), meta(0), name(name), attrPath(attrPath), system(system) { }; + DrvInfo(EvalState & state) : state(&state) { }; + DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs); - string queryDrvPath(); - string queryOutPath(); - string queryOutputName(); + string queryName() const; + string querySystem() const; + string queryDrvPath() const; + string queryOutPath() const; + string queryOutputName() const; /** Return the list of outputs. The "outputs to install" are determined by `mesa.outputsToInstall`. */ Outputs queryOutputs(bool onlyOutputsToInstall = false); @@ -58,15 +59,9 @@ public: MetaValue queryMetaInfo(EvalState & state, const string & name) const; */ - void setDrvPath(const string & s) - { - drvPath = s; - } - - void setOutPath(const string & s) - { - outPath = s; - } + void setName(const string & s) { name = s; } + void setDrvPath(const string & s) { drvPath = s; } + void setOutPath(const string & s) { outPath = s; } void setFailed() { failed = true; }; bool hasFailed() { return failed; }; diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 0eed49454..19a45d7e9 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -66,6 +66,7 @@ void Args::printHelp(const string & programName, std::ostream & out) std::cout << renderLabels({exp.label}); // FIXME: handle arity > 1 if (exp.arity == 0) std::cout << "..."; + if (exp.optional) std::cout << "?"; } std::cout << "\n"; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index ef8a7953e..37e780dd1 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -164,9 +164,9 @@ public: } /* Expect a string argument. */ - void expectArg(const std::string & label, string * dest) + void expectArg(const std::string & label, string * dest, bool optional = false) { - expectedArgs.push_back(ExpectedArg{label, 1, false, [=](Strings ss) { + expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](Strings ss) { *dest = ss.front(); }}); } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 10100d6a6..8620cd255 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -186,7 +186,7 @@ static void loadDerivations(EvalState & state, Path nixExprPath, system. */ for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) { j = i; j++; - if (systemFilter != "*" && i->system != systemFilter) + if (systemFilter != "*" && i->querySystem() != systemFilter) elems.erase(i); } } @@ -247,7 +247,7 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, for (DrvInfos::const_iterator j = allElems.begin(); j != allElems.end(); ++j, ++n) { - DrvName drvName(j->name); + DrvName drvName(j->queryName()); if (i.matches(drvName)) { i.hits++; matches.push_back(std::pair(*j, n)); @@ -269,36 +269,36 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, StringSet multiple; for (auto & j : matches) { - DrvName drvName(j.first.name); + DrvName drvName(j.first.queryName()); int d = 1; Newest::iterator k = newest.find(drvName.name); if (k != newest.end()) { - d = j.first.system == k->second.first.system ? 0 : - j.first.system == settings.thisSystem ? 1 : - k->second.first.system == settings.thisSystem ? -1 : 0; + d = j.first.querySystem() == k->second.first.querySystem() ? 0 : + j.first.querySystem() == settings.thisSystem ? 1 : + k->second.first.querySystem() == settings.thisSystem ? -1 : 0; if (d == 0) d = comparePriorities(state, j.first, k->second.first); if (d == 0) - d = compareVersions(drvName.version, DrvName(k->second.first.name).version); + d = compareVersions(drvName.version, DrvName(k->second.first.queryName()).version); } if (d > 0) { newest.erase(drvName.name); newest.insert(Newest::value_type(drvName.name, j)); - multiple.erase(j.first.name); + multiple.erase(j.first.queryName()); } else if (d == 0) { - multiple.insert(j.first.name); + multiple.insert(j.first.queryName()); } } matches.clear(); for (auto & j : newest) { - if (multiple.find(j.second.first.name) != multiple.end()) + if (multiple.find(j.second.first.queryName()) != multiple.end()) printInfo( - format("warning: there are multiple derivations named ‘%1%’; using the first one") - % j.second.first.name); + "warning: there are multiple derivations named ‘%1%’; using the first one", + j.second.first.queryName()); matches.push_back(j.second); } } @@ -386,7 +386,8 @@ static void queryInstSources(EvalState & state, if (dash != string::npos) name = string(name, dash + 1); - DrvInfo elem(state, name, "", "", 0); + DrvInfo elem(state, "", nullptr); + elem.setName(name); if (isDerivation(path)) { elem.setDrvPath(path); @@ -468,8 +469,8 @@ static void installDerivations(Globals & globals, path is not the one we want (e.g., `java-front' versus `java-front-0.9pre15899'). */ if (globals.forceName != "") - i.name = globals.forceName; - newNames.insert(DrvName(i.name).name); + i.setName(globals.forceName); + newNames.insert(DrvName(i.queryName()).name); } @@ -484,17 +485,17 @@ static void installDerivations(Globals & globals, DrvInfos installedElems = queryInstalled(*globals.state, profile); for (auto & i : installedElems) { - DrvName drvName(i.name); + DrvName drvName(i.queryName()); if (!globals.preserveInstalled && newNames.find(drvName.name) != newNames.end() && !keep(i)) - printInfo(format("replacing old ‘%1%’") % i.name); + printInfo("replacing old ‘%s’", i.queryName()); else allElems.push_back(i); } for (auto & i : newElems) - printInfo(format("installing ‘%1%’") % i.name); + printInfo("installing ‘%s’", i.queryName()); } printMissing(*globals.state, newElems); @@ -548,7 +549,7 @@ static void upgradeDerivations(Globals & globals, /* Go through all installed derivations. */ DrvInfos newElems; for (auto & i : installedElems) { - DrvName drvName(i.name); + DrvName drvName(i.queryName()); try { @@ -569,7 +570,7 @@ static void upgradeDerivations(Globals & globals, for (auto j = availElems.begin(); j != availElems.end(); ++j) { if (comparePriorities(*globals.state, i, *j) > 0) continue; - DrvName newName(j->name); + DrvName newName(j->queryName()); if (newName.name == drvName.name) { int d = compareVersions(drvName.version, newName.version); if ((upgradeType == utLt && d < 0) || @@ -596,14 +597,13 @@ static void upgradeDerivations(Globals & globals, { const char * action = compareVersions(drvName.version, bestVersion) <= 0 ? "upgrading" : "downgrading"; - printInfo( - format("%1% ‘%2%’ to ‘%3%’") - % action % i.name % bestElem->name); + printInfo("%1% ‘%2%’ to ‘%3%’", + action, i.queryName(), bestElem->queryName()); newElems.push_back(*bestElem); } else newElems.push_back(i); } catch (Error & e) { - e.addPrefix(format("while trying to find an upgrade for ‘%1%’:\n") % i.name); + e.addPrefix(fmt("while trying to find an upgrade for ‘%s’:\n", i.queryName())); throw; } } @@ -663,10 +663,10 @@ static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs) /* Update all matching derivations. */ for (auto & i : installedElems) { - DrvName drvName(i.name); + DrvName drvName(i.queryName()); for (auto & j : selectors) if (j.matches(drvName)) { - printInfo(format("setting flag on ‘%1%’") % i.name); + printInfo("setting flag on ‘%1%’", i.queryName()); j.hits++; setMetaFlag(*globals.state, i, flagName, flagValue); break; @@ -702,7 +702,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs) DrvInfo & drv(elems.front()); if (globals.forceName != "") - drv.name = globals.forceName; + drv.setName(globals.forceName); if (drv.queryDrvPath() != "") { PathSet paths = {drv.queryDrvPath()}; @@ -732,7 +732,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors, DrvInfos newElems; for (auto & i : installedElems) { - DrvName drvName(i.name); + DrvName drvName(i.queryName()); bool found = false; for (auto & j : selectors) /* !!! the repeated calls to followLinksToStorePath() @@ -740,7 +740,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors, if ((isPath(j) && i.queryOutPath() == globals.state->store->followLinksToStorePath(j)) || DrvName(j).matches(drvName)) { - printInfo(format("uninstalling ‘%1%’") % i.name); + printInfo("uninstalling ‘%s’", i.queryName()); found = true; break; } @@ -771,9 +771,11 @@ static bool cmpChars(char a, char b) static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b) { + auto a_name = a.queryName(); + auto b_name = b.queryName(); return lexicographical_compare( - a.name.begin(), a.name.end(), - b.name.begin(), b.name.end(), cmpChars); + a_name.begin(), a_name.end(), + b_name.begin(), b_name.end(), cmpChars); } @@ -822,13 +824,13 @@ typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff; static VersionDiff compareVersionAgainstSet( const DrvInfo & elem, const DrvInfos & elems, string & version) { - DrvName name(elem.name); + DrvName name(elem.queryName()); VersionDiff diff = cvUnavail; version = "?"; for (auto & i : elems) { - DrvName name2(i.name); + DrvName name2(i.queryName()); if (name.name == name2.name) { int d = compareVersions(name.version, name2.version); if (d < 0) { @@ -857,8 +859,8 @@ static void queryJSON(Globals & globals, vector & elems) for (auto & i : elems) { JSONObject pkgObj = topObj.object(i.attrPath); - pkgObj.attr("name", i.name); - pkgObj.attr("system", i.system); + pkgObj.attr("name", i.queryName()); + pkgObj.attr("system", i.querySystem()); JSONObject metaObj = pkgObj.object("meta"); StringSet metaNames = i.queryMetaNames(); @@ -866,7 +868,7 @@ static void queryJSON(Globals & globals, vector & elems) auto placeholder = metaObj.placeholder(j); Value * v = i.queryMeta(j); if (!v) { - printError(format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j); + printError("derivation ‘%s’ has invalid meta attribute ‘%s’", i.queryName(), j); placeholder.write(nullptr); } else { PathSet context; @@ -963,7 +965,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) try { paths.insert(i.queryOutPath()); } catch (AssertionError & e) { - printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name); + printMsg(lvlTalkative, "skipping derivation named ‘%s’ which gives an assertion failure", i.queryName()); i.setFailed(); } validPaths = globals.state->store->queryValidPaths(paths); @@ -1024,9 +1026,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) columns.push_back(i.attrPath); if (xmlOutput) - attrs["name"] = i.name; + attrs["name"] = i.queryName(); else if (printName) - columns.push_back(i.name); + columns.push_back(i.queryName()); if (compareVersions) { /* Compare this element against the versions of the @@ -1059,10 +1061,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) } if (xmlOutput) { - if (i.system != "") attrs["system"] = i.system; + if (i.querySystem() != "") attrs["system"] = i.querySystem(); } else if (printSystem) - columns.push_back(i.system); + columns.push_back(i.querySystem()); if (printDrvPath) { string drvPath = i.queryDrvPath(); @@ -1110,7 +1112,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) attrs2["name"] = j; Value * v = i.queryMeta(j); if (!v) - printError(format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j); + printError("derivation ‘%s’ has invalid meta attribute ‘%s’", i.queryName(), j); else { if (v->type == tString) { attrs2["type"] = "string"; @@ -1161,9 +1163,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) cout.flush(); } catch (AssertionError & e) { - printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i.name); + printMsg(lvlTalkative, "skipping derivation named ‘%1%’ which gives an assertion failure", i.queryName()); } catch (Error & e) { - e.addPrefix(format("while querying the derivation named ‘%1%’:\n") % i.name); + e.addPrefix(fmt("while querying the derivation named ‘%1%’:\n", i.queryName())); throw; } } diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index e9997fae5..df5105f12 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -56,9 +56,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, state.mkAttrs(v, 16); mkString(*state.allocAttr(v, state.sType), "derivation"); - mkString(*state.allocAttr(v, state.sName), i.name); - if (!i.system.empty()) - mkString(*state.allocAttr(v, state.sSystem), i.system); + mkString(*state.allocAttr(v, state.sName), i.queryName()); + auto system = i.querySystem(); + if (!system.empty()) + mkString(*state.allocAttr(v, state.sSystem), system); mkString(*state.allocAttr(v, state.sOutPath), i.queryOutPath()); if (drvPath != "") mkString(*state.allocAttr(v, state.sDrvPath), i.queryDrvPath()); diff --git a/src/nix/command.hh b/src/nix/command.hh index ae7709b5d..536802653 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -62,17 +62,13 @@ struct Installable } }; -/* A command that operates on a list of "installables", which can be - store paths, attribute paths, Nix expressions, etc. */ -struct InstallablesCommand : virtual Args, StoreCommand +struct SourceExprCommand : virtual Args, StoreCommand { - std::vector> installables; Path file; - InstallablesCommand() + SourceExprCommand() { mkFlag('f', "file", "file", "evaluate FILE rather than the default", &file); - expectArgs("installables", &_installables); } /* Return a value representing the Nix expression from which we @@ -81,14 +77,32 @@ struct InstallablesCommand : virtual Args, StoreCommand = import ...; bla = import ...; }’. */ Value * getSourceExpr(EvalState & state); + ref getEvalState(); + +private: + + std::shared_ptr evalState; + + Value * vSourceExpr = 0; +}; + +/* A command that operates on a list of "installables", which can be + store paths, attribute paths, Nix expressions, etc. */ +struct InstallablesCommand : virtual Args, SourceExprCommand +{ + std::vector> installables; + + InstallablesCommand() + { + expectArgs("installables", &_installables); + } + std::vector> parseInstallables(ref store, Strings ss); enum ToStorePathsMode { Build, NoBuild, DryRun }; PathSet toStorePaths(ref store, ToStorePathsMode mode); - ref getEvalState(); - void prepare() override; virtual bool useDefaultInstallables() { return true; } @@ -96,10 +110,6 @@ struct InstallablesCommand : virtual Args, StoreCommand private: Strings _installables; - - std::shared_ptr evalState; - - Value * vSourceExpr = 0; }; /* A command that operates on zero or more store paths. */ diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 7fad8fe41..4da736f4d 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -12,7 +12,7 @@ namespace nix { -Value * InstallablesCommand::getSourceExpr(EvalState & state) +Value * SourceExprCommand::getSourceExpr(EvalState & state) { if (vSourceExpr) return vSourceExpr; @@ -59,6 +59,13 @@ Value * InstallablesCommand::getSourceExpr(EvalState & state) return vSourceExpr; } +ref SourceExprCommand::getEvalState() +{ + if (!evalState) + evalState = std::make_shared(Strings{}, getStore()); + return ref(evalState); +} + struct InstallableStoreDrv : Installable { Path storePath; @@ -237,13 +244,6 @@ PathSet InstallablesCommand::toStorePaths(ref store, ToStorePathsMode mod return outPaths; } -ref InstallablesCommand::getEvalState() -{ - if (!evalState) - evalState = std::make_shared(Strings{}, getStore()); - return ref(evalState); -} - void InstallablesCommand::prepare() { installables = parseInstallables(getStore(), _installables); diff --git a/src/nix/search.cc b/src/nix/search.cc new file mode 100644 index 000000000..813f6d0a6 --- /dev/null +++ b/src/nix/search.cc @@ -0,0 +1,130 @@ +#include "command.hh" +#include "globals.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "names.hh" +#include "get-drvs.hh" + +#include + +using namespace nix; + +std::string hilite(const std::string & s, const std::smatch & m) +{ + return + m.empty() + ? s + : std::string(m.prefix()) + + ANSI_RED + std::string(m.str()) + ANSI_NORMAL + + std::string(m.suffix()); +} + +struct CmdSearch : SourceExprCommand +{ + std::string re; + + CmdSearch() + { + expectArg("regex", &re, true); + } + + std::string name() override + { + return "search"; + } + + std::string description() override + { + return "query available packages"; + } + + void run(ref store) override + { + settings.readOnlyMode = true; + + std::regex regex(re, std::regex::extended | std::regex::icase); + + auto state = getEvalState(); + + std::function doExpr; + + bool first = true; + + doExpr = [&](Value * v, std::string attrPath, bool toplevel) { + debug("at attribute ‘%s’", attrPath); + + try { + + state->forceValue(*v); + + if (v->type == tLambda && toplevel) { + Value * v2 = state->allocValue(); + state->autoCallFunction(*state->allocBindings(1), *v, *v2); + v = v2; + state->forceValue(*v); + } + + if (state->isDerivation(*v)) { + + DrvInfo drv(*state, attrPath, v->attrs); + + DrvName parsed(drv.queryName()); + + std::smatch attrPathMatch; + std::regex_search(attrPath, attrPathMatch, regex); + + auto name = parsed.name; + std::smatch nameMatch; + std::regex_search(name, nameMatch, regex); + + std::string description = drv.queryMetaString("description"); + std::replace(description.begin(), description.end(), '\n', ' '); + std::smatch descriptionMatch; + std::regex_search(description, descriptionMatch, regex); + + if (!attrPathMatch.empty() + || !nameMatch.empty() + || !descriptionMatch.empty()) + { + if (!first) std::cout << "\n"; + first = false; + + std::cout << fmt( + "Attribute name: %s\n" + "Package name: %s\n" + "Version: %s\n" + "Description: %s\n", + hilite(attrPath, attrPathMatch), + hilite(name, nameMatch), + parsed.version, + hilite(description, descriptionMatch)); + } + } + + else if (v->type == tAttrs) { + + if (!toplevel) { + auto attrs = v->attrs; + Bindings::iterator j = attrs->find(state->symbols.create("recurseForDerivations")); + if (j == attrs->end() || !state->forceBool(*j->value, *j->pos)) return; + } + + Bindings::iterator j = v->attrs->find(state->symbols.create("_toplevel")); + bool toplevel2 = j != v->attrs->end() && state->forceBool(*j->value, *j->pos); + + for (auto & i : *v->attrs) { + doExpr(i.value, + attrPath == "" ? (std::string) i.name : attrPath + "." + (std::string) i.name, + toplevel2); + } + } + + } catch (AssertionError & e) { + } + }; + + doExpr(getSourceExpr(*state), "", true); + } +}; + +static RegisterCommand r1(make_ref());