[go: nahoru, domu]

blob: 127e21877542819c4b90c4e5620e2bb3868fcb80 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Wraps bin/helper/bytecode_processor and expands @FileArgs."""
import argparse
import collections
import logging
import os
import pathlib
import sys
from typing import Dict, List
from util import build_utils
from util import dep_utils
from util import jar_utils
from util import server_utils
import action_helpers # build_utils adds //build to sys.path.
_SRC_PATH = pathlib.Path(build_utils.DIR_SOURCE_ROOT).resolve()
sys.path.append(str(_SRC_PATH / 'tools/android/modularization/gn'))
from dep_operations import NO_VALID_GN_STR
def _ShouldIgnoreDep(dep_name: str):
if 'gen.base_module.R' in dep_name:
return True
return False
def _ParseDepGraph(jar_path: str, output_dir: str):
output = jar_utils.run_jdeps(build_output_dir=pathlib.Path(output_dir),
filepath=pathlib.Path(jar_path))
assert output is not None, f'Unable to parse jdep for {jar_path}'
dep_graph = collections.defaultdict(set)
# pylint: disable=line-too-long
# Example output:
# java.javac.jar -> java.base
# java.javac.jar -> not found
# org.chromium.chrome.browser.tabmodel.AsyncTabParamsManagerFactory -> java.lang.Object java.base
# org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.base.ApplicationStatus not found
# org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.base.ApplicationStatus$ActivityStateListener not found
# org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.chrome.browser.tab.Tab not found
# pylint: enable=line-too-long
for line in output.splitlines():
parsed = line.split()
# E.g. java.javac.jar -> java.base
if len(parsed) <= 3:
continue
# E.g. java.javac.jar -> not found
if parsed[2] == 'not' and parsed[3] == 'found':
continue
if parsed[1] != '->':
continue
dep_from = parsed[0]
dep_to = parsed[2]
dep_graph[dep_from].add(dep_to)
return dep_graph
def _GnTargetToBuildFilePath(gn_target: str):
"""Returns the relative BUILD.gn file path for this target from src root."""
assert gn_target.startswith('//'), f'Relative {gn_target} name not supported.'
ninja_target_name = gn_target[2:]
# Remove the colon at the end
colon_index = ninja_target_name.find(':')
if colon_index != -1:
ninja_target_name = ninja_target_name[:colon_index]
return os.path.join(ninja_target_name, 'BUILD.gn')
def _EnsureDirectClasspathIsComplete(
*,
input_jar: str,
gn_target: str,
output_dir: str,
sdk_classpath_jars: List[str],
direct_classpath_jars: List[str],
full_classpath_jars: List[str],
full_classpath_gn_targets: List[str],
warnings_as_errors: bool,
):
logging.info('Parsing %d direct classpath jars', len(sdk_classpath_jars))
sdk_classpath_deps = set()
for jar in sdk_classpath_jars:
deps = jar_utils.extract_full_class_names_from_jar(
build_output_dir=pathlib.Path(output_dir), jar_path=pathlib.Path(jar))
sdk_classpath_deps.update(deps)
logging.info('Parsing %d direct classpath jars', len(direct_classpath_jars))
direct_classpath_deps = set()
for jar in direct_classpath_jars:
deps = jar_utils.extract_full_class_names_from_jar(
build_output_dir=pathlib.Path(output_dir), jar_path=pathlib.Path(jar))
direct_classpath_deps.update(deps)
logging.info('Parsing %d full classpath jars', len(full_classpath_jars))
full_classpath_deps = set()
dep_to_target = collections.defaultdict(set)
for jar, target in zip(full_classpath_jars, full_classpath_gn_targets):
deps = jar_utils.extract_full_class_names_from_jar(
build_output_dir=pathlib.Path(output_dir), jar_path=pathlib.Path(jar))
full_classpath_deps.update(deps)
for dep in deps:
dep_to_target[dep].add(target)
transitive_deps = full_classpath_deps - direct_classpath_deps
missing_targets: Dict[tuple, Dict[str, str]] = collections.defaultdict(dict)
dep_graph = _ParseDepGraph(input_jar, output_dir)
logging.info('Finding missing deps from %d classes', len(dep_graph))
# dep_graph.keys() is a list of all the classes in the current input_jar. Skip
# all of these to avoid checking dependencies in the same target (e.g. A
# depends on B, but both A and B are in input_jar).
# Since the bundle will always have access to classes in the current android
# sdk, those should not be considered missing.
seen_deps = set(dep_graph.keys()) | sdk_classpath_deps
for dep_from, deps_to in dep_graph.items():
for dep_to in deps_to - seen_deps:
if _ShouldIgnoreDep(dep_to):
continue
seen_deps.add(dep_to)
if dep_to in transitive_deps:
missing_target_names = tuple(sorted(dep_to_target[dep_to]))
missing_targets[missing_target_names][dep_to] = dep_from
if missing_targets:
def print_and_maybe_exit():
print('=' * 30 + ' Dependency Checks Failed ' + '=' * 30)
print(f'Target: {gn_target}')
print('Direct classpath is incomplete. To fix, add deps on:')
for missing_target_names, data in missing_targets.items():
if len(missing_target_names) > 1:
print(f' * One of {", ".join(missing_target_names)}')
else:
print(f' * {missing_target_names[0]}')
for missing_class, used_by in data.items():
print(f' ** {missing_class} (needed by {used_by})')
if warnings_as_errors:
sys.exit(1)
# TODO(https://crbug.com/1099522): This is better as a GN arg.
if os.environ.get('AUTO_ADD_MISSING_DEPS') != '1':
print_and_maybe_exit()
else:
# TODO(https://crbug.com/1099522): This should be generalized into util.
build_file_path = _GnTargetToBuildFilePath(gn_target)
cmd = [
'tools/android/modularization/gn/dep_operations.py', 'add', '--quiet',
'--file', build_file_path, '--target', gn_target, '--deps'
]
# For simplicity, always pick the first suggested target.
# TODO(https://crbug.com/1099522): Swap deps with preferred deps.
missing_deps = [names[0] for names in missing_targets.keys()]
cmd += missing_deps
try:
build_utils.CheckOutput(cmd, cwd=build_utils.DIR_SOURCE_ROOT)
except build_utils.CalledProcessError as e:
if NO_VALID_GN_STR in e.output:
print(f'Unable to add missing dep(s) to {build_file_path}.')
print_and_maybe_exit()
else:
raise
else:
print(f'Successfully updated {build_file_path} with missing direct '
f'deps: {missing_deps}')
def _AddSwitch(parser, val):
parser.add_argument(
val, action='store_const', default='--disabled', const=val)
def main(argv):
build_utils.InitLogging('BYTECODE_PROCESSOR_DEBUG')
argv = build_utils.ExpandFileArgs(argv[1:])
parser = argparse.ArgumentParser()
parser.add_argument('--target-name', help='Fully qualified GN target name.')
parser.add_argument('--use-build-server',
action='store_true',
help='Always use the build server.')
parser.add_argument('--script', required=True,
help='Path to the java binary wrapper script.')
parser.add_argument('--gn-target', required=True)
parser.add_argument('--input-jar', required=True)
parser.add_argument('--direct-classpath-jars')
parser.add_argument('--sdk-classpath-jars')
parser.add_argument('--full-classpath-jars')
parser.add_argument('--full-classpath-gn-targets')
parser.add_argument('--chromium-output-dir')
parser.add_argument('--stamp')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('--missing-classes-allowlist')
parser.add_argument('--warnings-as-errors',
action='store_true',
help='Treat all warnings as errors.')
_AddSwitch(parser, '--is-prebuilt')
args = parser.parse_args(argv)
if server_utils.MaybeRunCommand(name=args.target_name,
argv=sys.argv,
stamp_file=args.stamp,
force=args.use_build_server):
return
args.sdk_classpath_jars = action_helpers.parse_gn_list(
args.sdk_classpath_jars)
args.direct_classpath_jars = action_helpers.parse_gn_list(
args.direct_classpath_jars)
args.full_classpath_jars = action_helpers.parse_gn_list(
args.full_classpath_jars)
args.full_classpath_gn_targets = [
dep_utils.ReplaceGmsPackageIfNeeded(t)
for t in action_helpers.parse_gn_list(args.full_classpath_gn_targets)
]
args.missing_classes_allowlist = action_helpers.parse_gn_list(
args.missing_classes_allowlist)
verbose = '--verbose' if args.verbose else '--not-verbose'
# TODO(https://crbug.com/1099522): Make jdeps the default.
if os.environ.get('BYTECODE_PROCESSOR_USE_JDEPS'):
logging.info('Processed args for %s, starting direct classpath check.',
args.target_name)
_EnsureDirectClasspathIsComplete(
input_jar=args.input_jar,
gn_target=args.gn_target,
output_dir=args.chromium_output_dir,
sdk_classpath_jars=args.sdk_classpath_jars,
direct_classpath_jars=args.direct_classpath_jars,
full_classpath_jars=args.full_classpath_jars,
full_classpath_gn_targets=args.full_classpath_gn_targets,
warnings_as_errors=args.warnings_as_errors,
)
logging.info('Check completed.')
else:
cmd = [
args.script, args.gn_target, args.input_jar, verbose, args.is_prebuilt
]
cmd += [str(len(args.missing_classes_allowlist))]
cmd += args.missing_classes_allowlist
cmd += [str(len(args.sdk_classpath_jars))]
cmd += args.sdk_classpath_jars
cmd += [str(len(args.direct_classpath_jars))]
cmd += args.direct_classpath_jars
cmd += [str(len(args.full_classpath_jars))]
cmd += args.full_classpath_jars
cmd += [str(len(args.full_classpath_gn_targets))]
cmd += args.full_classpath_gn_targets
try:
build_utils.CheckOutput(cmd,
print_stdout=True,
fail_func=None,
fail_on_output=args.warnings_as_errors)
except build_utils.CalledProcessError as e:
# Do not output command line because it is massive and makes the actual
# error message hard to find.
sys.stderr.write(e.output)
sys.exit(1)
if args.stamp:
build_utils.Touch(args.stamp)
if __name__ == '__main__':
sys.exit(main(sys.argv))