Created
August 8, 2025 15:37
-
-
Save drbh/790d6a147d6a852887448242bedec47f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import json | |
import subprocess | |
import sys | |
from typing import Dict, Set, Tuple | |
def run_nix_eval(commit: str) -> Dict[str, str]: | |
"""Run nix eval command with the specified commit and return parsed JSON.""" | |
cmd = [ | |
"nix", "eval", | |
".#packages.x86_64-linux.python3Packages", | |
"--override-input", "nixpkgs", f"github:nixos/nixpkgs/{commit}", | |
"--json" | |
] | |
print(f"Running: {' '.join(cmd)}") | |
try: | |
result = subprocess.run(cmd, capture_output=True, text=True, check=True) | |
return json.loads(result.stdout) | |
except subprocess.CalledProcessError as e: | |
print(f"Error running nix eval: {e}") | |
print(f"stderr: {e.stderr}") | |
sys.exit(1) | |
except json.JSONDecodeError as e: | |
print(f"Error parsing JSON output: {e}") | |
sys.exit(1) | |
def extract_version(store_path: str) -> str: | |
"""Extract version from a nix store path.""" | |
parts = store_path.split('-') | |
version_parts = [] | |
found_python = False | |
for part in parts: | |
if part.startswith('python'): | |
found_python = True | |
continue | |
if found_python and part[0].isdigit(): | |
version_parts.append(part) | |
return '-'.join(version_parts) if version_parts else "unknown" | |
def print_table(headers, rows): | |
"""Print a formatted table.""" | |
if not rows: | |
return | |
# Calculate column widths | |
col_widths = [len(h) for h in headers] | |
for row in rows: | |
for i, cell in enumerate(row): | |
col_widths[i] = max(col_widths[i], len(str(cell))) | |
# Print header | |
header_line = " | ".join(h.ljust(w) for h, w in zip(headers, col_widths)) | |
print(header_line) | |
print("-+-".join("-" * w for w in col_widths)) | |
# Print rows | |
for row in rows: | |
print(" | ".join(str(cell).ljust(w) for cell, w in zip(row, col_widths))) | |
def compare_packages(old_commit: str, new_commit: str) -> None: | |
"""Compare packages between two commits.""" | |
print(f"Comparing packages between:") | |
print(f" Old: {old_commit}") | |
print(f" New: {new_commit}") | |
print() | |
old_packages = run_nix_eval(old_commit) | |
new_packages = run_nix_eval(new_commit) | |
old_keys = set(old_packages.keys()) | |
new_keys = set(new_packages.keys()) | |
added = new_keys - old_keys | |
removed = old_keys - new_keys | |
common = old_keys & new_keys | |
changed = [] | |
unchanged = [] | |
for pkg in common: | |
old_path = old_packages[pkg] | |
new_path = new_packages[pkg] | |
if old_path != new_path: | |
old_version = extract_version(old_path) | |
new_version = extract_version(new_path) | |
changed.append((pkg, old_version, new_version, old_path, new_path)) | |
else: | |
unchanged.append(pkg) | |
print(f"\nSummary:") | |
print(f" Total packages in old: {len(old_packages)}") | |
print(f" Total packages in new: {len(new_packages)}") | |
print(f" Added: {len(added)}") | |
print(f" Removed: {len(removed)}") | |
print(f" Changed: {len(changed)}") | |
print(f" Unchanged: {len(unchanged)}") | |
if added: | |
print(f"\nAdded packages ({len(added)}):") | |
rows = [] | |
for pkg in sorted(added): | |
version = extract_version(new_packages[pkg]) | |
rows.append([pkg, version]) | |
print_table(["Package", "Version"], rows) | |
if removed: | |
print(f"\nRemoved packages ({len(removed)}):") | |
rows = [] | |
for pkg in sorted(removed): | |
version = extract_version(old_packages[pkg]) | |
rows.append([pkg, version]) | |
print_table(["Package", "Version"], rows) | |
if changed: | |
print(f"\nChanged packages ({len(changed)}):") | |
rows = [] | |
for pkg, old_ver, new_ver, old_path, new_path in sorted(changed): | |
# Determine if version increased (+), decreased (-), or changed (blank) | |
diff = "" | |
if old_ver != "unknown" and new_ver != "unknown": | |
# Simple heuristic: if new version starts with higher number, it's likely upgraded | |
old_parts = old_ver.split('.') | |
new_parts = new_ver.split('.') | |
try: | |
if len(old_parts) > 0 and len(new_parts) > 0: | |
old_major = int(''.join(c for c in old_parts[0] if c.isdigit()) or '0') | |
new_major = int(''.join(c for c in new_parts[0] if c.isdigit()) or '0') | |
if new_major > old_major: | |
diff = "+" | |
elif new_major < old_major: | |
diff = "-" | |
elif len(old_parts) > 1 and len(new_parts) > 1: | |
old_minor = int(''.join(c for c in old_parts[1] if c.isdigit()) or '0') | |
new_minor = int(''.join(c for c in new_parts[1] if c.isdigit()) or '0') | |
if new_minor > old_minor: | |
diff = "+" | |
elif new_minor < old_minor: | |
diff = "-" | |
except: | |
# If parsing fails, leave blank | |
pass | |
rows.append([pkg, old_ver, new_ver, diff]) | |
print_table(["Package", "Old Version", "New Version", ""], rows) | |
if "--verbose" in sys.argv: | |
print(f"\nDetailed paths for changed packages:") | |
rows = [] | |
for pkg, old_ver, new_ver, old_path, new_path in sorted(changed): | |
rows.append([pkg, "old", old_path]) | |
rows.append(["", "new", new_path]) | |
print_table(["Package", "Type", "Path"], rows) | |
if unchanged and "--show-unchanged" in sys.argv: | |
print(f"\nUnchanged packages ({len(unchanged)}):") | |
rows = [] | |
for pkg in sorted(unchanged): | |
version = extract_version(old_packages[pkg]) | |
rows.append([pkg, version]) | |
print_table(["Package", "Version"], rows) | |
def main(): | |
old_commit = "d38025438a6ee456758dc03188ca6873a415463b" | |
new_commit = "679aa6b2219d272ae97f00adbc3092581680243" | |
if len(sys.argv) > 2: | |
old_commit = sys.argv[1] | |
new_commit = sys.argv[2] | |
compare_packages(old_commit, new_commit) | |
if __name__ == "__main__": | |
print("Nix Package Comparison Tool") | |
print("Usage: python compare_nix_packages.py [old_commit] [new_commit] [--verbose] [--show-unchanged]") | |
print() | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment