Skip to content

Commit

Permalink
Enable using human-readable name in nix profile
Browse files Browse the repository at this point in the history
  • Loading branch information
iFreilicht committed Nov 1, 2023
1 parent 6132ffd commit 34c4e74
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 68 deletions.
2 changes: 2 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@

- The flake-specific flags `--recreate-lock-file` and `--update-input` have been removed from all commands operating on installables.
They are superceded by `nix flake update`.

- [`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.
64 changes: 64 additions & 0 deletions src/libutil/tests/url-name.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "url-name.hh"
#include <gtest/gtest.h>

namespace nix {

/* ----------- tests for url-name.hh --------------------------------------------------*/

TEST(getNameFromURL, getsNameFromURL) {
ASSERT_EQ(getNameFromURL(parseURL("path:/home/user/project")), "project");
ASSERT_EQ(getNameFromURL(parseURL("path:~/repos/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("path:.#nonStandardAttr.mylaptop")), "nonStandardAttr.mylaptop");
ASSERT_EQ(getNameFromURL(parseURL("path:./repos/myflake#nonStandardAttr.mylaptop")), "nonStandardAttr.mylaptop");

ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix#packages.x86_64-linux.default")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix#")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("github:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");
ASSERT_EQ(getNameFromURL(parseURL("github:edolstra/nix-warez?rev=1234&dir=blender&ref=master")), "blender");

ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nixpkgs#hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix#packages.x86_64-linux.default")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix#")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("gitlab:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");

ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nixpkgs#hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix#packages.x86_64-linux.default")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix#")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix")), "nix");
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");

ASSERT_EQ(getNameFromURL(parseURL("git://github.com/edolstra/dwarffs")), "dwarffs");
ASSERT_EQ(getNameFromURL(parseURL("git://github.com/edolstra/nix-warez?dir=blender")), "blender");
ASSERT_EQ(getNameFromURL(parseURL("git+file:///home/user/project")), "project");
ASSERT_EQ(getNameFromURL(parseURL("git+file:///home/user/project?ref=fa1e2d23a22")), "project");
ASSERT_EQ(getNameFromURL(parseURL("git+ssh://[email protected]/someuser/my-repo#")), "my-repo");
ASSERT_EQ(getNameFromURL(parseURL("git+git://github.com/someuser/my-repo?rev=v1.2.3")), "my-repo");
ASSERT_EQ(getNameFromURL(parseURL("git+ssh:///home/user/project?dir=subproject&rev=v2.4")), "subproject");
ASSERT_EQ(getNameFromURL(parseURL("git+http://not-even-real#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("git+https://not-even-real#packages.aarch64-darwin.hello")), "hello");

ASSERT_EQ(getNameFromURL(parseURL("tarball+http://github.com/NixOS/nix/archive/refs/tags/2.18.1#packages.x86_64-linux.jq")), "jq");
ASSERT_EQ(getNameFromURL(parseURL("tarball+https://github.com/NixOS/nix/archive/refs/tags/2.18.1#packages.x86_64-linux.hg")), "hg");
ASSERT_EQ(getNameFromURL(parseURL("tarball+file:///home/user/Downloads/nixpkgs-2.18.1#packages.aarch64-darwin.ripgrep")), "ripgrep");

ASSERT_EQ(getNameFromURL(parseURL("https://github.com/NixOS/nix/archive/refs/tags/2.18.1.tar.gz#packages.x86_64-linux.pv")), "pv");
ASSERT_EQ(getNameFromURL(parseURL("http://github.com/NixOS/nix/archive/refs/tags/2.18.1.tar.gz#packages.x86_64-linux.pv")), "pv");

ASSERT_EQ(getNameFromURL(parseURL("file:///home/user/project?ref=fa1e2d23a22")), "project");
ASSERT_EQ(getNameFromURL(parseURL("file+file:///home/user/project?ref=fa1e2d23a22")), "project");
ASSERT_EQ(getNameFromURL(parseURL("file+http://not-even-real#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("file+http://gitfantasy.com/org/user/notaflake")), "notaflake");
ASSERT_EQ(getNameFromURL(parseURL("file+https://not-even-real#packages.aarch64-darwin.hello")), "hello");

ASSERT_EQ(getNameFromURL(parseURL("https://www.github.com/")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("path:.")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("file:.#")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("path:.#packages.x86_64-linux.default")), std::nullopt);
}
}
46 changes: 46 additions & 0 deletions src/libutil/url-name.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "url-name.hh"
#include <regex>
#include <iostream>

namespace nix {

static const std::string attributeNamePattern("[a-z0-9_-]+");
static const std::regex lastAttributeRegex("(?:" + attributeNamePattern + "\\.)*(?!default)(" + attributeNamePattern +")");
static const std::string pathSegmentPattern("[a-zA-Z0-9_-]+");
static const std::regex lastPathSegmentRegex(".*/(" + pathSegmentPattern +")");
static const std::regex secondPathSegmentRegex("(?:" + pathSegmentPattern + ")/(" + pathSegmentPattern +")(?:/.*)?");
static const std::regex gitProviderRegex("github|gitlab|sourcehut");
static const std::regex gitSchemeRegex("git($|\\+.*)");

std::optional<std::string> getNameFromURL(ParsedURL url) {
std::smatch match;

/* If there is a dir= argument, use its value */
if (url.query.count("dir") > 0)
return url.query.at("dir");

/* If the fragment isn't a "default" and contains two attribute elements, use the last one */
if (std::regex_match(url.fragment, match, lastAttributeRegex))
return match.str(1);

/* If this is a github/gitlab/sourcehut flake, use the repo name */
if (std::regex_match(url.scheme, gitProviderRegex) && std::regex_match(url.path, match, secondPathSegmentRegex))
return match.str(1);

/* If it is a regular git flake, use the directory name */
if (std::regex_match(url.scheme, gitSchemeRegex) && std::regex_match(url.path, match, lastPathSegmentRegex))
return match.str(1);

/* If everything failed but there is a non-default fragment, use it in full */
if (!url.fragment.empty() && !hasSuffix(url.fragment, "default"))
return url.fragment;

/* If there is no fragment, take the last element of the path */
if (std::regex_match(url.path, match, lastPathSegmentRegex))
return match.str(1);

/* If even that didn't work, the URL does not contain enough info to determine a useful name */
return {};
}

}
20 changes: 20 additions & 0 deletions src/libutil/url-name.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "url.hh"
#include "url-parts.hh"
#include "util.hh"
#include "split.hh"

namespace nix {

/**
* Try to extract a reasonably unique and meaningful, human-readable
* name of a flake output from a parsed URL.
* When nullopt is returned, the callsite should use information available
* to it outside of the URL to determine a useful name.
* This is a heuristic approach intended for user interfaces.
* @return nullopt if the extracted name is not useful to identify a
* flake output, for example because it is empty or "default".
* Otherwise returns the extracted name.
*/
std::optional<std::string> getNameFromURL(ParsedURL url);

}
10 changes: 8 additions & 2 deletions src/nix/profile-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ R""(

```console
# nix profile list
Name: gdb
Index: 0
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

Name: blender-bin
Index: 1
Flake attribute: packages.x86_64-linux.default
Original flake URL: flake:blender-bin
Expand All @@ -26,18 +28,22 @@ 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`.

* `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`).

Expand Down
9 changes: 5 additions & 4 deletions src/nix/profile-remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +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 attribute path:
* Remove a package by index
*(deprecated, will be removed in a future version)*:

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

* Remove all packages:
Expand Down
10 changes: 4 additions & 6 deletions src/nix/profile-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ R""(
# nix profile upgrade '.*'
```

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

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

* Upgrade a specific profile element by number:
* Upgrade a specific package by index
*(deprecated, will be removed in a future version)*:

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

# nix profile upgrade 0
```

Expand Down
Loading

0 comments on commit 34c4e74

Please sign in to comment.