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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()
« 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