blob: 4ff80ceb522a87aa831937ea338b86e20976f517 [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
Mike Frysinger02e1e072013-11-10 22:11:34 -050025 that transitively depend on the input node.
26 """
Brian Harringf90d82a2012-08-23 08:30:31 -070027 s = set()
28 def GetClosure(name):
29 s.add(name)
30 node = deps_map[name]
31 for dep in node['rev_deps']:
32 if dep in s:
33 continue
34 GetClosure(dep)
35
36 GetClosure(full_name)
37 return s
38
39
40def 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'],
43 options.format)
44
45
46def AddNodeToSubgraph(subgraph, node, options, color):
47 """Gets the dot definition for a node."""
48 name = node['full_name']
49 href = None
50 if options.link:
51 filename = GetOutputBaseName(node, options)
52 href = '%s%s' % (options.base_url, filename)
53 subgraph.AddNode(name, name, color, href)
54
55
56
57def GenerateDotGraph(package, deps_map, options):
58 """Generates the dot source for the dependency graph leading to a node.
59
Mike Frysinger02e1e072013-11-10 22:11:34 -050060 The output is a list of lines.
61 """
Brian Harringf90d82a2012-08-23 08:30:31 -070062 deps = GetReverseDependencyClosure(package, deps_map)
63 node = deps_map[package]
64
65 # Keep track of all the emitted nodes so that we don't issue multiple
66 # definitions
67 emitted = set()
68
69 graph = dot_helper.Graph(package)
70
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
73 # better layout).
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)
80 emitted.add(child)
81 graph.AddArc(package, child)
82
83 # Add the package in its own subgraph. If we didn't have children, make it
84 # a sink
85 if children_subgraph:
86 rank = 'same'
87 else:
88 rank = 'sink'
89 package_subgraph = graph.AddNewSubgraph(rank)
90 AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR)
91 emitted.add(package)
92
93 # Add all the other nodes, as well as all the arcs.
94 for dep in deps:
95 dep_node = deps_map[dep]
96 if not dep in emitted:
97 color = NORMAL_COLOR
98 if dep_node['action'] == 'seed':
99 color = SEED_COLOR
100 AddNodeToSubgraph(graph, dep_node, options, color)
101 for j in dep_node['rev_deps']:
102 graph.AddArc(j, dep)
103
104 return graph.Gen()
105
106
107def GenerateImages(data, options):
108 """Generate the output images for all the nodes in the input."""
109 deps_map = json.loads(data)
110
111 for package in deps_map:
112 lines = GenerateDotGraph(package, deps_map, options)
113
114 filename = os.path.join(options.output_dir,
115 GetOutputBaseName(deps_map[package], options))
116
117 save_dot_filename = None
118 if options.save_dot:
119 save_dot_filename = filename + '.dot'
120
121 dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename)
122
123
124def main(argv):
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',
133 help='Embed links.')
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)
139
140 try:
141 os.makedirs(options.output_dir)
142 except OSError:
143 # The directory already exists.
144 pass
145
146 if not inputs:
147 GenerateImages(sys.stdin.read(), options)
148 else:
149 for i in inputs:
150 with open(i) as handle:
151 GenerateImages(handle.read(), options)