summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorChandan Singh <csingh43@bloomberg.net>2019-02-13 21:36:42 +0530
committerChandan Singh <chandan@chandansingh.net>2019-02-15 06:58:27 +0000
commit6952001193603691fd450816046e512b37e7e733 (patch)
tree394a5f246060813ca86e823c8684ce6ddafe6d00 /contrib
parent56c07baad4cdd79e2fbab1fda856107ad6e3bfb7 (diff)
downloadbuildstream-6952001193603691fd450816046e512b37e7e733.tar.gz
contrib/bst-graph: Add script to print graph in DOT formatchandan/dot-graph
This script leverages the recently added format strings (`%{build-deps}`, `%{runtime-deps}`) to `bst show` to print a graph in DOT format. This requires users to have the `graphviz` python package installed. Additionally, users can also render the graph using the `--format` option if they have the `graphviz` command line tool installed.
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/bst-graph107
1 files changed, 107 insertions, 0 deletions
diff --git a/contrib/bst-graph b/contrib/bst-graph
new file mode 100755
index 000000000..5310aae41
--- /dev/null
+++ b/contrib/bst-graph
@@ -0,0 +1,107 @@
+#!/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()