1 # 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.
5 """Generates pretty dependency graphs for Chrome OS packages."""
12 from chromite.lib import dot_helper
15 NORMAL_COLOR = 'black'
21 def GetReverseDependencyClosure(full_name, deps_map):
22 """Gets the closure of the reverse dependencies of a node.
24 Walks the tree along all the reverse dependency paths to find all the nodes
25 that transitively depend on the input node.
31 for dep in node['rev_deps']:
40 def GetOutputBaseName(node, options):
41 """Gets the basename of the output file for a node."""
42 return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'],
46 def AddNodeToSubgraph(subgraph, node, options, color):
47 """Gets the dot definition for a node."""
48 name = node['full_name']
51 filename = GetOutputBaseName(node, options)
52 href = '%s%s' % (options.base_url, filename)
53 subgraph.AddNode(name, name, color, href)
57 def GenerateDotGraph(package, deps_map, options):
58 """Generates the dot source for the dependency graph leading to a node.
60 The output is a list of lines.
62 deps = GetReverseDependencyClosure(package, deps_map)
63 node = deps_map[package]
65 # Keep track of all the emitted nodes so that we don't issue multiple
69 graph = dot_helper.Graph(package)
71 # Add all the children if we want them, all of them in their own subgraph,
72 # as a sink. Keep the arcs outside of the subgraph though (it generates
74 children_subgraph = None
75 if options.children and node['deps']:
76 children_subgraph = graph.AddNewSubgraph('sink')
77 for child in node['deps']:
78 child_node = deps_map[child]
79 AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR)
81 graph.AddArc(package, child)
83 # Add the package in its own subgraph. If we didn't have children, make it
89 package_subgraph = graph.AddNewSubgraph(rank)
90 AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR)
93 # Add all the other nodes, as well as all the arcs.
95 dep_node = deps_map[dep]
96 if not dep in emitted:
98 if dep_node['action'] == 'seed':
100 AddNodeToSubgraph(graph, dep_node, options, color)
101 for j in dep_node['rev_deps']:
107 def GenerateImages(data, options):
108 """Generate the output images for all the nodes in the input."""
109 deps_map = json.loads(data)
111 for package in deps_map:
112 lines = GenerateDotGraph(package, deps_map, options)
114 filename = os.path.join(options.output_dir,
115 GetOutputBaseName(deps_map[package], options))
117 save_dot_filename = None
119 save_dot_filename = filename + '.dot'
121 dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename)
125 parser = optparse.OptionParser(usage='usage: %prog [options] input')
126 parser.add_option('-f', '--format', default='svg',
127 help='Dot output format (png, svg, etc.).')
128 parser.add_option('-o', '--output-dir', default='.',
129 help='Output directory.')
130 parser.add_option('-c', '--children', action='store_true',
131 help='Also add children.')
132 parser.add_option('-l', '--link', action='store_true',
134 parser.add_option('-b', '--base-url', default='',
135 help='Base url for links.')
136 parser.add_option('-s', '--save-dot', action='store_true',
137 help='Save dot files.')
138 (options, inputs) = parser.parse_args(argv)
141 os.makedirs(options.output_dir)
143 # The directory already exists.
147 GenerateImages(sys.stdin.read(), options)
150 with open(i) as handle:
151 GenerateImages(handle.read(), options)