1#!python3 2# 3# Copyright 2019-2023 Adrien Destugues <pulkomandy@pulkomandy.tk> 4# 5# Distributed under terms of the MIT license. 6 7from os import listdir 8from os.path import isfile, join 9import subprocess 10import re 11import sys 12 13""" 14Generate a graph of dependencies for a set of packages packages in an Haiku system. 15 16Usage: 17- Without arguments: generate a graph of all packages in /system/packages. This can be quite busy 18 and hard to understand. It will also take a while to generate the graph, as dot tries to route 19 thousands of edges. 20- With arguments: the arguments are a list of packages to analyze. This allows to print a subset 21 of the packages for a better view. 22 23Dependencies are resolved: if a package has a specific string in its REQUIRES and another has the 24same string in its PROVIDES, a BLUE edge is drawn between the two package. 25If a package has a REQUIRES that is not matched by any other package in the set, this REQUIRE entry 26is drawn as a node, and the edge pointing to it is RED (so you can easily see missing dependencies 27in a package subset). If you use the complete /system/packages hierarchy, there should be no red 28edges, all dependencies are satisfied. 29 30The output of the script can be saved to a file for manual analysis (for example, you can search 31packages that nothing points to, and see if you want to uninstall them), or piped into dot for 32rendering as a PNG, for example: 33 34 cd /system/packages 35 pkggraph.py qt* gst_plugins_ba* | dot -Tpng -o /tmp/packages.png 36 ShowImage /tmp/packages.png 37""" 38 39path = "/system/packages" 40if len(sys.argv) > 1: 41 packages = sys.argv[1:] 42else: 43 packages = [join(path, f) for f in listdir(path) if(isfile(join(path, f)))] 44 45print('strict digraph {\nrankdir="LR"\nsplines=ortho\nnode [ fontname="Noto", fontsize=10];') 46 47pmap = {} 48rmap = {} 49 50for p in packages: 51 pkgtool = subprocess.Popen(['package', 'list', '-i', p], stdout = subprocess.PIPE) 52 infos, stderr = pkgtool.communicate() 53 54 provides = [] 55 requires = [] 56 57 for line in infos.split(b'\n'): 58 if line.startswith(b"\tprovides:"): 59 provides.append(line.split(b' ')[1]) 60 if line.startswith(b"\trequires:"): 61 line = line.split(b' ')[1] 62 if b'>' in line: 63 line = line.split(b'>')[0] 64 if b'=' in line: 65 line = line.split(b'=')[0] 66 if line != b'haiku' and line != b'haiku_x86': 67 requires.append(line) 68 69 for pro in provides: 70 pmap[pro] = provides[0] 71 if len(requires) > 0: 72 rmap[provides[0]] = requires 73 74for k,v in rmap.items(): 75 for dep in v: 76 color = "red" 77 if dep in pmap: 78 dep = pmap[dep] 79 color = "blue" 80 print('"%s" -> "%s" [color=%s]' % (k.decode('utf-8'), dep.decode('utf-8'), color)) 81 82print("}") 83