Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(146)

Side by Side Diff: cros_deps_diff

Issue 2870042: add cros_deps_diff tool that creates dependency graph diffs (Closed) Base URL: ssh://git@chromiumos-git//crosutils.git
Patch Set: address review comments Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
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
4 # found in the LICENSE file.
5
6 """Generates dependency graph diffs.
7
8 As an input it takes 2 or more dependency graphs output from cros_extract_deps
9 and it finds all divergent packages (packages whose versions differ between
10 some of these dependency graphs) and outputs graphs that trace the divergence
11 in the dependency trees until common packages are found.
12 """
13
14 import dot_helper
15 import optparse
16 import os
17
18 NORMAL_COLOR = 'black'
19 BASE_COLORS = ['red', 'green', 'blue']
20
21
22 def UnversionedName(dep):
23 """Returns the name of the package, omitting the version."""
24 return '%s/%s' % (dep['category'], dep['name'])
25
26
27 def GetColor(index):
28 """Maps index to a color."""
29 try:
30 return BASE_COLORS[index]
31 except IndexError:
32 # Generate a color by splicing the bits to generate high contrast colors
33 index -= len(BASE_COLORS) - 1
34 chars = [0] * 3
35 for bit in xrange(0, 24):
36 chars[bit % 3] |= ((index >> bit) & 0x1) << (7-bit/3)
37 return "#%02x%02x%02x" % tuple(chars)
38
39
40 def GetReverseDependencyClosure(full_name, deps_map, divergent_set):
41 """Gets the closure of the reverse dependencies of a node.
42
43 Walks the tree along all the reverse dependency paths to find all the nodes
44 of the divergent set that transitively depend on the input node."""
45 s = set()
46 def GetClosure(name):
47 node = deps_map[name]
48 if UnversionedName(node) in divergent_set:
49 s.add(name)
50 for dep in node['rev_deps']:
51 if dep in s:
52 continue
53 GetClosure(dep)
54
55 GetClosure(full_name)
56 return s
57
58
59 def GetVersionMap(input_deps):
60 """Creates the version map for the input data.
61
62 The version map maps an unversioned package name to its corresponding
63 versioned name depending on the input dependency graph.
64
65 For every package, it maps the input data index to the full name (versioned)
66 of the package in that input data. E.g.
67 map['x11-base/xorg-server'] = {0:'x11-base/xorg-server-1.6.5-r203',
68 1:'x11-base/xorg-server-1.7.6-r8'}
69 """
70 version_map = {}
71 i = 0
72 for deps_map in input_deps:
73 for full_name, dep in deps_map.iteritems():
74 pkg = UnversionedName(dep)
75 entry = version_map.setdefault(pkg, {})
76 entry[i] = full_name
77 i += 1
78 return version_map
79
80
81 def GetDivergentSet(version_map, count):
82 """Gets the set of divergent packages.
83
84 Divergent packages are those that have a different version among the input
85 dependency graphs (or missing version altogether)."""
86 divergent_set = set()
87 for pkg, value in version_map.iteritems():
88 if len(value.keys()) != count or len(set(value.values())) > 1:
89 # The package doesn't exist for at least one ot the input, or there are
90 # more than 2 versions.
91 divergent_set.add(pkg)
92 return divergent_set
93
94
95 def BuildDependencyGraph(pkg, input_deps, version_map, divergent_set):
96 graph = dot_helper.Graph(pkg)
97
98 # A subgraph for the divergent package we're considering. Add all its
99 # versions as a sink.
100 pkg_subgraph = graph.AddNewSubgraph('sink')
101
102 # The outer packages are those that aren't divergent but depend on a
103 # divergent package. Add them in their own subgraph, as sources.
104 outer_subgraph = graph.AddNewSubgraph('source')
105
106 emitted = set()
107 for i in xrange(0, len(input_deps)):
108 try:
109 pkg_name = version_map[pkg][i]
110 except KeyError:
111 continue
112
113 color = GetColor(i)
114
115 if pkg_name not in emitted:
116 pkg_subgraph.AddNode(pkg_name, pkg_name, color, None)
117 emitted.add(pkg_name)
118
119 # Add one subgraph per version for generally better layout.
120 subgraph = graph.AddNewSubgraph()
121
122 nodes = GetReverseDependencyClosure(pkg_name, input_deps[i], divergent_set)
123 for node_name in nodes:
124 if node_name not in emitted:
125 subgraph.AddNode(node_name, node_name, color, None)
126 emitted.add(node_name)
127
128 # Add outer packages, and all the arcs.
129 for dep in input_deps[i][node_name]['rev_deps']:
130 dep_node = input_deps[i][dep]
131 if UnversionedName(dep_node) not in divergent_set and dep not in emitted :
132 outer_subgraph.AddNode(dep, dep, NORMAL_COLOR, None)
133 emitted.add(dep)
134 graph.AddArc(dep, node_name)
135
136 return graph
137
138
139 def main():
140 parser = optparse.OptionParser(
141 usage='usage: %prog [options] input1 input2...')
142 parser.add_option('-f', '--format', default='svg',
143 help='Dot output format (png, svg, etc.).')
144 parser.add_option('-o', '--output-dir', default='.',
145 help='Output directory.')
146 parser.add_option('-s', '--save-dot', action='store_true',
147 help='Save dot files.')
148 options, inputs = parser.parse_args()
149
150 input_deps = []
151 for i in inputs:
152 file = open(i)
153 input_deps.append(eval(file.read()))
154 file.close()
155
156 version_map = GetVersionMap(input_deps)
157 divergent_set = GetDivergentSet(version_map, len(input_deps))
158
159 # Get all the output directories
160 all_dirs = set(os.path.dirname(pkg) for pkg in divergent_set)
161
162 for i in all_dirs:
163 try:
164 os.makedirs(os.path.join(options.output_dir, i))
165 except OSError:
166 # The directory already exists.
167 pass
168
169 for pkg in divergent_set:
170 filename = os.path.join(options.output_dir, pkg) + '.' + options.format
171
172 save_dot_filename = None
173 if options.save_dot:
174 save_dot_filename = filename + '.dot'
175
176 graph = BuildDependencyGraph(pkg, input_deps, version_map, divergent_set)
177 lines = graph.Gen()
178 dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename)
179
180
181 if __name__ == '__main__':
182 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698