1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2016 The Chromium Authors 3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker 6*8975f5c5SAndroid Build Coastguard Worker"""Prints all non-system dependencies for the given module. 7*8975f5c5SAndroid Build Coastguard Worker 8*8975f5c5SAndroid Build Coastguard WorkerThe primary use-case for this script is to generate the list of python modules 9*8975f5c5SAndroid Build Coastguard Workerrequired for .isolate files. 10*8975f5c5SAndroid Build Coastguard Worker""" 11*8975f5c5SAndroid Build Coastguard Worker 12*8975f5c5SAndroid Build Coastguard Workerimport argparse 13*8975f5c5SAndroid Build Coastguard Workerimport os 14*8975f5c5SAndroid Build Coastguard Workerimport shlex 15*8975f5c5SAndroid Build Coastguard Workerimport sys 16*8975f5c5SAndroid Build Coastguard Worker 17*8975f5c5SAndroid Build Coastguard Worker# Don't use any helper modules, or else they will end up in the results. 18*8975f5c5SAndroid Build Coastguard Worker 19*8975f5c5SAndroid Build Coastguard Worker 20*8975f5c5SAndroid Build Coastguard Worker_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Worker 23*8975f5c5SAndroid Build Coastguard Workerdef ComputePythonDependencies(): 24*8975f5c5SAndroid Build Coastguard Worker """Gets the paths of imported non-system python modules. 25*8975f5c5SAndroid Build Coastguard Worker 26*8975f5c5SAndroid Build Coastguard Worker A path is assumed to be a "system" import if it is outside of chromium's 27*8975f5c5SAndroid Build Coastguard Worker src/. 28*8975f5c5SAndroid Build Coastguard Worker 29*8975f5c5SAndroid Build Coastguard Worker Returns: 30*8975f5c5SAndroid Build Coastguard Worker List of absolute paths. 31*8975f5c5SAndroid Build Coastguard Worker """ 32*8975f5c5SAndroid Build Coastguard Worker module_paths = (m.__file__ for m in sys.modules.values() 33*8975f5c5SAndroid Build Coastguard Worker if m and hasattr(m, '__file__') and m.__file__ 34*8975f5c5SAndroid Build Coastguard Worker and m.__name__ != '__main__') 35*8975f5c5SAndroid Build Coastguard Worker 36*8975f5c5SAndroid Build Coastguard Worker src_paths = set() 37*8975f5c5SAndroid Build Coastguard Worker for path in module_paths: 38*8975f5c5SAndroid Build Coastguard Worker path = os.path.abspath(path) # paths can be relative before python 3.9. 39*8975f5c5SAndroid Build Coastguard Worker if not path.startswith(_SRC_ROOT): 40*8975f5c5SAndroid Build Coastguard Worker continue 41*8975f5c5SAndroid Build Coastguard Worker 42*8975f5c5SAndroid Build Coastguard Worker if (path.endswith('.pyc') 43*8975f5c5SAndroid Build Coastguard Worker or (path.endswith('c') and not os.path.splitext(path)[1])): 44*8975f5c5SAndroid Build Coastguard Worker path = path[:-1] 45*8975f5c5SAndroid Build Coastguard Worker src_paths.add(path) 46*8975f5c5SAndroid Build Coastguard Worker 47*8975f5c5SAndroid Build Coastguard Worker return src_paths 48*8975f5c5SAndroid Build Coastguard Worker 49*8975f5c5SAndroid Build Coastguard Worker 50*8975f5c5SAndroid Build Coastguard Workerdef quote(string): 51*8975f5c5SAndroid Build Coastguard Worker if string.count(' ') > 0: 52*8975f5c5SAndroid Build Coastguard Worker return '"%s"' % string 53*8975f5c5SAndroid Build Coastguard Worker else: 54*8975f5c5SAndroid Build Coastguard Worker return string 55*8975f5c5SAndroid Build Coastguard Worker 56*8975f5c5SAndroid Build Coastguard Worker 57*8975f5c5SAndroid Build Coastguard Workerdef _NormalizeCommandLine(options): 58*8975f5c5SAndroid Build Coastguard Worker """Returns a string that when run from SRC_ROOT replicates the command.""" 59*8975f5c5SAndroid Build Coastguard Worker args = ['build/print_python_deps.py'] 60*8975f5c5SAndroid Build Coastguard Worker root = os.path.relpath(options.root, _SRC_ROOT) 61*8975f5c5SAndroid Build Coastguard Worker if root != '.': 62*8975f5c5SAndroid Build Coastguard Worker args.extend(('--root', root)) 63*8975f5c5SAndroid Build Coastguard Worker if options.output: 64*8975f5c5SAndroid Build Coastguard Worker args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT))) 65*8975f5c5SAndroid Build Coastguard Worker if options.gn_paths: 66*8975f5c5SAndroid Build Coastguard Worker args.extend(('--gn-paths',)) 67*8975f5c5SAndroid Build Coastguard Worker for allowlist in sorted(options.allowlists): 68*8975f5c5SAndroid Build Coastguard Worker args.extend(('--allowlist', os.path.relpath(allowlist, _SRC_ROOT))) 69*8975f5c5SAndroid Build Coastguard Worker args.append(os.path.relpath(options.module, _SRC_ROOT)) 70*8975f5c5SAndroid Build Coastguard Worker if os.name == 'nt': 71*8975f5c5SAndroid Build Coastguard Worker return ' '.join(quote(x) for x in args).replace('\\', '/') 72*8975f5c5SAndroid Build Coastguard Worker else: 73*8975f5c5SAndroid Build Coastguard Worker return ' '.join(shlex.quote(x) for x in args) 74*8975f5c5SAndroid Build Coastguard Worker 75*8975f5c5SAndroid Build Coastguard Worker 76*8975f5c5SAndroid Build Coastguard Workerdef _FindPythonInDirectory(directory, allow_test): 77*8975f5c5SAndroid Build Coastguard Worker """Returns an iterable of all non-test python files in the given directory.""" 78*8975f5c5SAndroid Build Coastguard Worker for root, _dirnames, filenames in os.walk(directory): 79*8975f5c5SAndroid Build Coastguard Worker for filename in filenames: 80*8975f5c5SAndroid Build Coastguard Worker if filename.endswith('.py') and (allow_test 81*8975f5c5SAndroid Build Coastguard Worker or not filename.endswith('_test.py')): 82*8975f5c5SAndroid Build Coastguard Worker yield os.path.join(root, filename) 83*8975f5c5SAndroid Build Coastguard Worker 84*8975f5c5SAndroid Build Coastguard Worker 85*8975f5c5SAndroid Build Coastguard Workerdef _ImportModuleByPath(module_path): 86*8975f5c5SAndroid Build Coastguard Worker """Imports a module by its source file.""" 87*8975f5c5SAndroid Build Coastguard Worker # Replace the path entry for print_python_deps.py with the one for the given 88*8975f5c5SAndroid Build Coastguard Worker # module. 89*8975f5c5SAndroid Build Coastguard Worker sys.path[0] = os.path.dirname(module_path) 90*8975f5c5SAndroid Build Coastguard Worker 91*8975f5c5SAndroid Build Coastguard Worker # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly 92*8975f5c5SAndroid Build Coastguard Worker module_name = os.path.splitext(os.path.basename(module_path))[0] 93*8975f5c5SAndroid Build Coastguard Worker import importlib.util # Python 3 only, since it's unavailable in Python 2. 94*8975f5c5SAndroid Build Coastguard Worker spec = importlib.util.spec_from_file_location(module_name, module_path) 95*8975f5c5SAndroid Build Coastguard Worker module = importlib.util.module_from_spec(spec) 96*8975f5c5SAndroid Build Coastguard Worker sys.modules[module_name] = module 97*8975f5c5SAndroid Build Coastguard Worker spec.loader.exec_module(module) 98*8975f5c5SAndroid Build Coastguard Worker 99*8975f5c5SAndroid Build Coastguard Worker 100*8975f5c5SAndroid Build Coastguard Workerdef main(): 101*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 102*8975f5c5SAndroid Build Coastguard Worker description='Prints all non-system dependencies for the given module.') 103*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('module', 104*8975f5c5SAndroid Build Coastguard Worker help='The python module to analyze.') 105*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--root', default='.', 106*8975f5c5SAndroid Build Coastguard Worker help='Directory to make paths relative to.') 107*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--output', 108*8975f5c5SAndroid Build Coastguard Worker help='Write output to a file rather than stdout.') 109*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--inplace', action='store_true', 110*8975f5c5SAndroid Build Coastguard Worker help='Write output to a file with the same path as the ' 111*8975f5c5SAndroid Build Coastguard Worker 'module, but with a .pydeps extension. Also sets the ' 112*8975f5c5SAndroid Build Coastguard Worker 'root to the module\'s directory.') 113*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--no-header', action='store_true', 114*8975f5c5SAndroid Build Coastguard Worker help='Do not write the "# Generated by" header.') 115*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--gn-paths', action='store_true', 116*8975f5c5SAndroid Build Coastguard Worker help='Write paths as //foo/bar/baz.py') 117*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--did-relaunch', action='store_true', 118*8975f5c5SAndroid Build Coastguard Worker help=argparse.SUPPRESS) 119*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--allowlist', 120*8975f5c5SAndroid Build Coastguard Worker default=[], 121*8975f5c5SAndroid Build Coastguard Worker action='append', 122*8975f5c5SAndroid Build Coastguard Worker dest='allowlists', 123*8975f5c5SAndroid Build Coastguard Worker help='Recursively include all non-test python files ' 124*8975f5c5SAndroid Build Coastguard Worker 'within this directory. May be specified multiple times.') 125*8975f5c5SAndroid Build Coastguard Worker options = parser.parse_args() 126*8975f5c5SAndroid Build Coastguard Worker 127*8975f5c5SAndroid Build Coastguard Worker if options.inplace: 128*8975f5c5SAndroid Build Coastguard Worker if options.output: 129*8975f5c5SAndroid Build Coastguard Worker parser.error('Cannot use --inplace and --output at the same time!') 130*8975f5c5SAndroid Build Coastguard Worker if not options.module.endswith('.py'): 131*8975f5c5SAndroid Build Coastguard Worker parser.error('Input module path should end with .py suffix!') 132*8975f5c5SAndroid Build Coastguard Worker options.output = options.module + 'deps' 133*8975f5c5SAndroid Build Coastguard Worker options.root = os.path.dirname(options.module) 134*8975f5c5SAndroid Build Coastguard Worker 135*8975f5c5SAndroid Build Coastguard Worker modules = [options.module] 136*8975f5c5SAndroid Build Coastguard Worker if os.path.isdir(options.module): 137*8975f5c5SAndroid Build Coastguard Worker modules = list(_FindPythonInDirectory(options.module, allow_test=True)) 138*8975f5c5SAndroid Build Coastguard Worker if not modules: 139*8975f5c5SAndroid Build Coastguard Worker parser.error('Input directory does not contain any python files!') 140*8975f5c5SAndroid Build Coastguard Worker 141*8975f5c5SAndroid Build Coastguard Worker is_vpython = 'vpython' in sys.executable 142*8975f5c5SAndroid Build Coastguard Worker if not is_vpython: 143*8975f5c5SAndroid Build Coastguard Worker # Prevent infinite relaunch if something goes awry. 144*8975f5c5SAndroid Build Coastguard Worker assert not options.did_relaunch 145*8975f5c5SAndroid Build Coastguard Worker # Re-launch using vpython will cause us to pick up modules specified in 146*8975f5c5SAndroid Build Coastguard Worker # //.vpython, but does not cause it to pick up modules defined inline via 147*8975f5c5SAndroid Build Coastguard Worker # [VPYTHON:BEGIN] ... [VPYTHON:END] comments. 148*8975f5c5SAndroid Build Coastguard Worker # TODO(agrieve): Add support for this if the need ever arises. 149*8975f5c5SAndroid Build Coastguard Worker os.execvp('vpython3', ['vpython3'] + sys.argv + ['--did-relaunch']) 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard Worker # Work-around for protobuf library not being loadable via importlib 152*8975f5c5SAndroid Build Coastguard Worker # This is needed due to compile_resources.py. 153*8975f5c5SAndroid Build Coastguard Worker import importlib._bootstrap_external 154*8975f5c5SAndroid Build Coastguard Worker importlib._bootstrap_external._NamespacePath.sort = lambda self, **_: 0 155*8975f5c5SAndroid Build Coastguard Worker 156*8975f5c5SAndroid Build Coastguard Worker paths_set = set() 157*8975f5c5SAndroid Build Coastguard Worker try: 158*8975f5c5SAndroid Build Coastguard Worker for module in modules: 159*8975f5c5SAndroid Build Coastguard Worker _ImportModuleByPath(module) 160*8975f5c5SAndroid Build Coastguard Worker paths_set.update(ComputePythonDependencies()) 161*8975f5c5SAndroid Build Coastguard Worker except Exception: 162*8975f5c5SAndroid Build Coastguard Worker # Output extra diagnostics when loading the script fails. 163*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('Error running print_python_deps.py.\n') 164*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('is_vpython={}\n'.format(is_vpython)) 165*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('did_relanuch={}\n'.format(options.did_relaunch)) 166*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('python={}\n'.format(sys.executable)) 167*8975f5c5SAndroid Build Coastguard Worker raise 168*8975f5c5SAndroid Build Coastguard Worker 169*8975f5c5SAndroid Build Coastguard Worker for path in options.allowlists: 170*8975f5c5SAndroid Build Coastguard Worker paths_set.update( 171*8975f5c5SAndroid Build Coastguard Worker os.path.abspath(p) 172*8975f5c5SAndroid Build Coastguard Worker for p in _FindPythonInDirectory(path, allow_test=False)) 173*8975f5c5SAndroid Build Coastguard Worker 174*8975f5c5SAndroid Build Coastguard Worker paths = [os.path.relpath(p, options.root) for p in paths_set] 175*8975f5c5SAndroid Build Coastguard Worker 176*8975f5c5SAndroid Build Coastguard Worker normalized_cmdline = _NormalizeCommandLine(options) 177*8975f5c5SAndroid Build Coastguard Worker out = open(options.output, 'w', newline='') if options.output else sys.stdout 178*8975f5c5SAndroid Build Coastguard Worker with out: 179*8975f5c5SAndroid Build Coastguard Worker if not options.no_header: 180*8975f5c5SAndroid Build Coastguard Worker out.write('# Generated by running:\n') 181*8975f5c5SAndroid Build Coastguard Worker out.write('# %s\n' % normalized_cmdline) 182*8975f5c5SAndroid Build Coastguard Worker prefix = '//' if options.gn_paths else '' 183*8975f5c5SAndroid Build Coastguard Worker for path in sorted(paths): 184*8975f5c5SAndroid Build Coastguard Worker out.write(prefix + path.replace('\\', '/') + '\n') 185*8975f5c5SAndroid Build Coastguard Worker 186*8975f5c5SAndroid Build Coastguard Worker 187*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 188*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 189