Skip to content

Instantly share code, notes, and snippets.

@Screwtapello
Created July 16, 2020 13:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Screwtapello/4eac31eb6c642dfc1d70f55fb9a3efde to your computer and use it in GitHub Desktop.
Save Screwtapello/4eac31eb6c642dfc1d70f55fb9a3efde to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
import collections
import dataclasses
import re
import subprocess
import sys
import typing
VERSIONED_DEP_RE = re.compile(r" \([^)]*\)")
def gather_package_info() -> str:
result = subprocess.run(
args=[
"dpkg-query",
"-Wf",
"${db:Status-Status}\t${Installed-Size}\t${Package}\t${Depends}\n",
],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
check=True,
text=True,
)
return result.stdout
@dataclasses.dataclass
class PackageInfo:
sizes: typing.Mapping[str, int]
dependencies: typing.Mapping[str, typing.AbstractSet[str]]
shared_packages: typing.AbstractSet[str]
root_packages: typing.AbstractSet[str]
def unshared_size(self, package: str) -> int:
res = self.sizes[package]
for each in self.dependencies[package]:
if each in self.shared_packages:
continue
res += self.unshared_size(each)
return res
def parse_package_info(info: str) -> PackageInfo:
sizes = collections.defaultdict(int)
dependencies = collections.defaultdict(set)
reverse_dependency_count = collections.defaultdict(int)
for line in info.split("\n"):
if line.strip() == "":
continue
(status, raw_size, name, raw_deps) = line.split("\t")
if status != "installed":
continue
sizes[name] = int(raw_size)
nested_deps = (
VERSIONED_DEP_RE.sub("", each).replace(":any", "").split(" | ")
for each in raw_deps.split(", ")
if each
)
dependencies[name] = set()
for alternatives in nested_deps:
for each in alternatives:
dependencies[name].add(each)
for each in dependencies[name]:
reverse_dependency_count[each] = reverse_dependency_count.get(each, 0) + 1
shared_packages = {
package
for (package, count) in reverse_dependency_count.items()
if count > 1
}
root_packages = {
package
for package in sizes
if package not in reverse_dependency_count
}
return PackageInfo(
sizes=sizes,
dependencies=dependencies,
shared_packages=shared_packages,
root_packages=root_packages,
)
def main(args):
raw_info = gather_package_info()
info = parse_package_info(raw_info)
for package in info.root_packages:
unshared_size = info.unshared_size(package)
print(unshared_size, "\t", package)
if __name__ == "__main__":
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment