jamesa/get_deps.py
changeset 5 842a773e65f2
equal deleted inserted replaced
4:60053dab7e2a 5:842a773e65f2
       
     1 # Copyright (c) 2009 Symbian Foundation Ltd
       
     2 # This component and the accompanying materials are made available
       
     3 # under the terms of the License "Eclipse Public License v1.0"
       
     4 # which accompanies this distribution, and is available
       
     5 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     6 #
       
     7 # Initial Contributors:
       
     8 # Symbian Foundation Ltd - initial contribution.
       
     9 # 
       
    10 # Contributors:
       
    11 #
       
    12 # Description:
       
    13 # Walk all nodes in the dependency graph to create a dependency report.
       
    14 
       
    15 """Walk a dependency graph to find dependencies for a particular set of
       
    16 components. This script uses the output of build_graph.py to trace
       
    17 dependencies.
       
    18 
       
    19 The directory_list refer to diretories in the source tree for which
       
    20 you wish to trace dependencies. The script will find all components
       
    21 in the graph file under these directories and trace dependencies from
       
    22 that point.
       
    23 """
       
    24 
       
    25 from optparse import OptionParser
       
    26 from _common import Node
       
    27 
       
    28 import sys
       
    29 import pickle
       
    30 import logging
       
    31 
       
    32 __author__ = 'James Aley'
       
    33 __email__ = 'jamesa@symbian.org'
       
    34 __version__ = '1.0'
       
    35 
       
    36 _LOG_FORMAT = '%(levelname)s: %(message)s'
       
    37 logging.basicConfig(format=_LOG_FORMAT, level=logging.WARNING, stream=sys.stdout)
       
    38 
       
    39 # Internalized graph object
       
    40 graph = {}
       
    41 
       
    42 # Report formatting
       
    43 _REPORT_HEADER = """// Generated by get_deps.py
       
    44 //
       
    45 // Dependency information for:
       
    46 //
       
    47 %s
       
    48 
       
    49 """
       
    50 
       
    51 _DEPENDENCY_FORMAT = """
       
    52 // Required components:
       
    53 
       
    54 %s
       
    55 
       
    56 """
       
    57 
       
    58 _MISSING_FORMAT = """
       
    59 // The following binary objects were referenced from the build files for
       
    60 // components required by your specified root components. However, there
       
    61 // were no build files for these objects found in the source tree parsing,
       
    62 // so dependencies for them may be missing in the above listing.
       
    63 
       
    64 %s
       
    65 
       
    66 """
       
    67 
       
    68 def load_graph(path):
       
    69     """Return the internalized graph dictionary object.
       
    70     """
       
    71     graph_file = None
       
    72     graph_loaded = {}
       
    73 
       
    74     try:
       
    75         graph_file = open(path, 'rb')
       
    76     except IOError, e:
       
    77         logging.critical('Unable to open graph from file: %s: %s' % (path, repr(e)))
       
    78         exit(1)
       
    79     try:
       
    80         graph_loaded = pickle.load(graph_file)
       
    81     except Exception, e:
       
    82         logging.critical('File %s does not contain a valid graph: %s' % (path, repr(e)))
       
    83     return graph_loaded
       
    84 
       
    85 def find_roots(root_dirs):
       
    86     """Return a list of root nodes from the graph for tracing, based on
       
    87     the specified component directories in the root_dirs list.
       
    88     """
       
    89     roots = []
       
    90     for root in root_dirs:
       
    91         for node in graph.keys():
       
    92             if node.startswith(root.lower()):
       
    93                 if node not in roots:
       
    94                     roots.append(node)
       
    95     return roots
       
    96 
       
    97 def trace(root, visited = set()):
       
    98     """Return a list of components required to support root.
       
    99     """
       
   100     node = graph[root]
       
   101     visited |= set([node.node_path]) | set(node.dependencies)
       
   102     for dep in node.dependencies:
       
   103         if dep not in visited:
       
   104             return trace(dep, visited)
       
   105     return visited
       
   106 
       
   107 def unresolved(deps):
       
   108     """Return a set of components with unknown dependencies from a 
       
   109     provided list of node names.
       
   110     """
       
   111     unresolved = set()
       
   112     for dep in deps:
       
   113         node = graph[dep]
       
   114         unresolved |= set(node.unresolved)
       
   115     return unresolved
       
   116 
       
   117 def report(out_path, roots, dependencies, missing):
       
   118     """Output the dependency information to file.
       
   119     """
       
   120     # open report file
       
   121     out_file = None
       
   122     try:
       
   123         out_file = open(out_path, 'w')
       
   124     except IOError, e:
       
   125         logging.critical('Unable to write report: %s' % (repr(e)))
       
   126         exit(1)
       
   127 
       
   128     # easier to read report with these sorted
       
   129     roots.sort()
       
   130     dependencies.sort()
       
   131     missing.sort()
       
   132 
       
   133     # format report
       
   134     formatted_header = _REPORT_HEADER % ('\n'.join(['// %s' % (line, ) for line in roots]), )
       
   135     formatted_body = _DEPENDENCY_FORMAT % ('\n'.join(dependencies))
       
   136     formatted_missing = _MISSING_FORMAT % ('\n'.join(['// %s' % (line, ) for line in missing]), )
       
   137 
       
   138     # write report
       
   139     out_file.write(formatted_header)
       
   140     out_file.write(formatted_body)
       
   141     out_file.write(formatted_missing)
       
   142 
       
   143     out_file.close()
       
   144 
       
   145 if __name__ == '__main__':
       
   146     # Options config
       
   147     parser = OptionParser()
       
   148     parser.set_description(__doc__)
       
   149     parser.set_usage('python get_deps.py [options] directory_list')
       
   150     parser.add_option('-g', '--graph', dest='graph_file', 
       
   151                       help='File name to write the graph to.', 
       
   152                       metavar='GRAPH_FILE', default='dependencies.graph')
       
   153     parser.add_option('-o', '--output', dest='output_file',
       
   154                       help='File to write the dependency report to.',
       
   155                       metavar='OUT_FILE', default='dependencies.txt')
       
   156     (options, args) = parser.parse_args()
       
   157 
       
   158     # Intenalize the graph file
       
   159     print 'Loading graph from %s' % (options.graph_file, )
       
   160     graph = load_graph(options.graph_file)
       
   161 
       
   162     # Extract relevant slices and merge dependencies
       
   163     roots = find_roots(args)
       
   164     print 'Tracing dependencies for %d components under %s' % (len(roots), ', '.join(args))
       
   165     deps = set()
       
   166     for root in roots:
       
   167         deps |= trace(root)
       
   168     print 'Dependency graph slice yields %d of %d components.' % (len(deps), len(graph))
       
   169     unresolved_deps = unresolved(deps)
       
   170     print 'Component dependencies for %d binaries are unresolved' % (len(unresolved_deps, ))
       
   171 
       
   172     # Write the report to the output file
       
   173     report(options.output_file, roots, list(deps), list(unresolved_deps))
       
   174     print 'Report written to: %s' % (options.output_file, )