diff --git a/doc/manual/rl-next/profile-flake-url.md b/doc/manual/rl-next/profile-flake-url.md new file mode 100644 index 000000000..e9b546650 --- /dev/null +++ b/doc/manual/rl-next/profile-flake-url.md @@ -0,0 +1,12 @@ +--- +synopsis: Removing and upgrading packages using flake URLs +prs: 10198 +--- + +It is now possible to remove and upgrade packages in `nix profile` using flake URLs. + +```console +$ nix profile install nixpkgs#firefox +$ nix profile upgrade nixpkgs#firefox +$ nix profile remove nixpkgs#firefox +``` diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 2c593729f..ec7602977 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -545,6 +545,43 @@ struct NameMatcher final : public Matcher } }; +std::regex defaultPackageFragmentPattern("(?:legacyPackages|packages)\\.(?:[^\\.]+)\\.default"); +std::regex packageFragmentPattern("(?:legacyPackages|packages)\\.(?:[^\\.]+)\\.(.+)"); +struct InstallableMatcher : public Matcher +{ + const FlakeRef flakeRef; + const std::string fragment; + + InstallableMatcher(const FlakeRef flakeRef, const std::string fragment) : flakeRef(flakeRef), fragment(fragment) + { } + + std::string getTitle() override + { + return fmt("Installable '%s#%s'", flakeRef.to_string(), fragment); + } + + bool matches(const std::string & name, const ProfileElement & element) override + { + if (element.source->originalRef != flakeRef) { + return false; + } + // Match "#packages.{system}.default" + if (fragment == "") { + return std::regex_match(element.source->attrPath, defaultPackageFragmentPattern); + } + // Match "#{name}" + if (element.source->attrPath == fragment) { + return true; + } + // Match "#packages.{system}.{name}" + std::smatch match; + if (std::regex_match(element.source->attrPath, match, packageFragmentPattern)) { + return match.str(1) == fragment; + } + return false; + } +}; + struct AllMatcher final : public Matcher { std::string getTitle() override @@ -592,6 +629,9 @@ public: throw Error("'nix profile' no longer supports indices ('%d')", *n); } else if (getStore()->isStorePath(arg)) { _matchers.push_back(make_ref(getStore()->parseStorePath(arg))); + } else if (std::regex_match(arg, std::regex(".*[#:].*"))) { + auto pair = parseFlakeRefWithFragment(arg); + _matchers.push_back(make_ref(pair.first, pair.second)); } else { _matchers.push_back(make_ref(arg)); } diff --git a/tests/functional/nix-profile.sh b/tests/functional/nix-profile.sh index b1cfef6b0..72f0689bd 100755 --- a/tests/functional/nix-profile.sh +++ b/tests/functional/nix-profile.sh @@ -110,9 +110,28 @@ warning: No packages to upgrade. Use 'nix profile list' to see the current profi EOF # Test removing all packages using regular expression. -nix profile remove --regex '.*' 2>&1 | grep "removed 2 packages, kept 0 packages" +assertStderr nix --offline profile remove --regex '.*' << EOF +removing 'path:$flake1Dir#packages.$system.default' +removing 'foo' +removed 2 packages, kept 0 packages +EOF nix profile rollback +# Test removing package using full url. +nix profile remove "path:$flake1Dir#packages.$system.default" +[[ ! -f $TEST_HOME/.nix-profile/bin/hello ]] +nix profile install $flake1Dir + +# Test removing package using shorthand flake url. +nix profile remove path:$flake1Dir +[[ ! -f $TEST_HOME/.nix-profile/bin/hello ]] +nix profile install $flake1Dir + +# Test removing package using shorthand package name. +nix profile remove path:$flake1Dir#default +[[ ! -f $TEST_HOME/.nix-profile/bin/hello ]] +nix profile install $flake1Dir + # Test 'history', 'diff-closures'. nix profile diff-closures