#!/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()