# Copyright (c) 2009 Symbian Foundation Ltd
# This component and the accompanying materials are made available
# under the terms of the License "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Symbian Foundation Ltd - initial contribution.
#
# Contributors:
#
# Description:
# Walk all nodes in the dependency graph to create a dependency report.
"""Walk a dependency graph to find dependencies for a particular set of
components. This script uses the output of build_graph.py to trace
dependencies.
The directory_list refer to diretories in the source tree for which
you wish to trace dependencies. The script will find all components
in the graph file under these directories and trace dependencies from
that point.
"""
from optparse import OptionParser
from _common import Node
import sys
import pickle
import logging
__author__ = 'James Aley'
__email__ = 'jamesa@symbian.org'
__version__ = '1.0'
_LOG_FORMAT = '%(levelname)s: %(message)s'
logging.basicConfig(format=_LOG_FORMAT, level=logging.WARNING, stream=sys.stdout)
# Internalized graph object
graph = {}
# Report formatting
_REPORT_HEADER = """// Generated by get_deps.py
//
// Dependency information for:
//
%s
"""
_DEPENDENCY_FORMAT = """
// Required components:
%s
"""
_MISSING_FORMAT = """
// The following binary objects were referenced from the build files for
// components required by your specified root components. However, there
// were no build files for these objects found in the source tree parsing,
// so dependencies for them may be missing in the above listing.
%s
"""
def load_graph(path):
"""Return the internalized graph dictionary object.
"""
graph_file = None
graph_loaded = {}
try:
graph_file = open(path, 'rb')
except IOError, e:
logging.critical('Unable to open graph from file: %s: %s' % (path, repr(e)))
exit(1)
try:
graph_loaded = pickle.load(graph_file)
except Exception, e:
logging.critical('File %s does not contain a valid graph: %s' % (path, repr(e)))
return graph_loaded
def find_roots(root_dirs):
"""Return a list of root nodes from the graph for tracing, based on
the specified component directories in the root_dirs list.
"""
roots = []
for root in root_dirs:
for node in graph.keys():
if node.startswith(root.lower()):
if node not in roots:
roots.append(node)
return roots
def trace(root, visited = set()):
"""Return a list of components required to support root.
"""
node = graph[root]
visited |= set([node.node_path]) | set(node.dependencies)
for dep in node.dependencies:
if dep not in visited:
return trace(dep, visited)
return visited
def unresolved(deps):
"""Return a set of components with unknown dependencies from a
provided list of node names.
"""
unresolved = set()
for dep in deps:
node = graph[dep]
unresolved |= set(node.unresolved)
return unresolved
def report(out_path, roots, dependencies, missing):
"""Output the dependency information to file.
"""
# open report file
out_file = None
try:
out_file = open(out_path, 'w')
except IOError, e:
logging.critical('Unable to write report: %s' % (repr(e)))
exit(1)
# easier to read report with these sorted
roots.sort()
dependencies.sort()
missing.sort()
# format report
formatted_header = _REPORT_HEADER % ('\n'.join(['// %s' % (line, ) for line in roots]), )
formatted_body = _DEPENDENCY_FORMAT % ('\n'.join(dependencies))
formatted_missing = _MISSING_FORMAT % ('\n'.join(['// %s' % (line, ) for line in missing]), )
# write report
out_file.write(formatted_header)
out_file.write(formatted_body)
out_file.write(formatted_missing)
out_file.close()
if __name__ == '__main__':
# Options config
parser = OptionParser()
parser.set_description(__doc__)
parser.set_usage('python get_deps.py [options] directory_list')
parser.add_option('-g', '--graph', dest='graph_file',
help='File name to write the graph to.',
metavar='GRAPH_FILE', default='dependencies.graph')
parser.add_option('-o', '--output', dest='output_file',
help='File to write the dependency report to.',
metavar='OUT_FILE', default='dependencies.txt')
(options, args) = parser.parse_args()
# Intenalize the graph file
print 'Loading graph from %s' % (options.graph_file, )
graph = load_graph(options.graph_file)
# Extract relevant slices and merge dependencies
roots = find_roots(args)
print 'Tracing dependencies for %d components under %s' % (len(roots), ', '.join(args))
deps = set()
for root in roots:
deps |= trace(root)
print 'Dependency graph slice yields %d of %d components.' % (len(deps), len(graph))
unresolved_deps = unresolved(deps)
print 'Component dependencies for %d binaries are unresolved' % (len(unresolved_deps, ))
# Write the report to the output file
report(options.output_file, roots, list(deps), list(unresolved_deps))
print 'Report written to: %s' % (options.output_file, )