Chandan Singh pushed to branch chandan/dot-graph at BuildStream / buildstream
Commits:
- 
ec4bbf35
by Benjamin Schubert at 2019-02-13T14:59:47Z
 - 
b41a82d3
by Benjamin Schubert at 2019-02-13T14:59:47Z
 - 
9db7f489
by Benjamin Schubert at 2019-02-13T16:13:16Z
 - 
f2d72ba9
by Chandan Singh at 2019-02-13T16:15:03Z
 
5 changed files:
- buildstream/_exceptions.py
 - buildstream/_loader/loader.py
 - buildstream/_project.py
 - buildstream/element.py
 - + contrib/bst-graph
 
Changes:
| ... | ... | @@ -19,6 +19,7 @@ | 
| 19 | 19 | 
 #        Tiago Gomes <tiago gomes codethink co uk>
 | 
| 20 | 20 | 
 | 
| 21 | 21 | 
 from enum import Enum
 | 
| 22 | 
+import os
 | 
|
| 22 | 23 | 
 | 
| 23 | 24 | 
 # Disable pylint warnings for whole file here:
 | 
| 24 | 25 | 
 # pylint: disable=global-statement
 | 
| ... | ... | @@ -50,6 +51,9 @@ def get_last_exception(): | 
| 50 | 51 | 
 # Used by regression tests
 | 
| 51 | 52 | 
 #
 | 
| 52 | 53 | 
 def get_last_task_error():
 | 
| 54 | 
+    if 'BST_TEST_SUITE' not in os.environ:
 | 
|
| 55 | 
+        raise BstError("Getting the last task error is only supported when running tests")
 | 
|
| 56 | 
+  | 
|
| 53 | 57 | 
     global _last_task_error_domain
 | 
| 54 | 58 | 
     global _last_task_error_reason
 | 
| 55 | 59 | 
 | 
| ... | ... | @@ -67,11 +71,12 @@ def get_last_task_error(): | 
| 67 | 71 | 
 # tests about how things failed in a machine readable way
 | 
| 68 | 72 | 
 #
 | 
| 69 | 73 | 
 def set_last_task_error(domain, reason):
 | 
| 70 | 
-    global _last_task_error_domain
 | 
|
| 71 | 
-    global _last_task_error_reason
 | 
|
| 74 | 
+    if 'BST_TEST_SUITE' in os.environ:
 | 
|
| 75 | 
+        global _last_task_error_domain
 | 
|
| 76 | 
+        global _last_task_error_reason
 | 
|
| 72 | 77 | 
 | 
| 73 | 
-    _last_task_error_domain = domain
 | 
|
| 74 | 
-    _last_task_error_reason = reason
 | 
|
| 78 | 
+        _last_task_error_domain = domain
 | 
|
| 79 | 
+        _last_task_error_reason = reason
 | 
|
| 75 | 80 | 
 | 
| 76 | 81 | 
 | 
| 77 | 82 | 
 class ErrorDomain(Enum):
 | 
| ... | ... | @@ -126,7 +131,8 @@ class BstError(Exception): | 
| 126 | 131 | 
         self.reason = reason
 | 
| 127 | 132 | 
 | 
| 128 | 133 | 
         # Hold on to the last raised exception for testing purposes
 | 
| 129 | 
-        _last_exception = self
 | 
|
| 134 | 
+        if 'BST_TEST_SUITE' in os.environ:
 | 
|
| 135 | 
+            _last_exception = self
 | 
|
| 130 | 136 | 
 | 
| 131 | 137 | 
 | 
| 132 | 138 | 
 # PluginError
 | 
| ... | ... | @@ -152,8 +152,27 @@ class Loader(): | 
| 152 | 152 | 
             #
 | 
| 153 | 153 | 
             ret.append(loader._collect_element(element))
 | 
| 154 | 154 | 
 | 
| 155 | 
+        self._clean_caches()
 | 
|
| 156 | 
+  | 
|
| 155 | 157 | 
         return ret
 | 
| 156 | 158 | 
 | 
| 159 | 
+    # clean_caches()
 | 
|
| 160 | 
+    #
 | 
|
| 161 | 
+    # Clean internal loader caches, recursively
 | 
|
| 162 | 
+    #
 | 
|
| 163 | 
+    # When loading the elements, the loaders use caches in order to not load the
 | 
|
| 164 | 
+    # same element twice. These are kept after loading and prevent garbage
 | 
|
| 165 | 
+    # collection. Cleaning them explicitely is required.
 | 
|
| 166 | 
+    #
 | 
|
| 167 | 
+    def _clean_caches(self):
 | 
|
| 168 | 
+        for loader in self._loaders.values():
 | 
