From 305e1361f7c02794bd587a700cf7f818901bca6e Mon Sep 17 00:00:00 2001 From: Brian Harring Date: Thu, 23 Aug 2012 08:16:57 -0700 Subject: [PATCH] Move cros_deps_diff and cros_generate_deps_grah into chromite. The core util they rely on, cros_extract_deps has been over yonder for a while, thus complete the move. CQ-DEPEND=CL:31218 BUG=chromium-os:33829 TEST=manual invocation of the scripts in question. Change-Id: I13d7f82f74ef3551f6efcbaf361a34b8847fa4e0 Reviewed-on: https://gerrit.chromium.org/gerrit/31217 Tested-by: Brian Harring Reviewed-by: Mike Frysinger Reviewed-by: Antoine Labour Commit-Ready: Brian Harring --- cros_deps_diff | 182 -------------------------------------- cros_generate_deps_graphs | 152 ------------------------------- dot_helper.py | 116 ------------------------ 3 files changed, 450 deletions(-) delete mode 100755 cros_deps_diff delete mode 100755 cros_generate_deps_graphs delete mode 100644 dot_helper.py diff --git a/cros_deps_diff b/cros_deps_diff deleted file mode 100755 index b18a92ab8f..0000000000 --- a/cros_deps_diff +++ /dev/null @@ -1,182 +0,0 @@ -#!/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() diff --git a/cros_generate_deps_graphs b/cros_generate_deps_graphs deleted file mode 100755 index a8c17d4609..0000000000 --- a/cros_generate_deps_graphs +++ /dev/null @@ -1,152 +0,0 @@ -#!/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 pretty dependency graphs for Chrome OS packages.""" - -import dot_helper -import optparse -import os -import sys - - -NORMAL_COLOR = 'black' -TARGET_COLOR = 'red' -SEED_COLOR = 'green' -CHILD_COLOR = 'grey' - - -def GetReverseDependencyClosure(full_name, deps_map): - """Gets the closure of the reverse dependencies of a node. - - Walks the tree along all the reverse dependency paths to find all the nodes - that transitively depend on the input node.""" - s = set() - def GetClosure(name): - s.add(name) - node = deps_map[name] - for dep in node['rev_deps']: - if dep in s: - continue - GetClosure(dep) - - GetClosure(full_name) - return s - - -def GetOutputBaseName(node, options): - """Gets the basename of the output file for a node.""" - return '%s_%s-%s.%s' % (node['category'], node['name'], node['version'], - options.format) - - -def AddNodeToSubgraph(subgraph, node, options, color): - """Gets the dot definition for a node.""" - name = node['full_name'] - href = None - if options.link: - filename = GetOutputBaseName(node, options) - href = '%s%s' % (options.base_url, filename) - subgraph.AddNode(name, name, color, href) - - - -def GenerateDotGraph(package, deps_map, options): - """Generates the dot source for the dependency graph leading to a node. - - The output is a list of lines.""" - deps = GetReverseDependencyClosure(package, deps_map) - node = deps_map[package] - - # Keep track of all the emitted nodes so that we don't issue multiple - # definitions - emitted = set() - - graph = dot_helper.Graph(package) - - # Add all the children if we want them, all of them in their own subgraph, - # as a sink. Keep the arcs outside of the subgraph though (it generates - # better layout). - children_subgraph = None - if options.children and node['deps']: - children_subgraph = graph.AddNewSubgraph('sink') - for child in node['deps']: - child_node = deps_map[child] - AddNodeToSubgraph(children_subgraph, child_node, options, CHILD_COLOR) - emitted.add(child) - graph.AddArc(package, child) - - # Add the package in its own subgraph. If we didn't have children, make it - # a sink - if children_subgraph: - rank = 'same' - else: - rank = 'sink' - package_subgraph = graph.AddNewSubgraph(rank) - AddNodeToSubgraph(package_subgraph, node, options, TARGET_COLOR) - emitted.add(package) - - # Add all the other nodes, as well as all the arcs. - for dep in deps: - dep_node = deps_map[dep] - if not dep in emitted: - color = NORMAL_COLOR - if dep_node['action'] == 'seed': - color = SEED_COLOR - AddNodeToSubgraph(graph, dep_node, options, color) - for j in dep_node['rev_deps']: - graph.AddArc(j, dep) - - return graph.Gen() - - -def GenerateImages(input, options): - """Generate the output images for all the nodes in the input.""" - deps_map = eval(input.read()) - - for package in deps_map: - lines = GenerateDotGraph(package, deps_map, options) - - filename = os.path.join(options.output_dir, - GetOutputBaseName(deps_map[package], options)) - - save_dot_filename = None - if options.save_dot: - save_dot_filename = filename + '.dot' - - dot_helper.GenerateImage(lines, filename, options.format, save_dot_filename) - - -def main(): - parser = optparse.OptionParser(usage='usage: %prog [options] input') - 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('-c', '--children', action='store_true', - help='Also add children.') - parser.add_option('-l', '--link', action='store_true', - help='Embed links.') - parser.add_option('-b', '--base-url', default='', - help='Base url for links.') - parser.add_option('-s', '--save-dot', action='store_true', - help='Save dot files.') - (options, inputs) = parser.parse_args() - - try: - os.makedirs(options.output_dir) - except OSError: - # The directory already exists. - pass - - if not inputs: - GenerateImages(sys.stdin, options) - else: - for i in inputs: - file = open(i) - GenerateImages(file, options) - file.close() - -if __name__ == '__main__': - main() diff --git a/dot_helper.py b/dot_helper.py deleted file mode 100644 index d22f901f92..0000000000 --- a/dot_helper.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/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. - -"""Helper functions for building graphs with dot.""" - -import subprocess - -class Subgraph(object): - """A subgraph in dot. Contains nodes, arcs, and other subgraphs.""" - - _valid_ranks = set(['source', 'sink', 'same', 'min', 'max', None]) - - def __init__(self, rank=None): - self.SetRank(rank) - self._nodes = [] - self._subgraphs = [] - self._arcs = set() - - def SetRank(self, rank): - """Sets the rank for the nodes in the graph. - - Can be one of 'source', 'sink', 'same', 'min', 'max' or None. See dot - documentation at http://www.graphviz.org/Documentation.php for exact - semantics.""" - assert(rank in self._valid_ranks) - self._rank = rank - - def AddNode(self, id, name=None, color=None, href=None): - """Adds a node to the subgraph.""" - tags = {} - if name: - tags['label'] = name - if color: - tags['color'] = color - tags['fontcolor'] = color - if href: - tags['href'] = href - self._nodes.append({'id': id, 'tags': tags}) - - def AddSubgraph(self, subgraph): - """Adds a subgraph to the subgraph.""" - self._subgraphs.append(subgraph) - - def AddNewSubgraph(self, rank=None): - """Adds a new subgraph to the subgraph. The new subgraph is returned.""" - subgraph = Subgraph(rank) - self.AddSubgraph(subgraph) - return subgraph - - def AddArc(self, node_from, node_to): - """Adds an arc between two nodes.""" - self._arcs.add((node_from, node_to)) - - def _GenNodes(self): - """Generates the code for all the nodes.""" - lines = [] - for node in self._nodes: - tags = ['%s="%s"' % (k, v) for (k, v) in node['tags'].iteritems()] - lines.append('"%s" [%s];' % (node['id'], ', '.join(tags))) - return lines - - def _GenSubgraphs(self): - """Generates the code for all the subgraphs contained in this subgraph.""" - lines = [] - for subgraph in self._subgraphs: - lines += subgraph.Gen(); - return lines - - def _GenArcs(self): - """Generates the code for all the arcs.""" - lines = [] - for node_from, node_to in self._arcs: - lines.append('"%s" -> "%s";' % (node_from, node_to)) - return lines - - def _GenInner(self): - """Generates the code for the inner contents of the subgraph.""" - lines = [] - if self._rank: - lines.append('rank=%s;' % self._rank) - lines += self._GenSubgraphs() - lines += self._GenNodes() - lines += self._GenArcs() - return lines - - def Gen(self): - """Generates the code for the subgraph.""" - return ['subgraph {'] + self._GenInner() + ['}'] - - -class Graph(Subgraph): - """A top-level graph in dot. It's basically a subgraph with a name.""" - - def __init__(self, name): - Subgraph.__init__(self) - self._name = name - - def Gen(self): - """Generates the code for the graph.""" - return ['digraph "%s" {' % self._name, - 'graph [name="%s"];' % self._name] + self._GenInner() + ['}'] - - -def GenerateImage(lines, filename, format='svg', save_dot_filename=None): - """Generates the image by calling dot on the input lines.""" - data = '\n'.join(lines) - proc = subprocess.Popen(['dot', '-T' + format, '-o' + filename], - stdin=subprocess.PIPE) - proc.communicate(data) - - if save_dot_filename: - file = open(save_dot_filename, 'w') - file.write(data) - file.close()