Skip to content

Commit

Permalink
Replace index with human-readable name
Browse files Browse the repository at this point in the history
  • Loading branch information
iFreilicht committed Jul 16, 2023
1 parent 8d871e1 commit 0c4c043
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 98 deletions.
8 changes: 4 additions & 4 deletions src/nix/profile-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ R""(

```console
# nix profile list
Index: 0
Name: gdb
Flake attribute: legacyPackages.x86_64-linux.gdb
Original flake URL: flake:nixpkgs
Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1

Index: 1
Name: blender-bin
Flake attribute: packages.x86_64-linux.default
Original flake URL: flake:blender-bin
Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender
Expand All @@ -26,15 +26,15 @@ R""(
# nix build github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender#packages.x86_64-linux.default
```

will build the package with index 1 shown above.
will build the package `blender-bin` shown above.

# Description

This command shows what packages are currently installed in a
profile. For each installed package, it shows the following
information:

* `Index`: An integer that can be used to unambiguously identify the
* `Name`: A unique name used to unambiguously identify the
package in invocations of `nix profile remove` and `nix profile
upgrade`.

Expand Down
10 changes: 2 additions & 8 deletions src/nix/profile-remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ R""(

# Examples

* Remove a package by position:
* Remove a package by name:

```console
# nix profile remove 3
```

* Remove a package by attribute path:

```console
# nix profile remove packages.x86_64-linux.hello
# nix profile remove hello
```

* Remove all packages:
Expand Down
11 changes: 1 addition & 10 deletions src/nix/profile-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,12 @@ R""(
# nix profile upgrade '.*'
```

* Upgrade a specific package:
* Upgrade a specific package by name:

```console
# nix profile upgrade packages.x86_64-linux.hello
```

* Upgrade a specific profile element by number:

```console
# nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify …

# nix profile upgrade 0
```

# Description

This command upgrades a previously installed package in a Nix profile,
Expand Down
176 changes: 112 additions & 64 deletions src/nix/profile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const int defaultPriority = 5;
struct ProfileElement
{
StorePathSet storePaths;
std::string name;
std::optional<ProfileElementSource> source;
bool active = true;
int priority = defaultPriority;
Expand Down Expand Up @@ -116,10 +117,13 @@ struct ProfileManifest

if (pathExists(manifestPath)) {
auto json = nlohmann::json::parse(readFile(manifestPath));
std::set<std::string> foundNames;

auto version = json.value("version", 0);
std::string sUrl;
std::string sOriginalUrl;
std::regex matchPackagesPrefix("((legacyP)|(p))ackages(\\.[a-z0-9_-]+)?\\.");
std::regex matchFlakeUrlPrefix("(.*?):(.*\\/)?");
switch (version) {
case 1:
sUrl = "uri";
Expand Down Expand Up @@ -149,6 +153,34 @@ struct ProfileManifest
e["outputs"].get<ExtendedOutputsSpec>()
};
}

std::string nameCandidate;
if (e.contains("name")) {
nameCandidate = e["name"];
}
else {
/* Heuristically determine a decent name for the element. */
if (element.source) {
nameCandidate = std::regex_replace(static_cast<std::string>(e["attrPath"]), matchPackagesPrefix, "");
if (nameCandidate == "default") {
auto originalRef = element.source->originalRef;
nameCandidate = originalRef.input.getName();
if (nameCandidate == "source") {
nameCandidate = std::regex_replace(originalRef.to_string(), matchFlakeUrlPrefix , "");
}
}
}
else {
nameCandidate = element.identifier();
}
}
std::string finalName = nameCandidate;
for (int i = 1; foundNames.contains(finalName); ++i) {
finalName = nameCandidate + std::to_string(i);
}
element.name = finalName;
foundNames.insert(element.name);

elements.emplace_back(std::move(element));
}
}
Expand All @@ -163,6 +195,7 @@ struct ProfileManifest
for (auto & drvInfo : drvInfos) {
ProfileElement element;
element.storePaths = {drvInfo.queryOutPath()};
element.name = element.identifier();
elements.emplace_back(std::move(element));
}
}
Expand Down Expand Up @@ -447,16 +480,14 @@ class MixProfileElementMatchers : virtual Args
std::string pattern;
std::regex reg;
};
typedef std::variant<size_t, Path, RegexPattern> Matcher;
typedef std::variant<Path, RegexPattern> Matcher;

std::vector<Matcher> getMatchers(ref<Store> store)
{
std::vector<Matcher> res;

for (auto & s : _matchers) {
if (auto n = string2Int<size_t>(s))
res.push_back(*n);
else if (store->isStorePath(s))
if (store->isStorePath(s))
res.push_back(s);
else
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
Expand All @@ -465,16 +496,13 @@ class MixProfileElementMatchers : virtual Args
return res;
}

bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
bool matches(const Store & store, const ProfileElement & element, const std::vector<Matcher> & matchers)
{
for (auto & matcher : matchers) {
if (auto n = std::get_if<size_t>(&matcher)) {
if (*n == pos) return true;
} else if (auto path = std::get_if<Path>(&matcher)) {
if (auto path = std::get_if<Path>(&matcher)) {
if (element.storePaths.count(store.parseStorePath(*path))) return true;
} else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
if (element.source
&& std::regex_match(element.source->attrPath, regex->reg))
if (std::regex_match(element.name, regex->reg))
return true;
}
}
Expand Down Expand Up @@ -505,9 +533,8 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem

ProfileManifest newManifest;

for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
auto & element(oldManifest.elements[i]);
if (!matches(*store, element, i, matchers)) {
for (auto & element : oldManifest.elements) {
if (!matches(*store, element, matchers)) {
newManifest.elements.push_back(std::move(element));
} else {
notice("removing '%s'", element.identifier());
Expand All @@ -521,12 +548,10 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem

if (removedCount == 0) {
for (auto matcher: matchers) {
if (const size_t * index = std::get_if<size_t>(&matcher)){
warn("'%d' is not a valid index", *index);
} else if (const Path * path = std::get_if<Path>(&matcher)){
warn("'%s' does not match any paths", *path);
if (const Path * path = std::get_if<Path>(&matcher)){
warn("'%s' does not match any paths.", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
warn("'%s' does not match any packages", regex->pattern);
warn("'%s' does not match any packages.", regex->pattern);
}
}
warn ("Use 'nix profile list' to see the current profile.");
Expand Down Expand Up @@ -558,66 +583,89 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
Installables installables;
std::vector<size_t> indices;

auto matchedCount = 0;
auto upgradedCount = 0;

for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
if (element.source
&& !element.source->originalRef.input.isLocked()
&& matches(*store, element, i, matchers))
{
upgradedCount++;

Activity act(*logger, lvlChatty, actUnknown,
fmt("checking '%s' for updates", element.source->attrPath));

auto installable = make_ref<InstallableFlake>(
this,
getEvalState(),
FlakeRef(element.source->originalRef),
"",
element.source->outputs,
Strings{element.source->attrPath},
Strings{},
lockFlags);

auto derivedPaths = installable->toDerivedPaths();
if (derivedPaths.empty()) continue;
auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
// `InstallableFlake` should use `ExtraPathInfoFlake`.
assert(infop);
auto & info = *infop;
if (!matches(*store, element, matchers)) {
continue;
}

if (element.source->lockedRef == info.flake.lockedRef) continue;
matchedCount++;

printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->lockedRef, info.flake.lockedRef);
if (!element.source) {
warn(
"Found package '%s', but it was not installed from a flake, so it can't be checked for upgrades!",
element.identifier()
);
continue;
}
if (element.source->originalRef.input.isLocked()) {
warn(
"Found package '%s', but it was installed from a locked flake reference so it can't be upgraded!",
element.identifier()
);
continue;
}

element.source = ProfileElementSource {
.originalRef = installable->flakeRef,
.lockedRef = info.flake.lockedRef,
.attrPath = info.value.attrPath,
.outputs = installable->extendedOutputsSpec,
};
upgradedCount++;

Activity act(*logger, lvlChatty, actUnknown,
fmt("checking '%s' for updates", element.source->attrPath));

auto installable = make_ref<InstallableFlake>(
this,
getEvalState(),
FlakeRef(element.source->originalRef),
"",
element.source->outputs,
Strings{element.source->attrPath},
Strings{},
lockFlags);

auto derivedPaths = installable->toDerivedPaths();
if (derivedPaths.empty()) continue;
auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
// `InstallableFlake` should use `ExtraPathInfoFlake`.
assert(infop);
auto & info = *infop;

if (element.source->lockedRef == info.flake.lockedRef) continue;

printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->lockedRef, info.flake.lockedRef);

element.source = ProfileElementSource {
.originalRef = installable->flakeRef,
.lockedRef = info.flake.lockedRef,
.attrPath = info.value.attrPath,
.outputs = installable->extendedOutputsSpec,
};

installables.push_back(installable);
indices.push_back(i);
}
installables.push_back(installable);
indices.push_back(i);
}

if (upgradedCount == 0) {
for (auto & matcher : matchers) {
if (const size_t * index = std::get_if<size_t>(&matcher)){
warn("'%d' is not a valid index", *index);
} else if (const Path * path = std::get_if<Path>(&matcher)){
warn("'%s' does not match any paths", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
warn("'%s' does not match any packages", regex->pattern);
if (matchedCount == 0) {
for (auto & matcher : matchers) {
if (const Path * path = std::get_if<Path>(&matcher)){
warn("'%s' does not match any paths.", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
warn("'%s' does not match any packages.", regex->pattern);
}
}
} else {
warn("Found some packages but none of them could be upgraded.");
}
warn ("Use 'nix profile list' to see the current profile.");
}

if (matchedCount > upgradedCount) {
warn("Not all found packages will be upgraded!");
}

auto builtPaths = builtPathsPerInstallable(
Installable::build2(
getEvalStore(), store, Realise::Outputs, installables, bmNormal));
Expand Down Expand Up @@ -659,8 +707,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
if (i) logger->cout("");
logger->cout("Index: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
i,
logger->cout("Name: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
element.name,
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
if (element.source) {
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
Expand Down
Loading

0 comments on commit 0c4c043

Please sign in to comment.