1#!/usr/bin/env python3 2# Copyright 2017 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Wraps bin/helper/bytecode_processor and expands @FileArgs.""" 7 8import argparse 9import collections 10import logging 11import pathlib 12import shlex 13import sys 14from typing import Dict, List, Tuple 15 16from util import build_utils 17from util import dep_utils 18from util import jar_utils 19from util import server_utils 20import action_helpers # build_utils adds //build to sys.path. 21 22_SRC_PATH = pathlib.Path(build_utils.DIR_SOURCE_ROOT).resolve() 23sys.path.append(str(_SRC_PATH / 'build/gn_ast')) 24from gn_editor import NO_VALID_GN_STR 25 26 27def _ShouldIgnoreDep(dep_name: str): 28 if 'gen.base_module.R' in dep_name: 29 return True 30 return False 31 32 33def _ParseDepGraph(jar_path: str): 34 output = jar_utils.run_jdeps(pathlib.Path(jar_path)) 35 assert output is not None, f'Unable to parse jdep for {jar_path}' 36 dep_graph = collections.defaultdict(set) 37 # pylint: disable=line-too-long 38 # Example output: 39 # java.javac.jar -> java.base 40 # java.javac.jar -> not found 41 # org.chromium.chrome.browser.tabmodel.AsyncTabParamsManagerFactory -> java.lang.Object java.base 42 # org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.base.ApplicationStatus not found 43 # org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.base.ApplicationStatus$ActivityStateListener not found 44 # org.chromium.chrome.browser.tabmodel.TabWindowManagerImpl -> org.chromium.chrome.browser.tab.Tab not found 45 # pylint: enable=line-too-long 46 for line in output.splitlines(): 47 parsed = line.split() 48 # E.g. java.javac.jar -> java.base 49 if len(parsed) <= 3: 50 continue 51 # E.g. java.javac.jar -> not found 52 if parsed[2] == 'not' and parsed[3] == 'found': 53 continue 54 if parsed[1] != '->': 55 continue 56 dep_from = parsed[0] 57 dep_to = parsed[2] 58 dep_graph[dep_from].add(dep_to) 59 return dep_graph 60 61 62def _EnsureDirectClasspathIsComplete( 63 *, 64 input_jar: str, 65 gn_target: str, 66 output_dir: str, 67 sdk_classpath_jars: List[str], 68 direct_classpath_jars: List[str], 69 full_classpath_jars: List[str], 70 full_classpath_gn_targets: List[str], 71 warnings_as_errors: bool, 72 auto_add_deps: bool, 73): 74 logging.info('Parsing %d direct classpath jars', len(sdk_classpath_jars)) 75 sdk_classpath_deps = set() 76 for jar in sdk_classpath_jars: 77 deps = jar_utils.extract_full_class_names_from_jar(jar) 78 sdk_classpath_deps.update(deps) 79 80 logging.info('Parsing %d direct classpath jars', len(direct_classpath_jars)) 81 direct_classpath_deps = set() 82 for jar in direct_classpath_jars: 83 deps = jar_utils.extract_full_class_names_from_jar(jar) 84 direct_classpath_deps.update(deps) 85 86 logging.info('Parsing %d full classpath jars', len(full_classpath_jars)) 87 full_classpath_deps = set() 88 dep_to_target = collections.defaultdict(set) 89 for jar, target in zip(full_classpath_jars, full_classpath_gn_targets): 90 deps = jar_utils.extract_full_class_names_from_jar(jar) 91 full_classpath_deps.update(deps) 92 for dep in deps: 93 dep_to_target[dep].add(target) 94 95 transitive_deps = full_classpath_deps - direct_classpath_deps 96 97 missing_classes: Dict[str, str] = {} 98 dep_graph = _ParseDepGraph(input_jar) 99 logging.info('Finding missing deps from %d classes', len(dep_graph)) 100 # dep_graph.keys() is a list of all the classes in the current input_jar. Skip 101 # all of these to avoid checking dependencies in the same target (e.g. A 102 # depends on B, but both A and B are in input_jar). 103 # Since the bundle will always have access to classes in the current android 104 # sdk, those should not be considered missing. 105 seen_deps = set(dep_graph.keys()) | sdk_classpath_deps 106 for dep_from, deps_to in dep_graph.items(): 107 for dep_to in deps_to - seen_deps: 108 if _ShouldIgnoreDep(dep_to): 109 continue 110 seen_deps.add(dep_to) 111 if dep_to in transitive_deps: 112 # Allow clobbering since it doesn't matter which specific class depends 113 # on |dep_to|. 114 missing_classes[dep_to] = dep_from 115 116 # missing_target_names = tuple(sorted(dep_to_target[dep_to])) 117 # missing_targets[missing_target_names][dep_to] = dep_from 118 if missing_classes: 119 class_lookup_index = dep_utils.ClassLookupIndex(pathlib.Path(output_dir), 120 should_build=False) 121 missing_deps = set() 122 for dep_to in missing_classes: 123 # Using dep_utils.ClassLookupIndex ensures we respect the preferred dep 124 # if any exists for the missing deps. 125 suggested_deps = class_lookup_index.match(dep_to) 126 assert suggested_deps, f'Unable to find target for {dep_to}' 127 suggested_deps = dep_utils.DisambiguateDeps(suggested_deps) 128 missing_deps.add(suggested_deps[0].target) 129 cmd = dep_utils.CreateAddDepsCommand(gn_target, sorted(missing_deps)) 130 131 def print_and_maybe_exit(): 132 missing_targets: Dict[Tuple, List[str]] = collections.defaultdict(list) 133 for dep_to, dep_from in missing_classes.items(): 134 missing_target_names = tuple(sorted(dep_to_target[dep_to])) 135 missing_targets[missing_target_names].append(dep_to) 136 print('=' * 30 + ' Dependency Checks Failed ' + '=' * 30) 137 print(f'Target: {gn_target}') 138 print('Direct classpath is incomplete. To fix, add deps on:') 139 for missing_target_names, deps_to in missing_targets.items(): 140 if len(missing_target_names) > 1: 141 print(f' * One of {", ".join(missing_target_names)}') 142 else: 143 print(f' * {missing_target_names[0]}') 144 for dep_to in deps_to: 145 dep_from = missing_classes[dep_to] 146 print(f' ** {dep_to} (needed by {dep_from})') 147 print('\nHint: Run the following command to add the missing deps:') 148 print(f' {shlex.join(cmd)}\n') 149 if warnings_as_errors: 150 sys.exit(1) 151 152 if not auto_add_deps: 153 print_and_maybe_exit() 154 else: 155 156 failed = False 157 try: 158 stdout = build_utils.CheckOutput(cmd, 159 cwd=build_utils.DIR_SOURCE_ROOT, 160 fail_on_output=warnings_as_errors) 161 if f'Unable to find {gn_target}' in stdout: 162 # This can happen if a target's deps are stored in a variable instead 163 # of a list and then simply assigned: `deps = deps_variable`. These 164 # need to be manually added to the `deps_variable`. 165 failed = True 166 except build_utils.CalledProcessError as e: 167 if NO_VALID_GN_STR in e.output: 168 failed = True 169 else: 170 raise 171 172 build_file_path = dep_utils.GnTargetToBuildFilePath(gn_target) 173 if failed: 174 print(f'Unable to auto-add missing dep(s) to {build_file_path}.') 175 print_and_maybe_exit() 176 else: 177 gn_target_name = gn_target.split(':', 1)[-1] 178 print(f'Successfully updated "{gn_target_name}" in {build_file_path} ' 179 f'with missing direct deps: {missing_deps}') 180 181 182def main(argv): 183 build_utils.InitLogging('BYTECODE_PROCESSOR_DEBUG') 184 argv = build_utils.ExpandFileArgs(argv[1:]) 185 parser = argparse.ArgumentParser() 186 parser.add_argument('--target-name', help='Fully qualified GN target name.') 187 parser.add_argument('--use-build-server', 188 action='store_true', 189 help='Always use the build server.') 190 parser.add_argument('--gn-target', required=True) 191 parser.add_argument('--input-jar', required=True) 192 parser.add_argument('--direct-classpath-jars') 193 parser.add_argument('--sdk-classpath-jars') 194 parser.add_argument('--full-classpath-jars') 195 parser.add_argument('--full-classpath-gn-targets') 196 parser.add_argument('--chromium-output-dir') 197 parser.add_argument('--stamp') 198 parser.add_argument('--warnings-as-errors', 199 action='store_true', 200 help='Treat all warnings as errors.') 201 parser.add_argument( 202 '--auto-add-deps', 203 action='store_true', 204 help='Attempt to automatically add missing deps to the corresponding ' 205 'BUILD.gn file.') 206 args = parser.parse_args(argv) 207 208 if server_utils.MaybeRunCommand(name=args.target_name, 209 argv=sys.argv, 210 stamp_file=args.stamp, 211 force=args.use_build_server): 212 return 213 214 args.sdk_classpath_jars = action_helpers.parse_gn_list( 215 args.sdk_classpath_jars) 216 args.direct_classpath_jars = action_helpers.parse_gn_list( 217 args.direct_classpath_jars) 218 args.full_classpath_jars = action_helpers.parse_gn_list( 219 args.full_classpath_jars) 220 args.full_classpath_gn_targets = [ 221 dep_utils.ReplaceGmsPackageIfNeeded(t) 222 for t in action_helpers.parse_gn_list(args.full_classpath_gn_targets) 223 ] 224 225 logging.info('Processed args for %s, starting direct classpath check.', 226 args.target_name) 227 _EnsureDirectClasspathIsComplete( 228 input_jar=args.input_jar, 229 gn_target=args.gn_target, 230 output_dir=args.chromium_output_dir, 231 sdk_classpath_jars=args.sdk_classpath_jars, 232 direct_classpath_jars=args.direct_classpath_jars, 233 full_classpath_jars=args.full_classpath_jars, 234 full_classpath_gn_targets=args.full_classpath_gn_targets, 235 warnings_as_errors=args.warnings_as_errors, 236 auto_add_deps=args.auto_add_deps, 237 ) 238 logging.info('Check completed.') 239 240 if args.stamp: 241 build_utils.Touch(args.stamp) 242 243 244if __name__ == '__main__': 245 sys.exit(main(sys.argv)) 246