blob: 5310aae41e47d7e05f55178490e0efe9bf7c8404 [file] [log] [blame]
#!/usr/bin/env python3
'''Print dependency graph of given element(s) in DOT format.
This script must be run from the same directory where you would normally
run `bst` commands.
When `--format` option is specified, the output will also be rendered in the
given format. A file with name `bst-graph.{format}` will be created in the same
directory. To use this option, you must have the `graphviz` command line tool
installed.
'''
import argparse
import subprocess
from graphviz import Digraph
def parse_args():
'''Handle parsing of command line arguments.
Returns:
A argparse.Namespace object
'''
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'ELEMENT', nargs='*',
help='Name of the element'
)
parser.add_argument(
'--format',
help='Redner the graph in given format (`pdf`, `png`, `svg` etc)'
)
parser.add_argument(
'--view', action='store_true',
help='Open the rendered graph with the default application'
)
return parser.parse_args()
def parse_graph(lines):
'''Return nodes and edges of the parsed grpah.
Args:
lines: List of lines in format 'NAME|BUILD-DEPS|RUNTIME-DEPS'
Returns:
Tuple of format (nodes,build_deps,runtime_deps)
Each member of build_deps and runtime_deps is also a tuple.
'''
nodes = set()
build_deps = set()
runtime_deps = set()
for line in lines:
# It is safe to split on '|' as it is not a valid character for
# element names.
name, build_dep, runtime_dep = line.split('|')
build_dep = build_dep.lstrip('[').rstrip(']').split(',')
runtime_dep = runtime_dep.lstrip('[').rstrip(']').split(',')
nodes.add(name)
[build_deps.add((name, dep)) for dep in build_dep if dep]
[runtime_deps.add((name, dep)) for dep in runtime_dep if dep]
return nodes, build_deps, runtime_deps
def generate_graph(nodes, build_deps, runtime_deps):
'''Generate graph from given nodes and edges.
Args:
nodes: set of nodes
build_deps: set of tuples of build depdencies
runtime_deps: set of tuples of runtime depdencies
Returns:
A graphviz.Digraph object
'''
graph = Digraph()
for node in nodes:
graph.node(node)
for source, target in build_deps:
graph.edge(source, target, label='build-dep')
for source, target in runtime_deps:
graph.edge(source, target, label='runtime-dep')
return graph
def main():
args = parse_args()
cmd = ['bst', 'show', '--format', '%{name}|%{build-deps}|%{runtime-deps}']
if 'element' in args:
cmd += args.element
graph_lines = subprocess.check_output(cmd, universal_newlines=True)
# NOTE: We generate nodes and edges before giving them to graphviz as
# the library does not de-deuplicate them.
nodes, build_deps, runtime_deps = parse_graph(graph_lines.splitlines())
graph = generate_graph(nodes, build_deps, runtime_deps)
print(graph.source)
if args.format:
graph.render(cleanup=True,
filename='bst-graph',
format=args.format,
view=args.view)
if __name__ == '__main__':
main()