Implement separate list command for listing matching packages (closes #5).

This commit is contained in:
Bryan Gardiner 2021-05-16 17:06:22 -07:00
parent 2c2fa1fbc9
commit 95c7ab7070
No known key found for this signature in database
GPG key ID: 53EFBCA063E6183C
3 changed files with 196 additions and 36 deletions

View file

@ -2,6 +2,10 @@
## 0.1.0
- Add a mandatory action argument to the CLI. The existing diff functionality
is under the `diff` command. A new `list` command is now implemented as well
(issue #5).
- Optimized first level dependency calculation to read depenencies from
`nix-store` rather than walking a directory tree manually (issue #4). This
also fixes nvd's support for things other than simple `buildEnv`s, e.g. file

138
src/nvd
View file

@ -19,6 +19,7 @@
# See the nvd(1) man page for user-facing docs.
# Package install state (by pname):
# I: Installed (unchanged)
# A: Added to closure
# R: Removed from closure
# U: Upgrade
@ -36,6 +37,7 @@
# R: Package manifest only available on the right; selected there.
import argparse
import fnmatch
import os
import os.path
import re
@ -46,24 +48,7 @@ from pathlib import Path
from subprocess import PIPE
from typing import Dict, List, Optional, Tuple, Union
parser = argparse.ArgumentParser(
description="Nix/NixOS package version diff tool",
epilog="See the nvd(1) manual page for more information.",
)
parser.add_argument(
"--color",
default="auto",
help="Controls use of colour; one of 'auto', 'never', 'always'.")
parser.add_argument(
"root1",
help="The first Nix store ptah to compare.")
parser.add_argument(
"root2",
help="The second Nix store path to compare.")
ARGS = parser.parse_args()
USE_COLOUR = ARGS.color == "always" or (ARGS.color == "auto" and sys.stdout.isatty())
USE_COLOUR = False
SGR_RESET = 0
SGR_BOLD = 1
@ -85,6 +70,7 @@ def sgr(*args):
return ""
return "\x1b[" + ";".join(str(arg) for arg in args) + "m"
INST_INSTALLED = "I"
INST_ADDED = sgr(SGR_FG + SGR_BRIGHT + SGR_GREEN) + "A"
INST_REMOVED = sgr(SGR_FG + SGR_BRIGHT + SGR_RED) + "R"
INST_UPGRADED = sgr(SGR_FG + SGR_BRIGHT + SGR_CYAN) + "U"
@ -257,6 +243,9 @@ class PackageManifest:
def contains_pname(self, pname: str) -> bool:
return pname in self._packages_by_pname
def all_pnames(self):
return self._packages_by_pname.keys()
class PackageManifestPair:
def __init__(
self,
@ -378,6 +367,10 @@ def print_package_list(
"Expect either one versions map, or a fixed install state."
have_single_version = right_versions_map is None
if not pnames:
print("No packages to display.")
return
count = 1
count_width = len(str(len(pnames)))
count_format_str = "#{:0" + str(count_width) + "d}"
@ -430,9 +423,51 @@ def print_package_list(
))
count += 1
def main():
left_path = Path(ARGS.root1)
right_path = Path(ARGS.root2)
def run_list(*, root, only_selected, name_patterns):
path = Path(root)
if not path.exists():
sys.stderr.write(f"Path does not exist: {path}\n")
sys.exit(1)
path = path.resolve()
if (path / "sw").is_dir():
manifest = PackageManifest.parse_tree((path / "sw").resolve())
else:
manifest = PackageManifest.parse_tree(path / "sw")
closure_paths: List[str] = subprocess.run(
["nix-store", "-qR", str(path)],
stdout=PIPE,
text=True,
).stdout.rstrip("\n").split("\n")
closure_map: Dict[str, List[str]] = closure_paths_to_map(closure_paths)
if only_selected:
target_pnames = list(manifest.all_pnames())
else:
target_pnames = list(closure_map.keys())
target_pnames = [
pname
for pname in target_pnames
if all(fnmatch.fnmatch(pname, pattern) for pattern in name_patterns)
]
target_pnames.sort()
print_package_list(
pnames=target_pnames,
manifest_pair=PackageManifestPair(manifest, manifest),
left_versions_map=closure_map,
right_versions_map=None,
fixed_install_state=INST_INSTALLED,
)
def run_diff(*, root1, root2):
left_path = Path(root1)
right_path = Path(root2)
if not left_path.exists():
sys.stderr.write(f"Path does not exist: {left_path}\n")
@ -547,6 +582,67 @@ def main():
f"delta {right_only_paths_count - left_only_paths_count:+d})."
)
def parse_args():
parser = argparse.ArgumentParser(
description="Nix/NixOS package version diff tool",
epilog="See the nvd(1) manual page for more information.",
)
parser.add_argument(
"--color",
default="auto",
help="Controls use of colour; one of 'auto', 'never', 'always'.")
subparsers = parser.add_subparsers(dest="action")
diff_parser = subparsers.add_parser(
"diff",
help="Diff two Nix store paths and their closures.")
diff_parser.add_argument(
"root1",
help="The first Nix store path to compare.")
diff_parser.add_argument(
"root2",
help="The second Nix store path to compare.")
list_parser = subparsers.add_parser(
"list",
help="List packages in the closure of a Nix store path.")
list_parser.add_argument(
"-r", "--root",
default="/run/current-system",
help="The The Nix store path to work with.")
list_parser.add_argument(
"-s", "--selected",
dest="only_selected",
action="store_true",
help="Only show selected packages (direct dependencies).")
list_parser.add_argument(
nargs="*",
default=[],
dest="name_patterns",
help="Patterns (globs) that must all match package names.")
return parser.parse_args()
def main():
global USE_COLOUR
args = parse_args()
action = args.action
USE_COLOUR = args.color == "always" or (args.color == "auto" and sys.stdout.isatty())
del args.action
del args.color
if action is None:
print("nvd: Subcommand required, see 'nvd --help'.")
sys.exit(1)
{
"list": run_list,
"diff": run_diff,
}[action](**vars(args))
# Importing this module is kinda broken because of the constant initializations
# at the top.
if __name__ == "__main__":

View file

@ -1,28 +1,52 @@
.TH nvd 1 2021-04-09 nvd "User Commands"
.TH nvd 1 2021-05-16 nvd "User Commands"
.SH NAME
nvd \- Nix/NixOS package version diff tool
.SH SYNOPSIS
.P
.B nvd [ -h | --help ]
.P
.B nvd [ --color=(auto|always|never) ] root1 root2
.B nvd [ --color=(auto|always|never) ] diff
.I root1
.I root2
.P
.B nvd [ --color=(auto|always|never) ] list
.RS
.B [ -r|--root
.I store-path
.B ]
.br
.B [ -s|--selected ]
.br
.B [
.I name-pattern
.B ]*
.RE
.SH DESCRIPTION
.P
.B nvd
is a tool for diffing the versions of all store paths in the closures of two Nix
store paths, neatly summarizing the differences. This is mainly intended for
comparing two system configurations and is inspired by the output of
is a tool for working with Nix store paths and their closures, grouped by
package name, with nice presentation for package versions. This is mainly
intended for listing installed packages and comparing two system configurations,
and is inspired by the output of
.B emerge -pv
from Gentoo's Portage package manager.
.B nvd
could also be likened to the output of Debian's
.B apt list -i
and
.B apt upgrade -V,
or any equivalent from other distributions' package managers.
.B nvd
isn't limited to comparing system configurations though, and can work with any
two store paths.
.P The
.B diff
command compares the packages and package versions included in two closures.
The
.B list
command displays the list of packages included in a closure, optionally filtered
to some criteria.
.P
When given two system configurations,
.B nvd
distinguishes between packages that are explicitly included in
.B environment.systemPackages,
@ -88,14 +112,41 @@ detects whether stdout is a terminal and only uses colour if it is. Passing
forces colour to always be used, and passing
.B never
forces colour to never be used.
.TP
-r|--root <store-path>
For the
.B list
command, this is the root store path to use, or a link to the store path to use.
.TP
-s|--selected
For the
.B list
command, only print out selected packages (direct dependencies).
.TP
<name-pattern>
For the
.B list
command, only print out names matching all of the given patterns. The character
.B ?
matches exactly one of any character, and the character
.B *
matches zero or more of any character.
.SH OUTPUT
.P
The output of
If the
.B diff
command is requested, then the output of
.B nvd
displays the given names of the two store paths being compared on the first two
lines, then changed packages on subsequent lines, one line per package, grouped
by the type of change. Newly added or removed packages, packages with version
or count changes, and packages with selection state changes are displayed. Hash changes are
lines. After this, for both the
.B diff
and
.B list
commands, package names, versions, and other information is printed out, with
one line per package name, in lexicographic order. When producing a diff,
packages are grouped by the type of change: newly added or removed packages,
packages with version or count changes, and packages with selection state
changes. Hash changes are
.B not
considered important, and will not cause a package to be displayed, however
changes in the number of times a package
@ -132,6 +183,7 @@ removed, upgraded or downgraded. The possibilities here are:
.P
.RS
.EX
I: Installed (no change).
A: Package is newly added to the closure.
R: All versions of the package are being removed the closure.
U: The package is being upgraded.
@ -140,7 +192,15 @@ C: None of the above apply; unspecified changes.
.EE
.RE
.P
Packages can be displayed with
The
.B list
command displays only the
.B I
state, and the
.B diff
command can display all states but the
.B I
state. Packages can be displayed with
.B C
install state for a few different reasons, including the number of copies of an
existing version in the closure changing, or due to the selection state changing
@ -200,7 +260,7 @@ plus additional versions of some existing packages:
.P
.RS
.EX
$ nvd /nix/var/nix/profiles/system-{29,30}-link
$ nvd diff /nix/var/nix/profiles/system-{29,30}-link
<<< /nix/var/nix/profiles/system-29-link
>>> /nix/var/nix/profiles/system-30-link
Version changes:
@ -232,7 +292,7 @@ have been installed.
.P
.RS
.EX
$ nvd /nix/var/nix/profiles/system-{33,34}-link
$ nvd diff /nix/var/nix/profiles/system-{33,34}-link
<<< /nix/var/nix/profiles/system/system-33-link
>>> /nix/var/nix/profiles/system/system-34-link
Version changes:
@ -263,7 +323,7 @@ one, despite this actually being a newer commit than the old one:
.P
.RS
.EX
$ nvd /nix/var/nix/profiles/system-{43,44}-link
$ nvd diff /nix/var/nix/profiles/system-{43,44}-link
<<< /nix/var/nix/profiles/system/system-43-link
>>> /nix/var/nix/profiles/system/system-44-link
Version changes:
@ -278,7 +338,7 @@ to output no packages at all if no versions have changed:
.P
.RS
.EX
$ nvd /nix/var/nix/profiles/system-{23,24}-link
$ nvd diff /nix/var/nix/profiles/system-{23,24}-link
<<< /nix/var/nix/profiles/system/system-23-link
>>> /nix/var/nix/profiles/system/system-24-link
No version or selection state changes.