#!/usr/bin/env python3 """Read-only invariant checks for the active Decman configuration.""" from __future__ import annotations import os import runpy import subprocess import sys from collections import Counter from collections.abc import Callable from pathlib import Path from typing import cast ROOT = Path(__file__).resolve().parents[1] PACKAGES = ROOT / "packages" def lines(path: Path) -> set[str]: return { line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip() } def actual(command: str) -> set[str]: return set( subprocess.run( ["pacman", command], check=True, text=True, stdout=subprocess.PIPE ).stdout.split() ) def main() -> None: repo_files = list(PACKAGES.glob("**/*-repo.txt")) aur_files = list(PACKAGES.glob("**/*-aur.txt")) repo = [name for path in repo_files for name in lines(path)] aur = [name for path in aur_files for name in lines(path)] duplicate = {name for name, count in Counter(repo + aur).items() if count > 1} if duplicate: raise SystemExit(f"duplicate package ownership: {sorted(duplicate)}") installed_repo = actual("-Qqen") installed_aur = actual("-Qqem") os.chdir(ROOT) sys.path.insert(0, str(ROOT)) import decman runpy.run_path(str(ROOT / "source.py")) loaded_repo: set[str] = set() loaded_aur: set[str] = set() for module in decman.modules: native = getattr(module, "native_packages", None) foreign = getattr(module, "aur_packages", None) if callable(native): loaded_repo |= cast(Callable[[], set[str]], native)() if callable(foreign): loaded_aur |= cast(Callable[[], set[str]], foreign)() if loaded_repo != set(repo) or loaded_aur != set(aur): raise SystemExit("active profile does not compose the full laptop package set") expected_system = set().union( *(lines(path) for path in (ROOT / "manifests").glob("**/*-system.txt")) ) expected_user = set().union( *(lines(path) for path in (ROOT / "manifests").glob("**/*-user.txt")) ) loaded_system: set[str] = set() loaded_user: set[str] = set() for module in decman.modules: system = getattr(module, "system_units", None) user = getattr(module, "user_units", None) if callable(system): loaded_system |= cast(Callable[[], set[str]], system)() if callable(user): loaded_user |= cast(Callable[[], dict[str, set[str]]], user)().get( "alex", set() ) if loaded_system != expected_system or loaded_user != expected_user: raise SystemExit("active profile does not compose the complete unit manifests") pending_remove = sorted((installed_repo - set(repo)) | (installed_aur - set(aur))) pending_install = sorted((set(repo) - installed_repo) | (set(aur) - installed_aur)) print( f"OK: repo={len(repo)} aur={len(aur)} system={len(loaded_system)} " f"user={len(loaded_user)} concerns={len(repo_files)} profile=laptop" ) print(f"PENDING REMOVE: {' '.join(pending_remove) or 'none'}") print(f"PENDING INSTALL: {' '.join(pending_install) or 'none'}") if __name__ == "__main__": main()