Rename "package manifests" to "package sets".

There are two styles of coding that are used currently.  First, the
PackageManifest{,Pair} classes track an arbitrary list of store paths (not
necessarily a closure) as parsed Package and Version objects.  These are used to
track and compare the sets of selected packages.  Then for everything else like
version diffing, we just store raw nested data structures holding package names
and versions, e.g. dict[str, list[optional[str]]] for mapping package names to
lists of available versions (and then parse Versions on the fly to do
comparisons, which is inefficient).

This is the beginning of clean-up to make the latter use PackageSet, Package,
and Version as well.
This commit is contained in:
Bryan Gardiner 2024-08-18 18:23:17 -07:00
parent 9594ab31ef
commit 682570f054
No known key found for this signature in database
GPG key ID: 53EFBCA063E6183C

144
src/nvd
View file

@ -31,10 +31,10 @@
# -: Package is newly unselected. # -: Package is newly unselected.
# *: Package is selected; state unchanged. # *: Package is selected; state unchanged.
# .: Not in environment.systemPackages; dependency. # .: Not in environment.systemPackages; dependency.
# l: Package manifest only available on the left; not selected there. # l: Selected package set only available on the left; not selected there.
# L: Package manifest only available on the left; selected there. # L: Selected package set only available on the left; selected there.
# r: Package manifest only available on the right; not selected there. # r: Selected package set only available on the right; not selected there.
# R: Package manifest only available on the right; selected there. # R: Selected package set only available on the right; selected there.
import argparse import argparse
import fnmatch import fnmatch
@ -89,13 +89,13 @@ SEL_SELECTED = "*"
SEL_UNSELECTED = "." SEL_UNSELECTED = "."
SEL_NEWLY_SELECTED = "+" SEL_NEWLY_SELECTED = "+"
SEL_NEWLY_UNSELECTED = "-" SEL_NEWLY_UNSELECTED = "-"
# There's no way to reach these selection states currently, since manifests are # There's no way to reach these selection states currently, since selected sets
# always computed. # are always computed.
SEL_LEFT_ONLY_SELECTED = "L" SEL_LEFT_ONLY_SELECTED = "L"
SEL_LEFT_ONLY_UNSELECTED = "l" SEL_LEFT_ONLY_UNSELECTED = "l"
SEL_RIGHT_ONLY_SELECTED = "R" SEL_RIGHT_ONLY_SELECTED = "R"
SEL_RIGHT_ONLY_UNSELECTED = "r" SEL_RIGHT_ONLY_UNSELECTED = "r"
SEL_NO_MANIFESTS = "" SEL_NO_SELECTED_SETS = ""
NIX_STORE_PATH_REGEX = re.compile(r"^/nix/store/[a-z0-9]+-(.+?)(-([0-9].*?))?(\.drv)?$") NIX_STORE_PATH_REGEX = re.compile(r"^/nix/store/[a-z0-9]+-(.+?)(-([0-9].*?))?(\.drv)?$")
@ -222,7 +222,7 @@ class Package:
def store_path(self) -> StorePath: def store_path(self) -> StorePath:
return self._store_path return self._store_path
class PackageManifest: class PackageSet:
def __init__(self, packages: List[Package]): def __init__(self, packages: List[Package]):
assert isinstance(packages, List) assert isinstance(packages, List)
assert all(isinstance(package, Package) for package in packages) assert all(isinstance(package, Package) for package in packages)
@ -234,27 +234,31 @@ class PackageManifest:
self._packages_by_pname.setdefault(entry.pname(), []).append(entry) self._packages_by_pname.setdefault(entry.pname(), []).append(entry)
@staticmethod @staticmethod
def parse_tree(root: Path) -> "PackageManifest": def from_direct_dependencies(path: Path) -> "PackageSet":
direct_deps_str: str = subprocess.run( return PackageSet.from_nix_query(["--references", str(path)])
[make_nix_bin_path("nix-store"), "--query", "--references", str(root)],
@staticmethod
def from_nix_query(nix_query_args: List[str]) -> "PackageSet":
result_paths_str: str = subprocess.run(
[make_nix_bin_path("nix-store"), "--query"] + nix_query_args,
stdout=PIPE, stdout=PIPE,
text=True, text=True,
check=True, check=True,
).stdout.rstrip("\n") ).stdout.rstrip("\n")
direct_deps: List[str] = \ result_paths: List[str] = \
direct_deps_str.split("\n") if direct_deps_str else [] result_paths_str.split("\n") if result_paths_str else []
packages = [] packages = []
for dep_path in direct_deps: for result_path in result_paths:
pname, version = parse_pname_version(dep_path) pname, version = parse_pname_version(result_path)
packages.append(Package( packages.append(Package(
pname=pname, pname=pname,
version=Version(version), version=Version(version),
store_path=StorePath(dep_path), store_path=StorePath(result_path),
)) ))
return PackageManifest(packages) return PackageSet(packages)
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
@ -262,61 +266,61 @@ class PackageManifest:
def all_pnames(self): def all_pnames(self):
return self._packages_by_pname.keys() return self._packages_by_pname.keys()
class PackageManifestPair: class PackageSetPair:
def __init__( def __init__(
self, self,
left_manifest: Optional[PackageManifest], left_set: Optional[PackageSet],
right_manifest: Optional[PackageManifest] right_set: Optional[PackageSet]
): ):
assert left_manifest is None or isinstance(left_manifest, PackageManifest) assert left_set is None or isinstance(left_set, PackageSet)
assert right_manifest is None or isinstance(right_manifest, PackageManifest) assert right_set is None or isinstance(right_set, PackageSet)
self._left_manifest = left_manifest self._left_set = left_set
self._right_manifest = right_manifest self._right_set = right_set
def get_left_manifest(self) -> Optional[PackageManifest]: def get_left_set(self) -> Optional[PackageSet]:
return self._left_manifest return self._left_set
def get_right_manifest(self) -> Optional[PackageManifest]: def get_right_set(self) -> Optional[PackageSet]:
return self._right_manifest return self._right_set
def get_selection_state(self, pname: str) -> str: def get_selection_state(self, pname: str) -> str:
left_manifest = self._left_manifest left_set = self._left_set
right_manifest = self._right_manifest right_set = self._right_set
if left_manifest is not None and right_manifest is not None: if left_set is not None and right_set is not None:
in_left_manifest = left_manifest.contains_pname(pname) in_left_set = left_set.contains_pname(pname)
in_right_manifest = right_manifest.contains_pname(pname) in_right_set = right_set.contains_pname(pname)
selection_state_str = [ selection_state_str = [
SEL_UNSELECTED, SEL_UNSELECTED,
SEL_NEWLY_UNSELECTED, SEL_NEWLY_UNSELECTED,
SEL_NEWLY_SELECTED, SEL_NEWLY_SELECTED,
SEL_SELECTED, SEL_SELECTED,
][int(in_left_manifest) + 2 * int(in_right_manifest)] ][int(in_left_set) + 2 * int(in_right_set)]
elif left_manifest is not None: elif left_set is not None:
in_left_manifest = left_manifest.contains_pname(pname) in_left_set = left_set.contains_pname(pname)
selection_state_str = [ selection_state_str = [
SEL_LEFT_ONLY_UNSELECTED, SEL_LEFT_ONLY_UNSELECTED,
SEL_LEFT_ONLY_SELECTED, SEL_LEFT_ONLY_SELECTED,
][int(in_left_manifest)] ][int(in_left_set)]
elif right_manifest is not None: elif right_set is not None:
in_right_manifest = right_manifest.contains_pname(pname) in_right_set = right_set.contains_pname(pname)
selection_state_str = [ selection_state_str = [
SEL_RIGHT_ONLY_UNSELECTED, SEL_RIGHT_ONLY_UNSELECTED,
SEL_RIGHT_ONLY_SELECTED, SEL_RIGHT_ONLY_SELECTED,
][int(in_right_manifest)] ][int(in_right_set)]
else: else:
selection_state_str = SEL_NO_MANIFESTS selection_state_str = SEL_NO_SELECTED_SETS
return selection_state_str return selection_state_str
def is_selection_state_changed(self, pname: str) -> str: def is_selection_state_changed(self, pname: str) -> str:
in_left_manifest = \ in_left_set = \
self._left_manifest is not None and \ self._left_set is not None and \
self._left_manifest.contains_pname(pname) self._left_set.contains_pname(pname)
in_right_manifest = \ in_right_set = \
self._right_manifest is not None and \ self._right_set is not None and \
self._right_manifest.contains_pname(pname) self._right_set.contains_pname(pname)
return in_left_manifest != in_right_manifest return in_left_set != in_right_set
def parse_pname_version(path: str) -> Tuple[str, Optional[str]]: def parse_pname_version(path: str) -> Tuple[str, Optional[str]]:
base_path = str(StorePath(path).to_base_path().path()) base_path = str(StorePath(path).to_base_path().path())
@ -387,7 +391,7 @@ def render_versions(
def print_package_list( def print_package_list(
*, *,
pnames: List[str], pnames: List[str],
manifest_pair: PackageManifestPair, selected_sets: PackageSetPair,
left_versions_map: Dict[str, List[str]], left_versions_map: Dict[str, List[str]],
right_versions_map: Optional[Dict[str, List[str]]] = None, right_versions_map: Optional[Dict[str, List[str]]] = None,
fixed_install_state: Optional[str] = None, fixed_install_state: Optional[str] = None,
@ -425,11 +429,11 @@ def print_package_list(
install_state_str = INST_CHANGED install_state_str = INST_CHANGED
selected_at_all = ( selected_at_all = (
manifest_pair.get_left_manifest() is not None selected_sets.get_left_set() is not None
and manifest_pair.get_left_manifest().contains_pname(pname) and selected_sets.get_left_set().contains_pname(pname)
) or ( ) or (
manifest_pair.get_right_manifest() is not None selected_sets.get_right_set() is not None
and manifest_pair.get_right_manifest().contains_pname(pname) and selected_sets.get_right_set().contains_pname(pname)
) )
if selected_at_all: if selected_at_all:
bold_if_selected_sgr = sgr(SGR_BOLD) bold_if_selected_sgr = sgr(SGR_BOLD)
@ -449,7 +453,7 @@ def print_package_list(
status_str = "[{}{}]".format( status_str = "[{}{}]".format(
bold_if_selected_sgr + install_state_str + sgr(SGR_RESET), bold_if_selected_sgr + install_state_str + sgr(SGR_RESET),
pname_sgr + manifest_pair.get_selection_state(pname) + sgr(SGR_RESET), pname_sgr + selected_sets.get_selection_state(pname) + sgr(SGR_RESET),
) )
count_str = count_format_str.format(count) count_str = count_format_str.format(count)
pname_str = pname_format_str.format(pname) pname_str = pname_format_str.format(pname)
@ -612,9 +616,9 @@ def run_list(*, root, only_selected, name_patterns):
path = path.resolve() path = path.resolve()
if (path / "sw").is_dir(): if (path / "sw").is_dir():
manifest = PackageManifest.parse_tree((path / "sw").resolve()) selected_set = PackageSet.from_direct_dependencies((path / "sw").resolve())
else: else:
manifest = PackageManifest.parse_tree(path / "sw") selected_set = PackageSet.from_direct_dependencies(path / "sw")
closure_paths: List[str] = subprocess.run( closure_paths: List[str] = subprocess.run(
[make_nix_bin_path("nix-store"), "-qR", str(path)], [make_nix_bin_path("nix-store"), "-qR", str(path)],
@ -626,7 +630,7 @@ def run_list(*, root, only_selected, name_patterns):
closure_map: Dict[str, List[str]] = closure_paths_to_map(closure_paths) closure_map: Dict[str, List[str]] = closure_paths_to_map(closure_paths)
if only_selected: if only_selected:
target_pnames = list(manifest.all_pnames()) target_pnames = list(selected_set.all_pnames())
else: else:
target_pnames = list(closure_map.keys()) target_pnames = list(closure_map.keys())
@ -640,7 +644,7 @@ def run_list(*, root, only_selected, name_patterns):
print_package_list( print_package_list(
pnames=target_pnames, pnames=target_pnames,
manifest_pair=PackageManifestPair(manifest, manifest), selected_sets=PackageSetPair(selected_set, selected_set),
left_versions_map=closure_map, left_versions_map=closure_map,
right_versions_map=None, right_versions_map=None,
fixed_install_state=INST_INSTALLED, fixed_install_state=INST_INSTALLED,
@ -669,17 +673,17 @@ def run_diff(
print(f"<<< {left_path}") print(f"<<< {left_path}")
print(f">>> {right_path}") print(f">>> {right_path}")
left_manifest: Optional[PackageManifest] = None left_selected_set: Optional[PackageSet] = None
right_manifest: Optional[PackageManifest] = None right_selected_set: Optional[PackageSet] = None
if (left_resolved / "sw").is_dir() and (right_resolved / "sw").is_dir(): if (left_resolved / "sw").is_dir() and (right_resolved / "sw").is_dir():
left_manifest = PackageManifest.parse_tree((left_resolved / "sw").resolve()) left_selected_set = PackageSet.from_direct_dependencies((left_resolved / "sw").resolve())
right_manifest = PackageManifest.parse_tree((right_resolved / "sw").resolve()) right_selected_set = PackageSet.from_direct_dependencies((right_resolved / "sw").resolve())
else: else:
left_manifest = PackageManifest.parse_tree(left_resolved) left_selected_set = PackageSet.from_direct_dependencies(left_resolved)
right_manifest = PackageManifest.parse_tree(right_resolved) right_selected_set = PackageSet.from_direct_dependencies(right_resolved)
manifest_pair = PackageManifestPair(left_manifest, right_manifest) selected_sets = PackageSetPair(left_selected_set, right_selected_set)
left_closure_paths: List[str] = subprocess.run( left_closure_paths: List[str] = subprocess.run(
[make_nix_bin_path("nix-store"), "-qR", str(left_resolved)], [make_nix_bin_path("nix-store"), "-qR", str(left_resolved)],
@ -711,7 +715,7 @@ def run_diff(
for pname in common_package_names: for pname in common_package_names:
if left_closure_map[pname] != right_closure_map[pname]: if left_closure_map[pname] != right_closure_map[pname]:
package_names_with_changed_versions.append(pname) package_names_with_changed_versions.append(pname)
elif manifest_pair.is_selection_state_changed(pname): elif selected_sets.is_selection_state_changed(pname):
package_names_with_changed_selection_states.append(pname) package_names_with_changed_selection_states.append(pname)
any_changes_displayed = False any_changes_displayed = False
@ -722,7 +726,7 @@ def run_diff(
print("Version changes:") print("Version changes:")
print_package_list( print_package_list(
pnames=package_names_with_changed_versions, pnames=package_names_with_changed_versions,
manifest_pair=manifest_pair, selected_sets=selected_sets,
left_versions_map=left_closure_map, left_versions_map=left_closure_map,
right_versions_map=right_closure_map, right_versions_map=right_closure_map,
no_version_suffix_highlight=no_version_suffix_highlight, no_version_suffix_highlight=no_version_suffix_highlight,
@ -734,7 +738,7 @@ def run_diff(
print("Selection state changes:") print("Selection state changes:")
print_package_list( print_package_list(
pnames=package_names_with_changed_selection_states, pnames=package_names_with_changed_selection_states,
manifest_pair=manifest_pair, selected_sets=selected_sets,
left_versions_map=left_closure_map, left_versions_map=left_closure_map,
fixed_install_state=INST_CHANGED, fixed_install_state=INST_CHANGED,
) )
@ -745,7 +749,7 @@ def run_diff(
print("Added packages:") print("Added packages:")
print_package_list( print_package_list(
pnames=right_only_package_names, pnames=right_only_package_names,
manifest_pair=manifest_pair, selected_sets=selected_sets,
left_versions_map=right_closure_map, # Yes, this is correct. left_versions_map=right_closure_map, # Yes, this is correct.
fixed_install_state=INST_ADDED, fixed_install_state=INST_ADDED,
) )
@ -756,7 +760,7 @@ def run_diff(
print("Removed packages:") print("Removed packages:")
print_package_list( print_package_list(
pnames=left_only_package_names, pnames=left_only_package_names,
manifest_pair=manifest_pair, selected_sets=selected_sets,
left_versions_map=left_closure_map, left_versions_map=left_closure_map,
fixed_install_state=INST_REMOVED, fixed_install_state=INST_REMOVED,
) )