Index: cros_deps_diff |
diff --git a/cros_deps_diff b/cros_deps_diff |
new file mode 100755 |
index 0000000000000000000000000000000000000000..b18a92ab8f9ddc84e93367442896f91b43035629 |
--- /dev/null |
+++ b/cros_deps_diff |
@@ -0,0 +1,182 @@ |
+#!/usr/bin/python |
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Generates dependency graph diffs. |
+ |
+As an input it takes 2 or more dependency graphs output from cros_extract_deps |
+and it finds all divergent packages (packages whose versions differ between |
+some of these dependency graphs) and outputs graphs that trace the divergence |
+in the dependency trees until common packages are found. |
+""" |
+ |
+import dot_helper |
+import optparse |
+import os |
+ |
+NORMAL_COLOR = 'black' |
+BASE_COLORS = ['red', 'green', 'blue'] |
+ |
+ |
+def UnversionedName(dep): |
+ """Returns the name of the package, omitting the version.""" |
+ return '%s/%s' % (dep['category'], dep['name']) |
+ |
+ |
+def GetColor(index): |
+ """Maps index to a color.""" |
+ try: |
+ return BASE_COLORS[index] |
+ except IndexError: |
+ # Generate a color by splicing the bits to generate high contrast colors |
+ index -= len(BASE_COLORS) - 1 |
+ chars = [0] * 3 |
+ for bit in xrange(0, 24): |
+ chars[bit % 3] |= ((index >> bit) & 0x1) << (7-bit/3) |
+ return "#%02x%02x%02x" % tuple(chars) |
+ |
+ |
+def GetReverseDependencyClosure(full_name, deps_map, divergent_set): |
+ """Gets the closure of the reverse dependencies of a node. |
+ |
+ Walks the tree along all the reverse dependency paths to find all the nodes |
+ of the divergent set that transitively depend on the input node.""" |
+ s = set() |
+ def GetClosure(name): |
+ node = deps_map[name] |
+ if UnversionedName(node) in divergent_set: |
+ s.add(name) |
+ for dep in node['rev_deps']: |
+ if dep in s: |
+ continue |
+ GetClosure(dep) |
+ |
+ GetClosure(full_name) |
+ return s |
+ |
+ |
+def GetVersionMap(input_deps): |
+ """Creates the version map for the input data. |
+ |
+ The version map maps an unversioned package name to its corresponding |
+ versioned name depending on the input dependency graph. |
+ |
+ For every package, it maps the input data index to the full name (versioned) |
+ of the package in that input data. E.g. |
+ map['x11-base/xorg-server'] = {0:'x11-base/xorg-server-1.6.5-r203', |
+ 1:'x11-base/xorg-server-1.7.6-r8'} |
+ """ |
+ version_map = {} |
+ i = 0 |
+ for deps_map in input_deps: |
+ for full_name, dep in deps_map.iteritems(): |
+ pkg = UnversionedName(dep) |
+ entry = version_map.setdefault(pkg, {}) |
+ entry[i] = full_name |
+ i += 1 |
+ return version_map |
+ |
+ |
+def GetDivergentSet(version_map, count): |
+ """Gets the set of divergent packages. |
+ |
+ Divergent packages are those that have a different version among the input |
+ dependency graphs (or missing version altogether).""" |
+ divergent_set = set() |
+ for pkg, value in version_map.iteritems(): |
+ if len(value.keys()) != count or len(set(value.values())) > 1: |
+ # The package doesn't exist for at least one ot the input, or there are |
+ # more than 2 versions. |
+ divergent_set.add(pkg) |
+ return divergent_set |
+ |
+ |
+def BuildDependencyGraph(pkg, input_deps, version_map, divergent_set): |
+ graph = dot_helper.Graph(pkg) |
+ |
+ # A subgraph for the divergent package we're considering. Add all its |
+ # versions as a sink. |
+ pkg_subgraph = graph.AddNewSubgraph('sink') |
+ |
+ # The outer packages are those that aren't divergent but depend on a |
+ # divergent package. Add them in their own subgraph, as sources. |
+ outer_subgraph = graph.AddNewSubgraph('source') |
+ |
+ emitted = set() |
+ for i in xrange(0, len(input_deps)): |
+ try: |
+ pkg_name = version_map[pkg][i] |
+ except KeyError: |
+ continue |
+ |
+ color = GetColor(i) |
+ |
+ if pkg_name not in emitted: |
+ pkg_subgraph.AddNode(pkg_name, pkg_name, color, None) |
+ emitted.add(pkg_name) |
+ |
+ # Add one subgraph per version for generally better layout. |
+ subgraph = graph.AddNewSubgraph() |
+ |
+ nodes = GetReverseDependencyClosure(pkg_name, input_deps[i], divergent_set) |
+ for node_name in nodes: |
+ if node_name not in emitted: |
+ subgraph.AddNode(node_name, node_name, color, None) |
+ emitted.add(node_name) |
+ |
+ # Add outer packages, and all the arcs. |
+ for dep in input_deps[i][node_name]['rev_deps']: |
+ dep_node = input_deps[i][dep] |
+ if UnversionedName(dep_node) not in divergent_set and dep not in emitted: |
+ outer_subgraph.AddNode(dep, dep, NORMAL_COLOR, None) |
+ emitted.add(dep) |
+ graph.AddArc(dep, node_name) |
+ |
+ return graph |
+ |
+ |
+def main(): |
+ parser = optparse.OptionParser( |
+ usage='usage: %prog [options] input1 input2...') |
+ parser.add_option('-f', '--format', default='svg', |
+ help='Dot output format (png, svg, etc.).') |
+ parser.add_option('-o', '--output-dir', default='.', |
+ help='Output directory.') |
+ parser.add_option('-s', '--save-dot', action='store_true', |
+ help='Save dot files.') |
+ options, inputs = parser.parse_args() |
+ |
+ input_deps = [] |
+ for i in inputs: |
+ file = open(i) |
+ input_deps.append(eval(file.read())) |
+ file.close() |
+ |
+ version_map = GetVersionMap(input_deps) |
+ divergent_set = GetDivergentSet(version_map, len(input_deps)) |
+ |
+ # Get all the output directories |
+ all_dirs = set(os.path.dirname(pkg) for pkg in divergent_set) |
+ |
+ for i in all_dirs: |
+ try: |
+ os.makedirs(os.path.join(options.output_dir, i)) |
+ except OSError: |
+ # The directory already exists. |
+ pass |
+ |
+ for pkg in divergent_set: |
+ filename = os.path.join(options.output_dir, pkg) + '.' + options.format |
+ |
+ save_dot_filename = None |
+ if options.save_dot: |
+ save_dot_filename = filename + '.dot' |
+ |
+ graph = BuildDependencyGraph(pkg, input_deps, version_map, divergent_set) |
+ lines = graph.Gen() |
+ dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename) |
+ |
+ |
+if __name__ == '__main__': |
+ main() |