mirror of
https://github.com/flatcar/scripts.git
synced 2025-09-24 23:21:17 +02:00
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:
parent
542cb53e9d
commit
305e1361f7
182
cros_deps_diff
182
cros_deps_diff
@ -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()
|
|
@ -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()
|
|
116
dot_helper.py
116
dot_helper.py
@ -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()
|
|
Loading…
x
Reference in New Issue
Block a user