|
| 169 | 
+            # value may be None with nested junctions without overrides
 | 
|
| 170 | 
+            if loader is not None:
 | 
|
| 171 | 
+                loader._clean_caches()
 | 
|
| 172 | 
+  | 
|
| 173 | 
+        self._meta_elements = {}
 | 
|
| 174 | 
+        self._elements = {}
 | 
|
| 175 | 
+  | 
|
| 157 | 176 | 
     ###########################################
 | 
| 158 | 177 | 
     #            Private Methods              #
 | 
| 159 | 178 | 
     ###########################################
 | 
| ... | ... | @@ -358,6 +358,8 @@ class Project(): | 
| 358 | 358 | 
                 for meta in meta_elements
 | 
| 359 | 359 | 
             ]
 | 
| 360 | 360 | 
 | 
| 361 | 
+        Element._clear_meta_elements_cache()
 | 
|
| 362 | 
+  | 
|
| 361 | 363 | 
         # Now warn about any redundant source references which may have
 | 
| 362 | 364 | 
         # been discovered in the resolve() phase.
 | 
| 363 | 365 | 
         redundant_refs = Element._get_redundant_source_refs()
 | 
| ... | ... | @@ -966,6 +966,21 @@ class Element(Plugin): | 
| 966 | 966 | 
 | 
| 967 | 967 | 
         return element
 | 
| 968 | 968 | 
 | 
| 969 | 
+    # _clear_meta_elements_cache()
 | 
|
| 970 | 
+    #
 | 
|
| 971 | 
+    # Clear the internal meta elements cache.
 | 
|
| 972 | 
+    #
 | 
|
| 973 | 
+    # When loading elements from meta, we cache already instantiated elements
 | 
|
| 974 | 
+    # in order to not have to load the same elements twice.
 | 
|
| 975 | 
+    # This clears the cache.
 | 
|
| 976 | 
+    #
 | 
|
| 977 | 
+    # It should be called whenever we are done loading all elements in order
 | 
|
| 978 | 
+    # to save memory.
 | 
|
| 979 | 
+    #
 | 
|
| 980 | 
+    @classmethod
 | 
|
| 981 | 
+    def _clear_meta_elements_cache(cls):
 | 
|
| 982 | 
+        cls.__instantiated_elements = {}
 | 
|
| 983 | 
+  | 
|
| 969 | 984 | 
     # _get_redundant_source_refs()
 | 
| 970 | 985 | 
     #
 | 
| 971 | 986 | 
     # Fetches a list of (Source, ref) tuples of all the Sources
 | 
| 1 | 
+#!/usr/bin/env python3
 | 
|
| 2 | 
+'''Print dependency graph of given element(s) in DOT format.
 | 
|
| 3 | 
+  | 
|
| 4 | 
+This script must be run from the same directory where you would normally
 | 
|
| 5 | 
+run `bst` commands.
 | 
|
| 6 | 
+  | 
|
| 7 | 
+When `--format` option is specified, the output will also be rendered in the
 | 
|
| 8 | 
+given format. A file with name `bst-graph.{format}` will be created in the same
 | 
|
| 9 | 
+directory. To use this option, you must have the `graphviz` command line tool
 | 
|
| 10 | 
+installed.
 | 
|
| 11 | 
+'''
 | 
|
| 12 | 
+  | 
|
| 13 | 
+import argparse
 | 
|
| 14 | 
+import subprocess
 | 
|
| 15 | 
+  | 
|
| 16 | 
+from graphviz import Digraph
 | 
|
| 17 | 
+  | 
|
| 18 | 
+  | 
|
| 19 | 
+def parse_args():
 | 
|
| 20 | 
+    '''Handle parsing of command line arguments.
 | 
|
| 21 | 
+  | 
|
| 22 | 
+    Returns:
 | 
|
| 23 | 
+       A argparse.Namespace object
 | 
|
| 24 | 
+    '''
 | 
|
| 25 | 
+    parser = argparse.ArgumentParser(description=__doc__)
 | 
|
| 26 | 
+    parser.add_argument(
 | 
|
| 27 | 
+        'ELEMENT', nargs='*',
 | 
|
| 28 | 
+        help='Name of the element'
 | 
|
| 29 | 
+    )
 | 
|
| 30 | 
+    parser.add_argument(
 | 
|
| 31 | 
+        '--format',
 | 
|
| 32 | 
+        help='Redner the graph in given format (`pdf`, `png`, `svg` etc)'
 | 
|
| 33 | 
+    )
 | 
|
| 34 | 
+    parser.add_argument(
 | 
|
| 35 | 
+        '--view', action='store_true',
 | 
|
| 36 | 
+        help='Open the rendered graph with the default application'
 | 
|
| 37 | 
+    )
 | 
