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 ## 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 - Optimized first level dependency calculation to read depenencies from
`nix-store` rather than walking a directory tree manually (issue #4). This `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 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. # See the nvd(1) man page for user-facing docs.
# Package install state (by pname): # Package install state (by pname):
# I: Installed (unchanged)
# A: Added to closure # A: Added to closure
# R: Removed from closure # R: Removed from closure
# U: Upgrade # U: Upgrade
@ -36,6 +37,7 @@
# R: Package manifest only available on the right; selected there. # R: Package manifest only available on the right; selected there.
import argparse import argparse
import fnmatch
import os import os
import os.path import os.path
import re import re
@ -46,24 +48,7 @@ from pathlib import Path
from subprocess import PIPE from subprocess import PIPE
from typing import Dict, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
parser = argparse.ArgumentParser( USE_COLOUR = False
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())
SGR_RESET = 0 SGR_RESET = 0
SGR_BOLD = 1 SGR_BOLD = 1
@ -85,6 +70,7 @@ def sgr(*args):
return "" return ""
return "\x1b[" + ";".join(str(arg) for arg in args) + "m" return "\x1b[" + ";".join(str(arg) for arg in args) + "m"
INST_INSTALLED = "I"
INST_ADDED = sgr(SGR_FG + SGR_BRIGHT + SGR_GREEN) + "A" INST_ADDED = sgr(SGR_FG + SGR_BRIGHT + SGR_GREEN) + "A"
INST_REMOVED = sgr(SGR_FG + SGR_BRIGHT + SGR_RED) + "R" INST_REMOVED = sgr(SGR_FG + SGR_BRIGHT + SGR_RED) + "R"
INST_UPGRADED = sgr(SGR_FG + SGR_BRIGHT + SGR_CYAN) + "U" INST_UPGRADED = sgr(SGR_FG + SGR_BRIGHT + SGR_CYAN) + "U"
@ -257,6 +243,9 @@ class PackageManifest:
def contains_pname(self, pname: str) -> bool: def contains_pname(self, pname: str) -> bool:
return pname in self._packages_by_pname return pname in self._packages_by_pname
def all_pnames(self):
return self._packages_by_pname.keys()
class PackageManifestPair: class PackageManifestPair:
def __init__( def __init__(
self, self,
@ -378,6 +367,10 @@ def print_package_list(
"Expect either one versions map, or a fixed install state." "Expect either one versions map, or a fixed install state."
have_single_version = right_versions_map is None have_single_version = right_versions_map is None
if not pnames:
print("No packages to display.")
return
count = 1 count = 1
count_width = len(str(len(pnames))) count_width = len(str(len(pnames)))
count_format_str = "#{:0" + str(count_width) + "d}" count_format_str = "#{:0" + str(count_width) + "d}"
@ -430,9 +423,51 @@ def print_package_list(
)) ))
count += 1 count += 1
def main(): def run_list(*, root, only_selected, name_patterns):
left_path = Path(ARGS.root1) path = Path(root)
right_path = Path(ARGS.root2)
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(): if not left_path.exists():
sys.stderr.write(f"Path does not exist: {left_path}\n") 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})." 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 # Importing this module is kinda broken because of the constant initializations
# at the top. # at the top.
if __name__ == "__main__": 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 .SH NAME
nvd \- Nix/NixOS package version diff tool nvd \- Nix/NixOS package version diff tool
.SH SYNOPSIS .SH SYNOPSIS
.P .P
.B nvd [ -h | --help ] .B nvd [ -h | --help ]
.P .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 .SH DESCRIPTION
.P .P
.B nvd .B nvd
is a tool for diffing the versions of all store paths in the closures of two Nix is a tool for working with Nix store paths and their closures, grouped by
store paths, neatly summarizing the differences. This is mainly intended for package name, with nice presentation for package versions. This is mainly
comparing two system configurations and is inspired by the output of intended for listing installed packages and comparing two system configurations,
and is inspired by the output of
.B emerge -pv .B emerge -pv
from Gentoo's Portage package manager. from Gentoo's Portage package manager.
.B nvd .B nvd
could also be likened to the output of Debian's could also be likened to the output of Debian's
.B apt list -i
and
.B apt upgrade -V, .B apt upgrade -V,
or any equivalent from other distributions' package managers. or any equivalent from other distributions' package managers.
.B nvd .B nvd
isn't limited to comparing system configurations though, and can work with any isn't limited to comparing system configurations though, and can work with any
two store paths. 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 .P
When given two system configurations,
.B nvd .B nvd
distinguishes between packages that are explicitly included in distinguishes between packages that are explicitly included in
.B environment.systemPackages, .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 forces colour to always be used, and passing
.B never .B never
forces colour to never be used. 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 .SH OUTPUT
.P .P
The output of If the
.B diff
command is requested, then the output of
.B nvd .B nvd
displays the given names of the two store paths being compared on the first two 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 lines. After this, for both the
by the type of change. Newly added or removed packages, packages with version .B diff
or count changes, and packages with selection state changes are displayed. Hash changes are 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 .B not
considered important, and will not cause a package to be displayed, however considered important, and will not cause a package to be displayed, however
changes in the number of times a package changes in the number of times a package
@ -132,6 +183,7 @@ removed, upgraded or downgraded. The possibilities here are:
.P .P
.RS .RS
.EX .EX
I: Installed (no change).
A: Package is newly added to the closure. A: Package is newly added to the closure.
R: All versions of the package are being removed the closure. R: All versions of the package are being removed the closure.
U: The package is being upgraded. U: The package is being upgraded.
@ -140,7 +192,15 @@ C: None of the above apply; unspecified changes.
.EE .EE
.RE .RE
.P .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 .B C
install state for a few different reasons, including the number of copies of an 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 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 .P
.RS .RS
.EX .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-29-link
>>> /nix/var/nix/profiles/system-30-link >>> /nix/var/nix/profiles/system-30-link
Version changes: Version changes:
@ -232,7 +292,7 @@ have been installed.
.P .P
.RS .RS
.EX .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-33-link
>>> /nix/var/nix/profiles/system/system-34-link >>> /nix/var/nix/profiles/system/system-34-link
Version changes: Version changes:
@ -263,7 +323,7 @@ one, despite this actually being a newer commit than the old one:
.P .P
.RS .RS
.EX .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-43-link
>>> /nix/var/nix/profiles/system/system-44-link >>> /nix/var/nix/profiles/system/system-44-link
Version changes: Version changes:
@ -278,7 +338,7 @@ to output no packages at all if no versions have changed:
.P .P
.RS .RS
.EX .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-23-link
>>> /nix/var/nix/profiles/system/system-24-link >>> /nix/var/nix/profiles/system/system-24-link
No version or selection state changes. No version or selection state changes.