|
1
|
+#!/usr/bin/env python3
|
|
2
|
+'''Print dependency graph of given element(s) in DOT format.
|
|
3
|
+
|
|
4
|
+This script must be run from the same directory where you would normally
|
|
5
|
+run `bst` commands.
|
|
6
|
+
|
|
7
|
+When `--format` option is specified, the output will also be rendered in the
|
|
8
|
+given format. A file with name `bst-graph.{format}` will be created in the same
|
|
9
|
+directory. To use this option, you must have the `graphviz` command line tool
|
|
10
|
+installed.
|
|
11
|
+'''
|
|
12
|
+
|
|
13
|
+import argparse
|
|
14
|
+import subprocess
|
|
15
|
+
|
|
16
|
+from graphviz import Digraph
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+def parse_args():
|
|
20
|
+ '''Handle parsing of command line arguments.
|
|
21
|
+
|
|
22
|
+ Returns:
|
|
23
|
+ A argparse.Namespace object
|
|
24
|
+ '''
|
|
25
|
+ parser = argparse.ArgumentParser(description=__doc__)
|
|
26
|
+ parser.add_argument(
|
|
27
|
+ 'ELEMENT', nargs='*',
|
|
28
|
+ help='Name of the element'
|
|
29
|
+ )
|
|
30
|
+ parser.add_argument(
|
|
31
|
+ '--format',
|
|
32
|
+ help='Redner the graph in given format (`pdf`, `png`, `svg` etc)'
|
|
33
|
+ )
|
|
34
|
+ parser.add_argument(
|
|
35
|
+ '--view', action='store_true',
|
|
36
|
+ help='Open the rendered graph with the default application'
|
|
37
|
+ )
|
|
38
|
+ return parser.parse_args()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+def parse_graph(lines):
|
|
42
|
+ '''Return nodes and edges of the parsed grpah.
|
|
43
|
+
|
|
44
|
+ Args:
|
|
45
|
+ lines: List of lines in format 'NAME|BUILD-DEPS|RUNTIME-DEPS'
|
|
46
|
+
|
|
47
|
+ Returns:
|
|
48
|
+ Tuple of format (nodes,build_deps,runtime_deps)
|
|
49
|
+ Each member of build_deps and runtime_deps is also a tuple.
|
|
50
|
+ '''
|
|
51
|
+ nodes = set()
|
|
52
|
+ build_deps = set()
|
|
53
|
+ runtime_deps = set()
|
|
54
|
+ for line in lines:
|
|
55
|
+ # It is safe to split on '|' as it is not a valid character for
|
|
56
|
+ # element names.
|
|
57
|
+ name, build_dep, runtime_dep = line.split('|')
|
|
58
|
+ build_dep = build_dep.lstrip('[').rstrip(']').split(',')
|
|
59
|
+ runtime_dep = runtime_dep.lstrip('[').rstrip(']').split(',')
|
|
60
|
+ nodes.add(name)
|
|
61
|
+ [build_deps.add((name, dep)) for dep in build_dep if dep]
|
|
62
|
+ [runtime_deps.add((name, dep)) for dep in runtime_dep if dep]
|
|
63
|
+
|
|
64
|
+ return nodes, build_deps, runtime_deps
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+def generate_graph(nodes, build_deps, runtime_deps):
|
|
68
|
+ '''Generate graph from given nodes and edges.
|
|
69
|
+
|
|
70
|
+ Args:
|
|
71
|
+ nodes: set of nodes
|
|
72
|
+ build_deps: set of tuples of build depdencies
|
|
73
|
+ runtime_deps: set of tuples of runtime depdencies
|
|
74
|
+
|
|
75
|
+ Returns:
|
|
76
|
+ A graphviz.Digraph object
|
|
77
|
+ '''
|
|
78
|
+ graph = Digraph()
|
|
79
|
+ for node in nodes:
|
|
80
|
+ graph.node(node)
|
|
81
|
+ for source, target in build_deps:
|
|
82
|
+ graph.edge(source, target, label='build-dep')
|
|
83
|
+ for source, target in runtime_deps:
|
|
84
|
+ graph.edge(source, target, label='runtime-dep')
|
|
85
|
+ return graph
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+def main():
|
|
89
|
+ args = parse_args()
|
|
90
|
+ cmd = ['bst', 'show', '--format', '%{name}|%{build-deps}|%{runtime-deps}']
|
|
91
|
+ if 'element' in args:
|
|
92
|
+ cmd += args.element
|
|
93
|
+ graph_lines = subprocess.check_output(cmd, universal_newlines=True)
|
|
94
|
+ # NOTE: We generate nodes and edges before giving them to graphviz as
|
|
95
|
+ # the library does not de-deuplicate them.
|
|
96
|
+ nodes, build_deps, runtime_deps = parse_graph(graph_lines.splitlines())
|
|
97
|
+ graph = generate_graph(nodes, build_deps, runtime_deps)
|
|
98
|
+ print(graph.source)
|
|
99
|
+ if args.format:
|
|
100
|
+ graph.render(cleanup=True,
|
|
101
|
+ filename='bst-graph',
|
|
102
|
+ format=args.format,
|
|
103
|
+ view=args.view)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+if __name__ == '__main__':
|
|
107
|
+ main()
|