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 <ferringb@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Antoine Labour <piman@chromium.org>
Commit-Ready: Brian Harring <ferringb@chromium.org>
This commit is contained in:
Brian Harring 2012-08-23 08:16:57 -07:00 committed by Gerrit
parent 542cb53e9d
commit 305e1361f7
3 changed files with 0 additions and 450 deletions

View File

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

View File

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

View File

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