summaryrefslogtreecommitdiff
path: root/contrib/bst-graph
blob: 5310aae41e47d7e05f55178490e0efe9bf7c8404 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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()