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