11cb0ef41Sopenharmony_ci#!/usr/bin/env python3
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci# Copyright (c) 2011 Google Inc. All rights reserved.
41cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
51cb0ef41Sopenharmony_ci# found in the LICENSE file.
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ci"""Using the JSON dumped by the dump-dependency-json generator,
81cb0ef41Sopenharmony_cigenerate input suitable for graphviz to render a dependency graph of
91cb0ef41Sopenharmony_citargets."""
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciimport collections
131cb0ef41Sopenharmony_ciimport json
141cb0ef41Sopenharmony_ciimport sys
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_cidef ParseTarget(target):
181cb0ef41Sopenharmony_ci    target, _, suffix = target.partition("#")
191cb0ef41Sopenharmony_ci    filename, _, target = target.partition(":")
201cb0ef41Sopenharmony_ci    return filename, target, suffix
211cb0ef41Sopenharmony_ci
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_cidef LoadEdges(filename, targets):
241cb0ef41Sopenharmony_ci    """Load the edges map from the dump file, and filter it to only
251cb0ef41Sopenharmony_ci  show targets in |targets| and their depedendents."""
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci    file = open("dump.json")
281cb0ef41Sopenharmony_ci    edges = json.load(file)
291cb0ef41Sopenharmony_ci    file.close()
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci    # Copy out only the edges we're interested in from the full edge list.
321cb0ef41Sopenharmony_ci    target_edges = {}
331cb0ef41Sopenharmony_ci    to_visit = targets[:]
341cb0ef41Sopenharmony_ci    while to_visit:
351cb0ef41Sopenharmony_ci        src = to_visit.pop()
361cb0ef41Sopenharmony_ci        if src in target_edges:
371cb0ef41Sopenharmony_ci            continue
381cb0ef41Sopenharmony_ci        target_edges[src] = edges[src]
391cb0ef41Sopenharmony_ci        to_visit.extend(edges[src])
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_ci    return target_edges
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_cidef WriteGraph(edges):
451cb0ef41Sopenharmony_ci    """Print a graphviz graph to stdout.
461cb0ef41Sopenharmony_ci  |edges| is a map of target to a list of other targets it depends on."""
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci    # Bucket targets by file.
491cb0ef41Sopenharmony_ci    files = collections.defaultdict(list)
501cb0ef41Sopenharmony_ci    for src, dst in edges.items():
511cb0ef41Sopenharmony_ci        build_file, target_name, toolset = ParseTarget(src)
521cb0ef41Sopenharmony_ci        files[build_file].append(src)
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci    print("digraph D {")
551cb0ef41Sopenharmony_ci    print("  fontsize=8")  # Used by subgraphs.
561cb0ef41Sopenharmony_ci    print("  node [fontsize=8]")
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci    # Output nodes by file.  We must first write out each node within
591cb0ef41Sopenharmony_ci    # its file grouping before writing out any edges that may refer
601cb0ef41Sopenharmony_ci    # to those nodes.
611cb0ef41Sopenharmony_ci    for filename, targets in files.items():
621cb0ef41Sopenharmony_ci        if len(targets) == 1:
631cb0ef41Sopenharmony_ci            # If there's only one node for this file, simplify
641cb0ef41Sopenharmony_ci            # the display by making it a box without an internal node.
651cb0ef41Sopenharmony_ci            target = targets[0]
661cb0ef41Sopenharmony_ci            build_file, target_name, toolset = ParseTarget(target)
671cb0ef41Sopenharmony_ci            print(
681cb0ef41Sopenharmony_ci                f'  "{target}" [shape=box, label="{filename}\\n{target_name}"]'
691cb0ef41Sopenharmony_ci            )
701cb0ef41Sopenharmony_ci        else:
711cb0ef41Sopenharmony_ci            # Group multiple nodes together in a subgraph.
721cb0ef41Sopenharmony_ci            print('  subgraph "cluster_%s" {' % filename)
731cb0ef41Sopenharmony_ci            print('    label = "%s"' % filename)
741cb0ef41Sopenharmony_ci            for target in targets:
751cb0ef41Sopenharmony_ci                build_file, target_name, toolset = ParseTarget(target)
761cb0ef41Sopenharmony_ci                print(f'    "{target}" [label="{target_name}"]')
771cb0ef41Sopenharmony_ci            print("  }")
781cb0ef41Sopenharmony_ci
791cb0ef41Sopenharmony_ci    # Now that we've placed all the nodes within subgraphs, output all
801cb0ef41Sopenharmony_ci    # the edges between nodes.
811cb0ef41Sopenharmony_ci    for src, dsts in edges.items():
821cb0ef41Sopenharmony_ci        for dst in dsts:
831cb0ef41Sopenharmony_ci            print(f'  "{src}" -> "{dst}"')
841cb0ef41Sopenharmony_ci
851cb0ef41Sopenharmony_ci    print("}")
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci
881cb0ef41Sopenharmony_cidef main():
891cb0ef41Sopenharmony_ci    if len(sys.argv) < 2:
901cb0ef41Sopenharmony_ci        print(__doc__, file=sys.stderr)
911cb0ef41Sopenharmony_ci        print(file=sys.stderr)
921cb0ef41Sopenharmony_ci        print("usage: %s target1 target2..." % (sys.argv[0]), file=sys.stderr)
931cb0ef41Sopenharmony_ci        return 1
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci    edges = LoadEdges("dump.json", sys.argv[1:])
961cb0ef41Sopenharmony_ci
971cb0ef41Sopenharmony_ci    WriteGraph(edges)
981cb0ef41Sopenharmony_ci    return 0
991cb0ef41Sopenharmony_ci
1001cb0ef41Sopenharmony_ci
1011cb0ef41Sopenharmony_ciif __name__ == "__main__":
1021cb0ef41Sopenharmony_ci    sys.exit(main())
103