| #!/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 dot_helper |
| import optparse |
| import os |
| 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 AddNodeToSubgraph(subgraph, node, options, color): |
| """Gets the dot definition for a node.""" |
| name = node['full_name'] |
| href = None |
| if options.link: |
| filename = GetOutputBaseName(node, options) |
| href = '%s%s' % (options.base_url, filename) |
| subgraph.AddNode(name, name, color, href) |
| |
| |
| |
| 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() |
| |
| graph = dot_helper.Graph(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). |
| children_subgraph = None |
| if options.children and node['deps']: |
| children_subgraph = graph.AddNewSubgraph('sink') |
| for child in node['deps']: |
| child_node = deps_map[child] |
| AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR) |
| emitted.add(child) |
| graph.AddArc(package, child) |
| |
| # Add the package in its own subgraph. If we didn't have children, make it |
| # a sink |
| if children_subgraph: |
| rank = 'same' |
| else: |
| rank = 'sink' |
| package_subgraph = graph.AddNewSubgraph(rank) |
| AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR) |
| emitted.add(package) |
| |
| # 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 |
| AddNodeToSubgraph(graph, dep_node, options, color) |
| for j in dep_node['rev_deps']: |
| graph.AddArc(j, dep) |
| |
| return graph.Gen() |
| |
| |
| 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) |
| |
| filename = os.path.join(options.output_dir, |
| GetOutputBaseName(deps_map[package], options)) |
| |
| save_dot_filename = None |
| if options.save_dot: |
| save_dot_filename = filename + '.dot' |
| |
| dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename) |
| |
| |
| 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() |