1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright 2016 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6"""Prints all non-system dependencies for the given module. 7 8The primary use-case for this script is to generate the list of python 9modules required for .isolate files. 10""" 11 12import argparse 13import imp 14import os 15import pipes 16import sys 17 18# Don't use any helper modules, or else they will end up in the results. 19 20_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 21 22 23def _compute_python_dependencies(): 24 """Gets the paths of imported non-system python modules. 25 26 A path is assumed to be a "system" import if it is outside of chromium's 27 src/. The paths will be relative to the current directory. 28 """ 29 module_paths = (m.__file__ for m in sys.modules.values() 30 if m and hasattr(m, '__file__')) 31 32 src_paths = set() 33 for path in module_paths: 34 if path == __file__: 35 continue 36 path = os.path.abspath(path) 37 if not path.startswith(_SRC_ROOT): 38 continue 39 40 if (path.endswith('.pyc') 41 or (path.endswith('c') and not os.path.splitext(path)[1])): 42 path = path[:-1] 43 src_paths.add(path) 44 45 return src_paths 46 47 48def _normalize_command_line(options): 49 """Returns a string that when run from SRC_ROOT replicates the command.""" 50 args = ['build/print_python_deps.py'] 51 root = os.path.relpath(options.root, _SRC_ROOT) 52 if root != '.': 53 args.extend(('--root', root)) 54 if options.output: 55 args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT))) 56 if options.gn_paths: 57 args.extend(('--gn-paths', )) 58 for allowlist in sorted(options.allowlists): 59 args.extend(('--allowlist', os.path.relpath(allowlist, _SRC_ROOT))) 60 args.append(os.path.relpath(options.module, _SRC_ROOT)) 61 return ' '.join(pipes.quote(x) for x in args) 62 63 64def _find_python_in_directory(directory: dict): 65 """Returns an iterable of all non-test python files in the given directory.""" 66 for root, _dirnames, filenames in os.walk(directory): 67 for filename in filenames: 68 if filename.endswith('.py') and not filename.endswith('_test.py'): 69 yield os.path.join(root, filename) 70 71 72def main(): 73 parser = argparse.ArgumentParser( 74 description='Prints all non-system dependencies for the given module.') 75 parser.add_argument('module', help='The python module to analyze.') 76 parser.add_argument('--root', 77 default='.', 78 help='Directory to make paths relative to.') 79 parser.add_argument('--output', 80 help='Write output to a file rather than stdout.') 81 parser.add_argument( 82 '--inplace', 83 action='store_true', 84 help='Write output to a file with the same path as the ' 85 'module, but with a .pydeps extension. Also sets the ' 86 'root to the module\'s directory.') 87 parser.add_argument('--no-header', 88 action='store_true', 89 help='Do not write the "# Generated by" header.') 90 parser.add_argument('--gn-paths', 91 action='store_true', 92 help='Write paths as //foo/bar/baz.py') 93 parser.add_argument('--allowlist', 94 default=[], 95 action='append', 96 dest='allowlists', 97 help='Recursively include all non-test python files ' 98 'within this directory. ' 99 'May be specified multiple times.') 100 options = parser.parse_args() 101 # Replace the path entry for print_python_deps.py with the one for the given 102 # module. 103 sys.path[0] = os.path.dirname(options.module) 104 imp.load_source('NAME', options.module) 105 106 if options.inplace: 107 if options.output: 108 parser.error('Cannot use --inplace and --output at the same time!') 109 if not options.module.endswith('.py'): 110 parser.error('Input module path should end with .py suffix!') 111 options.output = options.module + 'deps' 112 options.root = os.path.dirname(options.module) 113 114 paths_set = _compute_python_dependencies() 115 for path in options.allowlists: 116 paths_set.update( 117 os.path.abspath(p) for p in _find_python_in_directory(path)) 118 119 paths = [os.path.relpath(p, options.root) for p in paths_set] 120 121 normalized_cmdline = _normalize_command_line(options) 122 try: 123 with (open(options.output, 'w') 124 if options.output else sys.stdout) as out: 125 if not options.no_header: 126 out.write('# Generated by running:\n') 127 out.write('# %s\n' % normalized_cmdline) 128 prefix = '//' if options.gn_paths else '' 129 for path in sorted(paths): 130 out.write(prefix + path + '\n') 131 except OSError as err: 132 raise err 133 finally: 134 out.close() 135 136 137if __name__ == '__main__': 138 sys.exit(main()) 139