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