-
Notifications
You must be signed in to change notification settings - Fork 15
/
pkg-dependency-graph.py
executable file
·135 lines (111 loc) · 3.72 KB
/
pkg-dependency-graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#! /usr/bin/env python3.6
#
# A script to generate a .dot file (Graphviz) from installed
# packages, to show what depends on what. Prints the dot code
# to standard output.
#
# Use the option --root to print out, one per line, the roots
# of the forest of installed packages (this suppresses regular
# dot output).
import argparse
import os
import subprocess
import sys
if sys.version_info < (3, 5):
raise ValueError("Python 3.5 or later is required.")
def collect_pkg():
"""
Run pkg, and returns a dictionary
where the keys are names of packages,
and the values are lists of packages that the key depends on.
"""
deps = {}
PKG = "/usr/sbin/pkg"
QUERY = "query"
# Gather all packages, init deps to an empty dependency list per package
r = subprocess.run(
[PKG, QUERY, "%o"],
stdout=subprocess.PIPE)
output = r.stdout.split(b"\n")
for line in output:
if line:
line = line.decode("ascii")
deps[line] = []
# Now build up the dependency graph
r = subprocess.run(
[PKG, QUERY, "%o::%do"],
stdout=subprocess.PIPE)
output = r.stdout.split(b"\n")
for line in output:
if line:
line = line.decode("ascii")
package, dep = line.split("::")
deps[package].append(dep)
return deps
def normalize_deps(deps):
"""
Normalize the dependencies.
A package in @p deps now has a dependency list that contains
all the transitive dependencies; prune it down to only the
first-level dependencies.
"""
normalized_deps = {}
for key in deps.keys():
all_deps = set(deps[key])
second_deps = set()
for k in all_deps:
second_deps.update(set(deps[k]))
l = list(all_deps - second_deps)
l.sort()
normalized_deps[key] = l
return normalized_deps
def count_reverse(deps):
"""
For a dependency graph pkg -> [pkgs], returns a dictionary
with pkg as key, and the number of *other* packages that depend
on this one. May be done with a normalized, or not-normalized graph.
The keys that keep count 0 are the "root" nodes of the forest of
installed packages.
"""
reverse_count = {}
for key in deps.keys():
reverse_count[key] = 0
for key in deps.keys():
for d in deps[key]:
# These are the packages that k depends on, so they all get +1
reverse_count[d] += 1
return reverse_count
def parse_args():
parser = argparse.ArgumentParser(description="pkg(8) graphing tool")
parser.add_argument("--roots", "-r", dest="roots", action="store_true", default=False)
return parser.parse_args()
def do_graph(dependency_graph):
packages = list(dependency_graph.keys())
packages.sort()
print("### Root nodes:\n###\n#")
count = count_reverse(dependency_graph)
for k in packages:
if count[k] == 0:
print("# p%d %s" % (packages.index(k), k))
print("digraph {")
for k in packages:
print(" p%d [label=\"%s\"];" % (packages.index(k), str(k)))
c = 1
for k in packages:
for dep in dependency_graph[k]:
print(" p%d -> p%d;" % (packages.index(k), packages.index(dep)) )
print("}")
def do_roots(dependency_graph):
count = count_reverse(dependency_graph)
packages = list(dependency_graph.keys())
packages.sort()
for k in packages:
if count[k] == 0:
print(k)
if __name__ == "__main__":
args = parse_args()
dependency_graph = normalize_deps(collect_pkg())
if args.roots:
do_roots(dependency_graph)
else:
do_graph(dependency_graph)