|
| 38 | 
+    return parser.parse_args()
 | 
|
| 39 | 
+  | 
|
| 40 | 
+  | 
|
| 41 | 
+def parse_graph(lines):
 | 
|
| 42 | 
+    '''Return nodes and edges of the parsed grpah.
 | 
|
| 43 | 
+  | 
|
| 44 | 
+    Args:
 | 
|
| 45 | 
+       lines: List of lines in format 'NAME|BUILD-DEPS|RUNTIME-DEPS'
 | 
|
| 46 | 
+  | 
|
| 47 | 
+    Returns:
 | 
|
| 48 | 
+       Tuple of format (nodes,build_deps,runtime_deps)
 | 
|
| 49 | 
+       Each member of build_deps and runtime_deps is also a tuple.
 | 
|
| 50 | 
+    '''
 | 
|
| 51 | 
+    nodes = set()
 | 
|
| 52 | 
+    build_deps = set()
 | 
|
| 53 | 
+    runtime_deps = set()
 | 
|
| 54 | 
+    for line in lines:
 | 
|
| 55 | 
+        # It is safe to split on '|' as it is not a valid character for
 | 
|
| 56 | 
+        # element names.
 | 
|
| 57 | 
+        name, build_dep, runtime_dep = line.split('|')
 | 
|
| 58 | 
+        build_dep = build_dep.lstrip('[').rstrip(']').split(',')
 | 
|
| 59 | 
+        runtime_dep = runtime_dep.lstrip('[').rstrip(']').split(',')
 | 
|
| 60 | 
+        nodes.add(name)
 | 
|
| 61 | 
+        [build_deps.add((name, dep)) for dep in build_dep if dep]
 | 
|
| 62 | 
+        [runtime_deps.add((name, dep)) for dep in runtime_dep if dep]
 | 
|
| 63 | 
+  | 
|
| 64 | 
+    return nodes, build_deps, runtime_deps
 | 
|
| 65 | 
+  | 
|
| 66 | 
+  | 
|
| 67 | 
+def generate_graph(nodes, build_deps, runtime_deps):
 | 
|
| 68 | 
+    '''Generate graph from given nodes and edges.
 | 
|
| 69 | 
+  | 
|
| 70 | 
+    Args:
 | 
|
| 71 | 
+       nodes: set of nodes
 | 
|
| 72 | 
+       build_deps: set of tuples of build depdencies
 | 
|
| 73 | 
+       runtime_deps: set of tuples of runtime depdencies
 | 
|
| 74 | 
+  | 
|
| 75 | 
+    Returns:
 | 
|
| 76 | 
+       A graphviz.Digraph object
 | 
|
| 77 | 
+    '''
 | 
|
| 78 | 
+    graph = Digraph()
 | 
|
| 79 | 
+    for node in nodes:
 | 
|
| 80 | 
+        graph.node(node)
 | 
|
| 81 | 
+    for source, target in build_deps:
 | 
|
| 82 | 
+        graph.edge(source, target, label='build-dep')
 | 
|
| 83 | 
+    for source, target in runtime_deps:
 | 
|
| 84 | 
+        graph.edge(source, target, label='runtime-dep')
 | 
|
| 85 | 
+    return graph
 | 
|
| 86 | 
+  | 
|
| 87 | 
+  | 
|
| 88 | 
+def main():
 | 
|
| 89 | 
+    args = parse_args()
 | 
|
| 90 | 
+    cmd = ['bst', 'show', '--format', '%{name}|%{build-deps}|%{runtime-deps}']
 | 
|
| 91 | 
+    if 'element' in args:
 | 
|
| 92 | 
+        cmd += args.element
 | 
|
| 93 | 
+    graph_lines = subprocess.check_output(cmd, universal_newlines=True)
 | 
|
| 94 | 
+    # NOTE: We generate nodes and edges before giving them to graphviz as
 | 
|
| 95 | 
+    # the library does not de-deuplicate them.
 | 
|
| 96 | 
+    nodes, build_deps, runtime_deps = parse_graph(graph_lines.splitlines())
 | 
|
| 97 | 
+    graph = generate_graph(nodes, build_deps, runtime_deps)
 | 
|
| 98 | 
+    print(graph.source)
 | 
|
| 99 | 
+    if args.format:
 | 
|
| 100 | 
+        graph.render(cleanup=True,
 | 
|
| 101 | 
+                     filename='bst-graph',
 | 
|
| 102 | 
+                     format=args.format,
 | 
|
| 103 | 
+                     view=args.view)
 | 
|
| 104 | 
+  | 
|
| 105 | 
+  | 
|
| 106 | 
+if __name__ == '__main__':
 | 
|
| 107 | 
+    main()
 | 
