jamesa/get_deps.py
author MattD <mattd@symbian.org>
Wed, 17 Mar 2010 12:29:10 +0000
changeset 204 d19c45914cb2
parent 5 842a773e65f2
permissions -rw-r--r--
Catchup merge

# 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, )