mirror of
https://github.com/flatcar/scripts.git
synced 2025-08-07 21:16:57 +02:00
This will be re-used by a separate tool Review URL: http://codereview.chromium.org/2859039
153 lines
4.4 KiB
Python
Executable File
153 lines
4.4 KiB
Python
Executable File
#!/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()
|