Index: cros_generate_deps_graphs |
diff --git a/cros_generate_deps_graphs b/cros_generate_deps_graphs |
new file mode 100755 |
index 0000000000000000000000000000000000000000..946cc38404db1d4a89e8595593e66a8010421f94 |
--- /dev/null |
+++ b/cros_generate_deps_graphs |
@@ -0,0 +1,176 @@ |
+#!/usr/bin/python |
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Generates pretty dependency graphs for Chrome OS packages.""" |
+ |
+import optparse |
+import os |
+import subprocess |
+import sys |
+ |
+ |
+NORMAL_COLOR = 'black' |
+TARGET_COLOR = 'red' |
+SEED_COLOR = 'green' |
+CHILD_COLOR = 'grey' |
+ |
+ |
+def GetReverseDependencyClosure(full_name, deps_map): |
+ """Gets the closure of the reverse dependencies of a node. |
+ |
+ Walks the tree along all the reverse dependency paths to find all the nodes |
+ that transitively depend on the input node.""" |
+ s = set() |
+ def GetClosure(name): |
+ s.add(name) |
+ node = deps_map[name] |
+ for dep in node['rev_deps']: |
+ if dep in s: |
+ continue |
+ GetClosure(dep) |
+ |
+ GetClosure(full_name) |
+ return s |
+ |
+ |
+def GetOutputBaseName(node, options): |
+ """Gets the basename of the output file for a node.""" |
+ return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'], |
+ options.format) |
+ |
+ |
+def GetNodeLines(node, options, color): |
+ """Gets the dot definition for a node.""" |
+ name = node['full_name'] |
+ tags = ['label="%s (%s)"' % (name, node['action']), |
+ 'color="%s"' % color, |
+ 'fontcolor="%s"' % color] |
+ if options.link: |
+ filename = GetOutputBaseName(node, options) |
+ tags.append('href="%s%s"' % (options.base_url, filename)) |
+ return ['"%s" [%s];' % (name, ', '.join(tags))] |
+ |
+ |
+def GetReverseDependencyArcLines(node, options): |
+ """Gets the dot definitions for the arcs leading to a node.""" |
+ lines = [] |
+ name = node['full_name'] |
+ for j in node['rev_deps']: |
+ lines.append('"%s" -> "%s";' % (j, name)) |
+ return lines |
+ |
+ |
+def GenerateDotGraph(package, deps_map, options): |
+ """Generates the dot source for the dependency graph leading to a node. |
+ |
+ The output is a list of lines.""" |
+ deps = GetReverseDependencyClosure(package, deps_map) |
+ node = deps_map[package] |
+ |
+ # Keep track of all the emitted nodes so that we don't issue multiple |
+ # definitions |
+ emitted = set() |
+ |
+ lines = ['digraph dep {', |
+ 'graph [name="%s"];' % package] |
+ |
+ # Add all the children if we want them, all of them in their own subgraph, |
+ # as a sink. Keep the arcs outside of the subgraph though (it generates |
+ # better layout). |
+ has_children = False |
+ if options.children and node['deps']: |
+ has_children = True |
+ lines += ['subgraph {', |
+ 'rank=sink;'] |
+ arc_lines = [] |
+ for child in node['deps']: |
+ child_node = deps_map[child] |
+ lines += GetNodeLines(child_node, options, CHILD_COLOR) |
+ emitted.add(child) |
+ # If child is in the rev_deps, we'll get the arc later. |
+ if not child in node['rev_deps']: |
+ arc_lines.append('"%s" -> "%s";' % (package, child)) |
+ lines += ['}'] |
+ lines += arc_lines |
+ |
+ # Add the package in its own subgraph. If we didn't have children, make it |
+ # a sink |
+ lines += ['subgraph {'] |
+ if has_children: |
+ lines += ['rank=same;'] |
+ else: |
+ lines += ['rank=sink;'] |
+ lines += GetNodeLines(node, options, TARGET_COLOR) |
+ emitted.add(package) |
+ lines += ['}'] |
+ |
+ # Add all the other nodes, as well as all the arcs. |
+ for dep in deps: |
+ dep_node = deps_map[dep] |
+ if not dep in emitted: |
+ color = NORMAL_COLOR |
+ if dep_node['action'] == 'seed': |
+ color = SEED_COLOR |
+ lines += GetNodeLines(dep_node, options, color) |
+ lines += GetReverseDependencyArcLines(dep_node, options) |
+ |
+ lines += ['}'] |
+ return lines |
+ |
+ |
+def GenerateImages(input, options): |
+ """Generate the output images for all the nodes in the input.""" |
+ deps_map = eval(input.read()) |
+ |
+ for package in deps_map: |
+ lines = GenerateDotGraph(package, deps_map, options) |
+ data = '\n'.join(lines) |
+ |
+ filename = os.path.join(options.output_dir, |
+ GetOutputBaseName(deps_map[package], options)) |
+ |
+ # Send the source to dot. |
+ proc = subprocess.Popen(['dot', '-T' + options.format, '-o' + filename], |
+ stdin=subprocess.PIPE) |
+ proc.communicate(data) |
+ |
+ if options.save_dot: |
+ file = open(filename + '.dot', 'w') |
+ file.write(data) |
+ file.close() |
+ |
+ |
+def main(): |
+ parser = optparse.OptionParser(usage='usage: %prog [options] input') |
+ parser.add_option('-f', '--format', default='svg', |
+ help='Dot output format (png, svg, etc.).') |
+ parser.add_option('-o', '--output-dir', default='.', |
+ help='Output directory.') |
+ parser.add_option('-c', '--children', action='store_true', |
+ help='Also add children.') |
+ parser.add_option('-l', '--link', action='store_true', |
+ help='Embed links.') |
+ parser.add_option('-b', '--base-url', default='', |
+ help='Base url for links.') |
+ parser.add_option('-s', '--save-dot', action='store_true', |
+ help='Save dot files.') |
+ (options, inputs) = parser.parse_args() |
+ |
+ try: |
+ os.makedirs(options.output_dir) |
+ except OSError: |
+ # The directory already exists. |
+ pass |
+ |
+ if not inputs: |
+ GenerateImages(sys.stdin, options) |
+ else: |
+ for i in inputs: |
+ file = open(i) |
+ GenerateImages(file, options) |
+ file.close() |
+ |
+if __name__ == '__main__': |
+ main() |