Offer suggestions for nix-env -i
Closes https://github.com/NixOS/nix/issues/972
This commit is contained in:
parent
79f27500a4
commit
4b28798bfc
|
@ -42,7 +42,7 @@ DrvName::~DrvName()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
||||||
bool DrvName::matches(DrvName & n)
|
bool DrvName::matches(const DrvName & n)
|
||||||
{
|
{
|
||||||
if (name != "*") {
|
if (name != "*") {
|
||||||
if (!regex) {
|
if (!regex) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ struct DrvName
|
||||||
DrvName(std::string_view s);
|
DrvName(std::string_view s);
|
||||||
~DrvName();
|
~DrvName();
|
||||||
|
|
||||||
bool matches(DrvName & n);
|
bool matches(const DrvName & n);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Regex> regex;
|
std::unique_ptr<Regex> regex;
|
||||||
|
|
|
@ -224,6 +224,91 @@ static void checkSelectorUse(DrvNames & selectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::set<std::string> searchByPrefix(const DrvInfos & allElems, std::string_view prefix) {
|
||||||
|
constexpr std::size_t maxResults = 3;
|
||||||
|
std::set<std::string> result;
|
||||||
|
for (const auto & drvInfo : allElems) {
|
||||||
|
const auto drvName = DrvName { drvInfo.queryName() };
|
||||||
|
if (hasPrefix(drvName.name, prefix)) {
|
||||||
|
result.emplace(drvName.name);
|
||||||
|
|
||||||
|
if (result.size() >= maxResults) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Match
|
||||||
|
{
|
||||||
|
DrvInfo drvInfo;
|
||||||
|
std::size_t index;
|
||||||
|
|
||||||
|
Match(DrvInfo drvInfo_, std::size_t index_)
|
||||||
|
: drvInfo{std::move(drvInfo_)}
|
||||||
|
, index{index_}
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* If a selector matches multiple derivations
|
||||||
|
with the same name, pick the one matching the current
|
||||||
|
system. If there are still multiple derivations, pick the
|
||||||
|
one with the highest priority. If there are still multiple
|
||||||
|
derivations, pick the one with the highest version.
|
||||||
|
Finally, if there are still multiple derivations,
|
||||||
|
arbitrarily pick the first one. */
|
||||||
|
std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches) {
|
||||||
|
/* Map from package names to derivations. */
|
||||||
|
std::map<std::string, Match> newest;
|
||||||
|
StringSet multiple;
|
||||||
|
|
||||||
|
for (auto & match : matches) {
|
||||||
|
auto & oneDrv = match.drvInfo;
|
||||||
|
|
||||||
|
const auto drvName = DrvName { oneDrv.queryName() };
|
||||||
|
long comparison = 1;
|
||||||
|
|
||||||
|
const auto itOther = newest.find(drvName.name);
|
||||||
|
|
||||||
|
if (itOther != newest.end()) {
|
||||||
|
auto & newestDrv = itOther->second.drvInfo;
|
||||||
|
|
||||||
|
comparison =
|
||||||
|
oneDrv.querySystem() == newestDrv.querySystem() ? 0 :
|
||||||
|
oneDrv.querySystem() == settings.thisSystem ? 1 :
|
||||||
|
newestDrv.querySystem() == settings.thisSystem ? -1 : 0;
|
||||||
|
if (comparison == 0)
|
||||||
|
comparison = comparePriorities(state, oneDrv, newestDrv);
|
||||||
|
if (comparison == 0)
|
||||||
|
comparison = compareVersions(drvName.version, DrvName { newestDrv.queryName() }.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison > 0) {
|
||||||
|
newest.erase(drvName.name);
|
||||||
|
newest.emplace(drvName.name, match);
|
||||||
|
multiple.erase(drvName.fullName);
|
||||||
|
} else if (comparison == 0) {
|
||||||
|
multiple.insert(drvName.fullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.clear();
|
||||||
|
for (auto & [name, match] : newest) {
|
||||||
|
if (multiple.find(name) != multiple.end())
|
||||||
|
printInfo(
|
||||||
|
"warning: there are multiple derivations named '%1%'; using the first one",
|
||||||
|
name);
|
||||||
|
matches.push_back(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end namespace
|
||||||
|
|
||||||
static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
|
static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
|
||||||
const Strings & args, bool newestOnly)
|
const Strings & args, bool newestOnly)
|
||||||
{
|
{
|
||||||
|
@ -232,79 +317,42 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
|
||||||
selectors.emplace_back("*");
|
selectors.emplace_back("*");
|
||||||
|
|
||||||
DrvInfos elems;
|
DrvInfos elems;
|
||||||
set<unsigned int> done;
|
std::set<std::size_t> done;
|
||||||
|
|
||||||
for (auto & i : selectors) {
|
for (auto & selector : selectors) {
|
||||||
typedef list<std::pair<DrvInfo, unsigned int> > Matches;
|
std::vector<Match> matches;
|
||||||
Matches matches;
|
for (const auto & [index, drvInfo] : enumerate(allElems)) {
|
||||||
unsigned int n = 0;
|
const auto drvName = DrvName { drvInfo.queryName() };
|
||||||
for (DrvInfos::const_iterator j = allElems.begin();
|
if (selector.matches(drvName)) {
|
||||||
j != allElems.end(); ++j, ++n)
|
++selector.hits;
|
||||||
{
|
matches.emplace_back(drvInfo, index);
|
||||||
DrvName drvName(j->queryName());
|
|
||||||
if (i.matches(drvName)) {
|
|
||||||
i.hits++;
|
|
||||||
matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If `newestOnly', if a selector matches multiple derivations
|
|
||||||
with the same name, pick the one matching the current
|
|
||||||
system. If there are still multiple derivations, pick the
|
|
||||||
one with the highest priority. If there are still multiple
|
|
||||||
derivations, pick the one with the highest version.
|
|
||||||
Finally, if there are still multiple derivations,
|
|
||||||
arbitrarily pick the first one. */
|
|
||||||
if (newestOnly) {
|
if (newestOnly) {
|
||||||
|
matches = pickNewestOnly(state, std::move(matches));
|
||||||
/* Map from package names to derivations. */
|
|
||||||
typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
|
|
||||||
Newest newest;
|
|
||||||
StringSet multiple;
|
|
||||||
|
|
||||||
for (auto & j : matches) {
|
|
||||||
DrvName drvName(j.first.queryName());
|
|
||||||
long d = 1;
|
|
||||||
|
|
||||||
Newest::iterator k = newest.find(drvName.name);
|
|
||||||
|
|
||||||
if (k != newest.end()) {
|
|
||||||
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.queryName()).version);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d > 0) {
|
|
||||||
newest.erase(drvName.name);
|
|
||||||
newest.insert(Newest::value_type(drvName.name, j));
|
|
||||||
multiple.erase(j.first.queryName());
|
|
||||||
} else if (d == 0) {
|
|
||||||
multiple.insert(j.first.queryName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matches.clear();
|
|
||||||
for (auto & j : newest) {
|
|
||||||
if (multiple.find(j.second.first.queryName()) != multiple.end())
|
|
||||||
printInfo(
|
|
||||||
"warning: there are multiple derivations named '%1%'; using the first one",
|
|
||||||
j.second.first.queryName());
|
|
||||||
matches.push_back(j.second);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert only those elements in the final list that we
|
/* Insert only those elements in the final list that we
|
||||||
haven't inserted before. */
|
haven't inserted before. */
|
||||||
for (auto & j : matches)
|
for (auto & match : matches)
|
||||||
if (done.insert(j.second).second)
|
if (done.insert(match.index).second)
|
||||||
elems.push_back(j.first);
|
elems.push_back(match.drvInfo);
|
||||||
}
|
|
||||||
|
|
||||||
checkSelectorUse(selectors);
|
if (selector.hits == 0 && selector.fullName != "*") {
|
||||||
|
const auto prefixHits = searchByPrefix(allElems, selector.name);
|
||||||
|
|
||||||
|
if (prefixHits.empty()) {
|
||||||
|
throw Error("selector '%1%' matches no derivations", selector.fullName);
|
||||||
|
} else {
|
||||||
|
std::string suggestionMessage = ", maybe you meant:";
|
||||||
|
for (const auto & drvName : prefixHits) {
|
||||||
|
suggestionMessage += fmt("\n%s", drvName);
|
||||||
|
}
|
||||||
|
throw Error("selector '%1%' matches no derivations" + suggestionMessage, selector.fullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return elems;
|
return elems;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue