| 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()
|
|
|