nix3-profile: allow using human-readable names to select packages
These names are parsed from the URL provided for that package
Based off of commit 257b768436a0e8ab7887f9b790c5b92a7fe51ef5
Upstream-PR: https://github.com/NixOS/nix/pull/8678
Co-authored-by: Felix Uhl <felix.uhl@outlook.com>
Change-Id: I76d5f9cfb11d3d2915b3dd1db21d7bb49e91f4fb
This commit is contained in:
parent
1425aa0b7c
commit
f88423813f
7
doc/manual/rl-next/nix-profile-names.md
Normal file
7
doc/manual/rl-next/nix-profile-names.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
synopsis: "`nix profile` now allows referring to elements by human-readable name"
|
||||
prs: 8678
|
||||
cls: 978
|
||||
---
|
||||
|
||||
[`nix profile`](@docroot@/command-ref/new-cli/nix3-profile.md) now uses names to refer to installed packages when running [`list`](@docroot@/command-ref/new-cli/nix3-profile-list.md), [`remove`](@docroot@/command-ref/new-cli/nix3-profile-remove.md) or [`upgrade`](@docroot@/command-ref/new-cli/nix3-profile-upgrade.md) as opposed to indices. Indices are deprecated and will be removed in a future version.
|
|
@ -1,6 +1,9 @@
|
|||
#include <set>
|
||||
|
||||
#include "cmd-profiles.hh"
|
||||
#include "built-path.hh"
|
||||
#include "builtins/buildenv.hh"
|
||||
#include "logging.hh"
|
||||
#include "names.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
|
@ -106,6 +109,8 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
|||
|
||||
if (pathExists(manifestPath)) {
|
||||
auto json = nlohmann::json::parse(readFile(manifestPath));
|
||||
// Keep track of already found names so we can prevent duplicates.
|
||||
std::set<std::string> foundNames;
|
||||
|
||||
auto version = json.value("version", 0);
|
||||
std::string sUrl;
|
||||
|
@ -139,6 +144,26 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
|||
e["attrPath"],
|
||||
e["outputs"].get<ExtendedOutputsSpec>()};
|
||||
}
|
||||
|
||||
std::string nameCandidate(element.identifier());
|
||||
|
||||
if (e.contains("name")) {
|
||||
nameCandidate = e["name"];
|
||||
} else if (element.source) {
|
||||
auto const url = parseURL(element.source->to_string());
|
||||
auto const name = getNameFromURL(url);
|
||||
if (name) {
|
||||
nameCandidate = *name;
|
||||
}
|
||||
}
|
||||
|
||||
auto finalName = nameCandidate;
|
||||
for (unsigned appendedIndex = 1; foundNames.contains(finalName); ++appendedIndex) {
|
||||
finalName = nameCandidate + std::to_string(appendedIndex);
|
||||
}
|
||||
element.name = finalName;
|
||||
foundNames.insert(element.name);
|
||||
|
||||
elements.emplace_back(std::move(element));
|
||||
}
|
||||
} else if (pathExists(profile + "/manifest.nix")) {
|
||||
|
@ -151,6 +176,7 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
|||
for (auto & drvInfo : drvInfos) {
|
||||
ProfileElement element;
|
||||
element.storePaths = {drvInfo.queryOutPath()};
|
||||
element.name = element.identifier();
|
||||
elements.emplace_back(std::move(element));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "flake/flakeref.hh"
|
||||
#include "get-drvs.hh"
|
||||
#include "types.hh"
|
||||
#include "url.hh"
|
||||
#include "url-name.hh"
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
@ -33,6 +35,7 @@ constexpr int DEFAULT_PRIORITY = 5;
|
|||
struct ProfileElement
|
||||
{
|
||||
StorePathSet storePaths;
|
||||
std::string name;
|
||||
std::optional<ProfileElementSource> source;
|
||||
bool active = true;
|
||||
int priority = DEFAULT_PRIORITY;
|
||||
|
|
|
@ -6,6 +6,7 @@ R""(
|
|||
|
||||
```console
|
||||
# nix profile list
|
||||
Name: gdb
|
||||
Index: 0
|
||||
Flake attribute: legacyPackages.x86_64-linux.gdb
|
||||
Original flake URL: flake:nixpkgs
|
||||
|
@ -13,6 +14,7 @@ R""(
|
|||
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
|
||||
|
@ -26,7 +28,7 @@ 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 with name blender-bin shown above.
|
||||
|
||||
# Description
|
||||
|
||||
|
@ -34,10 +36,13 @@ 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`.
|
||||
|
||||
* `Index`: An integer that can be used to unambiguously identify the package in invocations of `nix profile remove` and `nix profile upgrade`.
|
||||
(*Deprecated, will be removed in a future version in favor of `Name`.*)
|
||||
|
||||
* `Flake attribute`: The flake output attribute path that provides the
|
||||
package (e.g. `packages.x86_64-linux.hello`).
|
||||
|
||||
|
|
|
@ -2,10 +2,17 @@ R""(
|
|||
|
||||
# Examples
|
||||
|
||||
* Remove a package by position:
|
||||
* Remove a package by name:
|
||||
|
||||
```console
|
||||
# nix profile remove 3
|
||||
# nix profile remove hello
|
||||
```
|
||||
|
||||
* Remove a package by index
|
||||
*(deprecated, will be removed in a future version)*:
|
||||
|
||||
```console
|
||||
$ nix profile remove 3
|
||||
```
|
||||
|
||||
* Remove a package by attribute path:
|
||||
|
|
|
@ -9,18 +9,19 @@ R""(
|
|||
# nix profile upgrade '.*'
|
||||
```
|
||||
|
||||
* Upgrade a specific package:
|
||||
* Upgrade a specific package by name:
|
||||
|
||||
```console
|
||||
# nix profile upgrade hello
|
||||
```
|
||||
|
||||
```console
|
||||
# nix profile upgrade packages.x86_64-linux.hello
|
||||
```
|
||||
|
||||
* Upgrade a specific profile element by number:
|
||||
* Upgrade a specific package by index:
|
||||
|
||||
```console
|
||||
# nix profile list
|
||||
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify …
|
||||
|
||||
# nix profile upgrade 0
|
||||
```
|
||||
|
||||
|
|
|
@ -189,13 +189,24 @@ public:
|
|||
{
|
||||
std::vector<Matcher> res;
|
||||
|
||||
auto anyIndexMatchers = false;
|
||||
|
||||
for (auto & s : _matchers) {
|
||||
if (auto n = string2Int<size_t>(s))
|
||||
if (auto n = string2Int<size_t>(s)) {
|
||||
res.push_back(*n);
|
||||
else if (store->isStorePath(s))
|
||||
anyIndexMatchers = true;
|
||||
} else if (store->isStorePath(s)) {
|
||||
res.push_back(s);
|
||||
else
|
||||
} else {
|
||||
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
|
||||
}
|
||||
}
|
||||
|
||||
if (anyIndexMatchers) {
|
||||
warn(
|
||||
"Indices are deprecated and be removed in a future version!\n"
|
||||
" Refer to packages by their `Name` printed by `nix profile list`.\n"
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -206,12 +217,13 @@ public:
|
|||
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 (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,62 +306,100 @@ 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 (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);
|
||||
if (!matches(*store, element, i, matchers)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
matchedCount += 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
upgradedCount++;
|
||||
|
||||
Activity act(
|
||||
*logger,
|
||||
lvlChatty,
|
||||
actUnknown,
|
||||
fmt("checking '%s' for updates", element.source->attrPath),
|
||||
Logger::Fields{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);
|
||||
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn("Found some packages but none of them could be upgraded");
|
||||
}
|
||||
warn ("Use 'nix profile list' to see the current profile.");
|
||||
}
|
||||
|
@ -394,10 +444,18 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
|
|||
} else {
|
||||
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,
|
||||
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
|
||||
if (i) {
|
||||
logger->cout("");
|
||||
}
|
||||
logger->cout(
|
||||
"Name: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
|
||||
element.name,
|
||||
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL
|
||||
);
|
||||
logger->cout(
|
||||
"Index: " ANSI_BOLD "%s" ANSI_NORMAL "%S",
|
||||
i
|
||||
);
|
||||
if (element.source) {
|
||||
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
|
||||
logger->cout("Original flake URL: %s", element.source->originalRef.to_string());
|
||||
|
|
|
@ -47,7 +47,7 @@ cp ./config.nix $flake1Dir/
|
|||
|
||||
# Test upgrading from nix-env.
|
||||
nix-env -f ./user-envs.nix -i foo-1.0
|
||||
nix profile list | grep -A2 'Index:.*0' | grep 'Store paths:.*foo-1.0'
|
||||
nix profile list | grep -A2 'Name:.*foo' | grep 'Store paths:.*foo-1.0'
|
||||
nix profile install $flake1Dir -L
|
||||
nix profile list | grep -A4 'Index:.*1' | grep 'Locked flake URL:.*narHash'
|
||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
|
||||
|
@ -81,7 +81,7 @@ nix profile rollback
|
|||
|
||||
# Test uninstall.
|
||||
[ -e $TEST_HOME/.nix-profile/bin/foo ]
|
||||
nix profile remove 0
|
||||
nix profile remove "foo"
|
||||
(! [ -e $TEST_HOME/.nix-profile/bin/foo ])
|
||||
nix profile history | grep 'foo: 1.0 -> ∅'
|
||||
nix profile diff-closures | grep 'Version 3 -> 4'
|
||||
|
@ -93,6 +93,13 @@ nix profile remove 1
|
|||
nix profile install $(nix-build --no-out-link ./simple.nix)
|
||||
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
|
||||
|
||||
# Test packages with same name from different sources
|
||||
mkdir $TEST_ROOT/simple-too
|
||||
cp ./simple.nix ./config.nix simple.builder.sh $TEST_ROOT/simple-too
|
||||
nix profile install --file $TEST_ROOT/simple-too/simple.nix ''
|
||||
nix profile list | grep -A4 'Name:.*simple' | grep 'Name:.*simple1'
|
||||
nix profile remove simple1
|
||||
|
||||
# Test wipe-history.
|
||||
nix profile wipe-history
|
||||
[[ $(nix profile history | grep Version | wc -l) -eq 1 ]]
|
||||
|
@ -104,7 +111,7 @@ nix profile upgrade 0
|
|||
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 3.0, 3.0-man"
|
||||
|
||||
# Test new install of CA package.
|
||||
nix profile remove 0
|
||||
nix profile remove flake1
|
||||
printf 4.0 > $flake1Dir/version
|
||||
printf Utrecht > $flake1Dir/who
|
||||
nix profile install $flake1Dir
|
||||
|
@ -112,19 +119,20 @@ nix profile install $flake1Dir
|
|||
[[ $(nix path-info --json $(realpath $TEST_HOME/.nix-profile/bin/hello) | jq -r .[].ca) =~ fixed:r:sha256: ]]
|
||||
|
||||
# Override the outputs.
|
||||
nix profile remove 0 1
|
||||
nix profile remove simple flake1
|
||||
nix profile install "$flake1Dir^*"
|
||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]]
|
||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||
[ -e $TEST_HOME/.nix-profile/include ]
|
||||
|
||||
printf Nix > $flake1Dir/who
|
||||
nix profile upgrade 0
|
||||
nix profile list
|
||||
nix profile upgrade flake1
|
||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]]
|
||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||
[ -e $TEST_HOME/.nix-profile/include ]
|
||||
|
||||
nix profile remove 0
|
||||
nix profile remove flake1
|
||||
nix profile install "$flake1Dir^man"
|
||||
(! [ -e $TEST_HOME/.nix-profile/bin/hello ])
|
||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||
|
|
Loading…
Reference in a new issue