blob: b7dffb2d63b6746016f3a66ede6d4ba90dc44234 [file] [log] [blame]
Brian Harringf90d82a2012-08-23 08:30:31 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generates pretty dependency graphs for Chrome OS packages."""
6
7import json
8import optparse
9import os
10import sys
11
12from chromite.lib import dot_helper
13
14
15NORMAL_COLOR = 'black'
16TARGET_COLOR = 'red'
17SEED_COLOR = 'green'
18CHILD_COLOR = 'grey'
19
20
21def GetReverseDependencyClosure(full_name, deps_map):
22 """Gets the closure of the reverse dependencies of a node.
23
24 Walks the tree along all the reverse dependency paths to find all the nodes
25 that transitively depend on the input node."""
26 s = set()
27 def GetClosure(name):
28 s.add(name)
29 node = deps_map[name]
30 for dep in node['rev_deps']:
31 if dep in s:
32 continue
33 GetClosure(dep)
34
35 GetClosure(full_name)
36 return s
37
38
39def GetOutputBaseName(node, options):
40 """Gets the basename of the output file for a node."""
41 return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'],
42 options.format)
43
44
45def AddNodeToSubgraph(subgraph, node, options, color):
46 """Gets the dot definition for a node."""
47 name = node['full_name']
48 href = None
49 if options.link:
50 filename = GetOutputBaseName(node, options)
51 href = '%s%s' % (options.base_url, filename)
52 subgraph.AddNode(name, name, color, href)
53
54
55
56def GenerateDotGraph(package, deps_map, options):
57 """Generates the dot source for the dependency graph leading to a node.
58
59 The output is a list of lines."""
60 deps = GetReverseDependencyClosure(package, deps_map)
61 node = deps_map[package]
62
63 # Keep track of all the emitted nodes so that we don't issue multiple
64 # definitions
65 emitted = set()
66
67 graph = dot_helper.Graph(package)
68
69 # Add all the children if we want them, all of them in their own subgraph,
70 # as a sink. Keep the arcs outside of the subgraph though (it generates
71 # better layout).
72 children_subgraph = None
73 if options.children and node['deps']:
74 children_subgraph = graph.AddNewSubgraph('sink')
75 for child in node['deps']:
76 child_node = deps_map[child]
77 AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR)
78 emitted.add(child)
79 graph.AddArc(package, child)
80
81 # Add the package in its own subgraph. If we didn't have children, make it
82 # a sink
83 if children_subgraph:
84 rank = 'same'
85 else:
86 rank = 'sink'
87 package_subgraph = graph.AddNewSubgraph(rank)
88 AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR)
89 emitted.add(package)
90
91 # Add all the other nodes, as well as all the arcs.
92 for dep in deps:
93 dep_node = deps_map[dep]
94 if not dep in emitted:
95 color = NORMAL_COLOR
96 if dep_node['action'] == 'seed':
97 color = SEED_COLOR
98 AddNodeToSubgraph(graph, dep_node, options, color)
99 for j in dep_node['rev_deps']:
100 graph.AddArc(j, dep)
101
102 return graph.Gen()
103
104
105def GenerateImages(data, options):
106 """Generate the output images for all the nodes in the input."""
107 deps_map = json.loads(data)
108
109 for package in deps_map:
110 lines = GenerateDotGraph(package, deps_map, options)
111
112 filename = os.path.join(options.output_dir,
113 GetOutputBaseName(deps_map[package], options))
114
115 save_dot_filename = None
116 if options.save_dot:
117 save_dot_filename = filename + '.dot'
118
119 dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename)
120
121
122def main(argv):
123 parser = optparse.OptionParser(usage='usage: %prog [options] input')
124 parser.add_option('-f', '--format', default='svg',
125 help='Dot output format (png, svg, etc.).')
126 parser.add_option('-o', '--output-dir', default='.',
127 help='Output directory.')
128 parser.add_option('-c', '--children', action='store_true',
129 help='Also add children.')
130 parser.add_option('-l', '--link', action='store_true',
131 help='Embed links.')
132 parser.add_option('-b', '--base-url', default='',
133 help='Base url for links.')
134 parser.add_option('-s', '--save-dot', action='store_true',
135 help='Save dot files.')
136 (options, inputs) = parser.parse_args(argv)
137
138 try:
139 os.makedirs(options.output_dir)
140 except OSError:
141 # The directory already exists.
142 pass
143
144 if not inputs:
145 GenerateImages(sys.stdin.read(), options)
146 else:
147 for i in inputs:
148 with open(i) as handle:
149 GenerateImages(handle.read(), options)