Created
July 30, 2024 11:07
-
-
Save iTrooz/1d7cbefedd994ae4719fc08f35a649cc to your computer and use it in GitHub Desktop.
pacman package upgrade size verbose
This file contains 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 python | |
""" | |
This script shows the individual size changes of upgradable packages in Arch Linux. | |
You can use it to understand, e.g. why your upgrade size is negative. | |
""" | |
import typing | |
import subprocess | |
import sys | |
# ---- UNIT FUNCTIONS | |
def get_unit_weight(unit: str) -> int: | |
match unit: | |
case 'B': | |
return 1 | |
case 'KiB': | |
return 1024 | |
case 'MiB': | |
return 1024 * 1024 | |
case 'GiB': | |
return 1024 * 1024 * 1024 | |
case 'TiB': | |
return 1024 * 1024 * 1024 * 1024 | |
case _: | |
raise ValueError(f"Unknown unit: {unit}") | |
# From Github Copilot | |
def convert_to_human_size(size: float) -> str: | |
if size < 0: | |
return f"-{convert_to_human_size(-size)}" | |
units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] | |
unit_index = 0 | |
while size >= 1024 and unit_index < len(units) - 1: | |
size /= 1024 | |
unit_index += 1 | |
return f"{size:.2f} {units[unit_index]}" | |
# Parse a text in the form 2,5 MiB and returns bytes | |
def parse_human_size(size_str: str) -> float: | |
size_str = size_str.replace(',', '.') | |
num, unit = size_str.split(' ') | |
return float(num) * get_unit_weight(unit) | |
# ----- PACMAN PARSING FUNCTIONS | |
# Get upgradable packages | |
def get_wanted_packages(): | |
output = subprocess.check_output(['pacman', '-Qu']).decode('utf-8') | |
output = output.strip().split('\n') | |
packages = list(map(lambda x: x.split(' ')[0], output)) | |
return packages | |
def parse_installed_sizes(output: str) -> dict[str, float]: | |
def get_value(line: str) -> str: | |
return line.split(':')[1].strip() | |
sizes = {} | |
current_name = None | |
for line in output.split('\n'): | |
if line.startswith('Name'): | |
current_name = get_value(line) | |
if line.startswith('Installed Size'): | |
size_str = get_value(line) | |
size = parse_human_size(size_str) | |
sizes[current_name] = size | |
return sizes | |
def get_cmd_output(cmd: list[str]) -> str: | |
try: | |
return subprocess.check_output([*cmd], stderr=subprocess.DEVNULL) | |
except subprocess.CalledProcessError as e: | |
return e.output | |
def get_packages_size(packages: list[str]) -> typing.Union[float, float] | None: | |
sync_output = get_cmd_output(['pacman', '-Si', *packages]).decode('utf-8') | |
sync_sizes = parse_installed_sizes(sync_output) | |
installed_output = get_cmd_output(['pacman', '-Qi', *packages]).decode('utf-8') | |
local_sizes = parse_installed_sizes(installed_output) | |
return local_sizes, sync_sizes | |
# ----- MAIN | |
def compare_package_sizes(): | |
packages = get_wanted_packages() | |
total_delta = 0 | |
deltas = {} | |
local_sizes, sync_sizes = get_packages_size(packages) | |
for package, local_size in local_sizes.items(): | |
if package not in sync_sizes: | |
print(f"Package '{package}' not found in sync database. Probably an AUR/locally installed package.", file=sys.stderr) | |
continue | |
sync_size = sync_sizes[package] | |
delta = sync_size - local_size | |
total_delta += delta | |
deltas[package] = delta | |
if len(sys.argv) == 1: | |
s = sorted(deltas.items(), key=lambda x: x[1]) | |
elif len(sys.argv) == 2: | |
if sys.argv[1] == "reverse": | |
s = sorted(deltas.items(), key=lambda x: x[1], reverse=True) | |
else: | |
raise ValueError(f"Unknown argument: {sys.argv[1]}") | |
else: | |
raise ValueError("Too many arguments") | |
for package, delta in s: | |
print(f"{package} size change: {convert_to_human_size(delta)}") | |
print(f"Total install size delta: {convert_to_human_size(total_delta)}") | |
compare_package_sizes() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment