| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Generates pretty dependency graphs for Chrome OS packages.""" | 6 """Generates pretty dependency graphs for Chrome OS packages.""" |
| 7 | 7 |
| 8 import dot_helper |
| 8 import optparse | 9 import optparse |
| 9 import os | 10 import os |
| 10 import subprocess | |
| 11 import sys | 11 import sys |
| 12 | 12 |
| 13 | 13 |
| 14 NORMAL_COLOR = 'black' | 14 NORMAL_COLOR = 'black' |
| 15 TARGET_COLOR = 'red' | 15 TARGET_COLOR = 'red' |
| 16 SEED_COLOR = 'green' | 16 SEED_COLOR = 'green' |
| 17 CHILD_COLOR = 'grey' | 17 CHILD_COLOR = 'grey' |
| 18 | 18 |
| 19 | 19 |
| 20 def GetReverseDependencyClosure(full_name, deps_map): | 20 def GetReverseDependencyClosure(full_name, deps_map): |
| (...skipping 13 matching lines...) Expand all Loading... |
| 34 GetClosure(full_name) | 34 GetClosure(full_name) |
| 35 return s | 35 return s |
| 36 | 36 |
| 37 | 37 |
| 38 def GetOutputBaseName(node, options): | 38 def GetOutputBaseName(node, options): |
| 39 """Gets the basename of the output file for a node.""" | 39 """Gets the basename of the output file for a node.""" |
| 40 return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'], | 40 return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'], |
| 41 options.format) | 41 options.format) |
| 42 | 42 |
| 43 | 43 |
| 44 def GetNodeLines(node, options, color): | 44 def AddNodeToSubgraph(subgraph, node, options, color): |
| 45 """Gets the dot definition for a node.""" | 45 """Gets the dot definition for a node.""" |
| 46 name = node['full_name'] | 46 name = node['full_name'] |
| 47 tags = ['label="%s (%s)"' % (name, node['action']), | 47 href = None |
| 48 'color="%s"' % color, | |
| 49 'fontcolor="%s"' % color] | |
| 50 if options.link: | 48 if options.link: |
| 51 filename = GetOutputBaseName(node, options) | 49 filename = GetOutputBaseName(node, options) |
| 52 tags.append('href="%s%s"' % (options.base_url, filename)) | 50 href = '%s%s' % (options.base_url, filename) |
| 53 return ['"%s" [%s];' % (name, ', '.join(tags))] | 51 subgraph.AddNode(name, name, color, href) |
| 54 | 52 |
| 55 | 53 |
| 56 def GetReverseDependencyArcLines(node, options): | |
| 57 """Gets the dot definitions for the arcs leading to a node.""" | |
| 58 lines = [] | |
| 59 name = node['full_name'] | |
| 60 for j in node['rev_deps']: | |
| 61 lines.append('"%s" -> "%s";' % (j, name)) | |
| 62 return lines | |
| 63 | |
| 64 | 54 |
| 65 def GenerateDotGraph(package, deps_map, options): | 55 def GenerateDotGraph(package, deps_map, options): |
| 66 """Generates the dot source for the dependency graph leading to a node. | 56 """Generates the dot source for the dependency graph leading to a node. |
| 67 | 57 |
| 68 The output is a list of lines.""" | 58 The output is a list of lines.""" |
| 69 deps = GetReverseDependencyClosure(package, deps_map) | 59 deps = GetReverseDependencyClosure(package, deps_map) |
| 70 node = deps_map[package] | 60 node = deps_map[package] |
| 71 | 61 |
| 72 # Keep track of all the emitted nodes so that we don't issue multiple | 62 # Keep track of all the emitted nodes so that we don't issue multiple |
| 73 # definitions | 63 # definitions |
| 74 emitted = set() | 64 emitted = set() |
| 75 | 65 |
| 76 lines = ['digraph dep {', | 66 graph = dot_helper.Graph(package) |
| 77 'graph [name="%s"];' % package] | |
| 78 | 67 |
| 79 # Add all the children if we want them, all of them in their own subgraph, | 68 # Add all the children if we want them, all of them in their own subgraph, |
| 80 # as a sink. Keep the arcs outside of the subgraph though (it generates | 69 # as a sink. Keep the arcs outside of the subgraph though (it generates |
| 81 # better layout). | 70 # better layout). |
| 82 has_children = False | 71 children_subgraph = None |
| 83 if options.children and node['deps']: | 72 if options.children and node['deps']: |
| 84 has_children = True | 73 children_subgraph = graph.AddNewSubgraph('sink') |
| 85 lines += ['subgraph {', | |
| 86 'rank=sink;'] | |
| 87 arc_lines = [] | |
| 88 for child in node['deps']: | 74 for child in node['deps']: |
| 89 child_node = deps_map[child] | 75 child_node = deps_map[child] |
| 90 lines += GetNodeLines(child_node, options, CHILD_COLOR) | 76 AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR) |
| 91 emitted.add(child) | 77 emitted.add(child) |
| 92 # If child is in the rev_deps, we'll get the arc later. | 78 graph.AddArc(package, child) |
| 93 if not child in node['rev_deps']: | |
| 94 arc_lines.append('"%s" -> "%s";' % (package, child)) | |
| 95 lines += ['}'] | |
| 96 lines += arc_lines | |
| 97 | 79 |
| 98 # Add the package in its own subgraph. If we didn't have children, make it | 80 # Add the package in its own subgraph. If we didn't have children, make it |
| 99 # a sink | 81 # a sink |
| 100 lines += ['subgraph {'] | 82 if children_subgraph: |
| 101 if has_children: | 83 rank = 'same' |
| 102 lines += ['rank=same;'] | |
| 103 else: | 84 else: |
| 104 lines += ['rank=sink;'] | 85 rank = 'sink' |
| 105 lines += GetNodeLines(node, options, TARGET_COLOR) | 86 package_subgraph = graph.AddNewSubgraph(rank) |
| 87 AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR) |
| 106 emitted.add(package) | 88 emitted.add(package) |
| 107 lines += ['}'] | |
| 108 | 89 |
| 109 # Add all the other nodes, as well as all the arcs. | 90 # Add all the other nodes, as well as all the arcs. |
| 110 for dep in deps: | 91 for dep in deps: |
| 111 dep_node = deps_map[dep] | 92 dep_node = deps_map[dep] |
| 112 if not dep in emitted: | 93 if not dep in emitted: |
| 113 color = NORMAL_COLOR | 94 color = NORMAL_COLOR |
| 114 if dep_node['action'] == 'seed': | 95 if dep_node['action'] == 'seed': |
| 115 color = SEED_COLOR | 96 color = SEED_COLOR |
| 116 lines += GetNodeLines(dep_node, options, color) | 97 AddNodeToSubgraph(graph, dep_node, options, color) |
| 117 lines += GetReverseDependencyArcLines(dep_node, options) | 98 for j in dep_node['rev_deps']: |
| 99 graph.AddArc(j, dep) |
| 118 | 100 |
| 119 lines += ['}'] | 101 return graph.Gen() |
| 120 return lines | |
| 121 | 102 |
| 122 | 103 |
| 123 def GenerateImages(input, options): | 104 def GenerateImages(input, options): |
| 124 """Generate the output images for all the nodes in the input.""" | 105 """Generate the output images for all the nodes in the input.""" |
| 125 deps_map = eval(input.read()) | 106 deps_map = eval(input.read()) |
| 126 | 107 |
| 127 for package in deps_map: | 108 for package in deps_map: |
| 128 lines = GenerateDotGraph(package, deps_map, options) | 109 lines = GenerateDotGraph(package, deps_map, options) |
| 129 data = '\n'.join(lines) | |
| 130 | 110 |
| 131 filename = os.path.join(options.output_dir, | 111 filename = os.path.join(options.output_dir, |
| 132 GetOutputBaseName(deps_map[package], options)) | 112 GetOutputBaseName(deps_map[package], options)) |
| 133 | 113 |
| 134 # Send the source to dot. | 114 save_dot_filename = None |
| 135 proc = subprocess.Popen(['dot', '-T' + options.format, '-o' + filename], | 115 if options.save_dot: |
| 136 stdin=subprocess.PIPE) | 116 save_dot_filename = filename + '.dot' |
| 137 proc.communicate(data) | |
| 138 | 117 |
| 139 if options.save_dot: | 118 dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename) |
| 140 file = open(filename + '.dot', 'w') | |
| 141 file.write(data) | |
| 142 file.close() | |
| 143 | 119 |
| 144 | 120 |
| 145 def main(): | 121 def main(): |
| 146 parser = optparse.OptionParser(usage='usage: %prog [options] input') | 122 parser = optparse.OptionParser(usage='usage: %prog [options] input') |
| 147 parser.add_option('-f', '--format', default='svg', | 123 parser.add_option('-f', '--format', default='svg', |
| 148 help='Dot output format (png, svg, etc.).') | 124 help='Dot output format (png, svg, etc.).') |
| 149 parser.add_option('-o', '--output-dir', default='.', | 125 parser.add_option('-o', '--output-dir', default='.', |
| 150 help='Output directory.') | 126 help='Output directory.') |
| 151 parser.add_option('-c', '--children', action='store_true', | 127 parser.add_option('-c', '--children', action='store_true', |
| 152 help='Also add children.') | 128 help='Also add children.') |
| (...skipping 14 matching lines...) Expand all Loading... |
| 167 if not inputs: | 143 if not inputs: |
| 168 GenerateImages(sys.stdin, options) | 144 GenerateImages(sys.stdin, options) |
| 169 else: | 145 else: |
| 170 for i in inputs: | 146 for i in inputs: |
| 171 file = open(i) | 147 file = open(i) |
| 172 GenerateImages(file, options) | 148 GenerateImages(file, options) |
| 173 file.close() | 149 file.close() |
| 174 | 150 |
| 175 if __name__ == '__main__': | 151 if __name__ == '__main__': |
| 176 main() | 152 main() |
| OLD | NEW |