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 |