jamesa/get_deps.py
changeset 5 842a773e65f2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jamesa/get_deps.py	Wed Nov 04 17:40:17 2009 +0000
@@ -0,0 +1,174 @@
+# 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, )