
The Python 'pipes' module is deprecated and the 'quote' functionality has been moved since Python 3.3 to the 'shlex' module. Replace all instances of 'pipes.quote' with 'shlex.quote' in light of the eventual removal of this module in Python 3.10. Bug: chromium:1453653 Change-Id: I701e24af2fed439c38fdfb305a318165cd410dab Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4606522 Reviewed-by: Sam Maier <smaier@chromium.org> Auto-Submit: Prashanth Swaminathan <prashanthsw@google.com> Commit-Queue: Sam Maier <smaier@chromium.org> Cr-Commit-Position: refs/heads/main@{#1156440}
187 lines
6.8 KiB
Python
Executable File
187 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env vpython3
|
|
# Copyright 2016 The Chromium Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Prints all non-system dependencies for the given module.
|
|
|
|
The primary use-case for this script is to generate the list of python modules
|
|
required for .isolate files.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import shlex
|
|
import sys
|
|
|
|
# Don't use any helper modules, or else they will end up in the results.
|
|
|
|
|
|
_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
|
|
|
|
def ComputePythonDependencies():
|
|
"""Gets the paths of imported non-system python modules.
|
|
|
|
A path is assumed to be a "system" import if it is outside of chromium's
|
|
src/. The paths will be relative to the current directory.
|
|
"""
|
|
module_paths = (m.__file__ for m in sys.modules.values()
|
|
if m and hasattr(m, '__file__') and m.__file__)
|
|
|
|
src_paths = set()
|
|
for path in module_paths:
|
|
if path == __file__:
|
|
continue
|
|
path = os.path.abspath(path)
|
|
if not path.startswith(_SRC_ROOT):
|
|
continue
|
|
|
|
if (path.endswith('.pyc')
|
|
or (path.endswith('c') and not os.path.splitext(path)[1])):
|
|
path = path[:-1]
|
|
src_paths.add(path)
|
|
|
|
return src_paths
|
|
|
|
|
|
def quote(string):
|
|
if string.count(' ') > 0:
|
|
return '"%s"' % string
|
|
else:
|
|
return string
|
|
|
|
|
|
def _NormalizeCommandLine(options):
|
|
"""Returns a string that when run from SRC_ROOT replicates the command."""
|
|
args = ['build/print_python_deps.py']
|
|
root = os.path.relpath(options.root, _SRC_ROOT)
|
|
if root != '.':
|
|
args.extend(('--root', root))
|
|
if options.output:
|
|
args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT)))
|
|
if options.gn_paths:
|
|
args.extend(('--gn-paths',))
|
|
for allowlist in sorted(options.allowlists):
|
|
args.extend(('--allowlist', os.path.relpath(allowlist, _SRC_ROOT)))
|
|
args.append(os.path.relpath(options.module, _SRC_ROOT))
|
|
if os.name == 'nt':
|
|
return ' '.join(quote(x) for x in args).replace('\\', '/')
|
|
else:
|
|
return ' '.join(shlex.quote(x) for x in args)
|
|
|
|
|
|
def _FindPythonInDirectory(directory, allow_test):
|
|
"""Returns an iterable of all non-test python files in the given directory."""
|
|
for root, _dirnames, filenames in os.walk(directory):
|
|
for filename in filenames:
|
|
if filename.endswith('.py') and (allow_test
|
|
or not filename.endswith('_test.py')):
|
|
yield os.path.join(root, filename)
|
|
|
|
|
|
def _ImportModuleByPath(module_path):
|
|
"""Imports a module by its source file."""
|
|
# Replace the path entry for print_python_deps.py with the one for the given
|
|
# module.
|
|
sys.path[0] = os.path.dirname(module_path)
|
|
|
|
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
|
module_name = os.path.splitext(os.path.basename(module_path))[0]
|
|
import importlib.util # Python 3 only, since it's unavailable in Python 2.
|
|
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[module_name] = module
|
|
spec.loader.exec_module(module)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Prints all non-system dependencies for the given module.')
|
|
parser.add_argument('module',
|
|
help='The python module to analyze.')
|
|
parser.add_argument('--root', default='.',
|
|
help='Directory to make paths relative to.')
|
|
parser.add_argument('--output',
|
|
help='Write output to a file rather than stdout.')
|
|
parser.add_argument('--inplace', action='store_true',
|
|
help='Write output to a file with the same path as the '
|
|
'module, but with a .pydeps extension. Also sets the '
|
|
'root to the module\'s directory.')
|
|
parser.add_argument('--no-header', action='store_true',
|
|
help='Do not write the "# Generated by" header.')
|
|
parser.add_argument('--gn-paths', action='store_true',
|
|
help='Write paths as //foo/bar/baz.py')
|
|
parser.add_argument('--did-relaunch', action='store_true',
|
|
help=argparse.SUPPRESS)
|
|
parser.add_argument('--allowlist',
|
|
default=[],
|
|
action='append',
|
|
dest='allowlists',
|
|
help='Recursively include all non-test python files '
|
|
'within this directory. May be specified multiple times.')
|
|
options = parser.parse_args()
|
|
|
|
if options.inplace:
|
|
if options.output:
|
|
parser.error('Cannot use --inplace and --output at the same time!')
|
|
if not options.module.endswith('.py'):
|
|
parser.error('Input module path should end with .py suffix!')
|
|
options.output = options.module + 'deps'
|
|
options.root = os.path.dirname(options.module)
|
|
|
|
modules = [options.module]
|
|
if os.path.isdir(options.module):
|
|
modules = list(_FindPythonInDirectory(options.module, allow_test=True))
|
|
if not modules:
|
|
parser.error('Input directory does not contain any python files!')
|
|
|
|
is_vpython = 'vpython' in sys.executable
|
|
if not is_vpython:
|
|
# Prevent infinite relaunch if something goes awry.
|
|
assert not options.did_relaunch
|
|
# Re-launch using vpython will cause us to pick up modules specified in
|
|
# //.vpython, but does not cause it to pick up modules defined inline via
|
|
# [VPYTHON:BEGIN] ... [VPYTHON:END] comments.
|
|
# TODO(agrieve): Add support for this if the need ever arises.
|
|
os.execvp('vpython3', ['vpython3'] + sys.argv + ['--did-relaunch'])
|
|
|
|
# Work-around for protobuf library not being loadable via importlib
|
|
# This is needed due to compile_resources.py.
|
|
import importlib._bootstrap_external
|
|
importlib._bootstrap_external._NamespacePath.sort = lambda self, **_: 0
|
|
|
|
paths_set = set()
|
|
try:
|
|
for module in modules:
|
|
_ImportModuleByPath(module)
|
|
paths_set.update(ComputePythonDependencies())
|
|
except Exception:
|
|
# Output extra diagnostics when loading the script fails.
|
|
sys.stderr.write('Error running print_python_deps.py.\n')
|
|
sys.stderr.write('is_vpython={}\n'.format(is_vpython))
|
|
sys.stderr.write('did_relanuch={}\n'.format(options.did_relaunch))
|
|
sys.stderr.write('python={}\n'.format(sys.executable))
|
|
raise
|
|
|
|
for path in options.allowlists:
|
|
paths_set.update(
|
|
os.path.abspath(p)
|
|
for p in _FindPythonInDirectory(path, allow_test=False))
|
|
|
|
paths = [os.path.relpath(p, options.root) for p in paths_set]
|
|
|
|
normalized_cmdline = _NormalizeCommandLine(options)
|
|
out = open(options.output, 'w', newline='') if options.output else sys.stdout
|
|
with out:
|
|
if not options.no_header:
|
|
out.write('# Generated by running:\n')
|
|
out.write('# %s\n' % normalized_cmdline)
|
|
prefix = '//' if options.gn_paths else ''
|
|
for path in sorted(paths):
|
|
out.write(prefix + path.replace('\\', '/') + '\n')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|