1#!/usr/bin/env python 2# Copyright 2017 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# Using colorama.Fore/Back/Style members 7# pylint: disable=no-member 8 9from __future__ import print_function 10 11import argparse 12import collections 13import json 14import logging 15import os 16import pipes 17import posixpath 18import random 19import re 20import shlex 21import shutil 22import subprocess 23import sys 24import tempfile 25import textwrap 26import zipfile 27 28import adb_command_line 29import devil_chromium 30from devil import devil_env 31from devil.android import apk_helper 32from devil.android import device_errors 33from devil.android import device_utils 34from devil.android import flag_changer 35from devil.android.sdk import adb_wrapper 36from devil.android.sdk import build_tools 37from devil.android.sdk import intent 38from devil.android.sdk import version_codes 39from devil.utils import run_tests_helper 40 41_DIR_SOURCE_ROOT = os.path.normpath( 42 os.path.join(os.path.dirname(__file__), '..', '..')) 43_JAVA_HOME = os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current') 44 45with devil_env.SysPath( 46 os.path.join(_DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src')): 47 import colorama 48 49from incremental_install import installer 50from pylib import constants 51from pylib.symbols import deobfuscator 52from pylib.utils import simpleperf 53from pylib.utils import app_bundle_utils 54 55with devil_env.SysPath( 56 os.path.join(_DIR_SOURCE_ROOT, 'build', 'android', 'gyp')): 57 import bundletool 58 59BASE_MODULE = 'base' 60 61 62def _Colorize(text, style=''): 63 return (style 64 + text 65 + colorama.Style.RESET_ALL) 66 67 68def _InstallApk(devices, apk, install_dict): 69 def install(device): 70 if install_dict: 71 installer.Install(device, install_dict, apk=apk, permissions=[]) 72 else: 73 device.Install(apk, permissions=[], allow_downgrade=True, reinstall=True) 74 75 logging.info('Installing %sincremental apk.', '' if install_dict else 'non-') 76 device_utils.DeviceUtils.parallel(devices).pMap(install) 77 78 79# A named tuple containing the information needed to convert a bundle into 80# an installable .apks archive. 81# Fields: 82# bundle_path: Path to input bundle file. 83# bundle_apk_path: Path to output bundle .apks archive file. 84# aapt2_path: Path to aapt2 tool. 85# keystore_path: Path to keystore file. 86# keystore_password: Password for the keystore file. 87# keystore_alias: Signing key name alias within the keystore file. 88# system_image_locales: List of Chromium locales to include in system .apks. 89BundleGenerationInfo = collections.namedtuple( 90 'BundleGenerationInfo', 91 'bundle_path,bundle_apks_path,aapt2_path,keystore_path,keystore_password,' 92 'keystore_alias,system_image_locales') 93 94 95def _GenerateBundleApks(info, 96 output_path=None, 97 minimal=False, 98 minimal_sdk_version=None, 99 mode=None): 100 """Generate an .apks archive from a bundle on demand. 101 102 Args: 103 info: A BundleGenerationInfo instance. 104 output_path: Path of output .apks archive. 105 minimal: Create the minimal set of apks possible (english-only). 106 minimal_sdk_version: When minimal=True, use this sdkVersion. 107 mode: Build mode, either None, or one of app_bundle_utils.BUILD_APKS_MODES. 108 """ 109 logging.info('Generating .apks file') 110 app_bundle_utils.GenerateBundleApks( 111 info.bundle_path, 112 # Store .apks file beside the .aab file by default so that it gets cached. 113 output_path or info.bundle_apks_path, 114 info.aapt2_path, 115 info.keystore_path, 116 info.keystore_password, 117 info.keystore_alias, 118 system_image_locales=info.system_image_locales, 119 mode=mode, 120 minimal=minimal, 121 minimal_sdk_version=minimal_sdk_version) 122 123 124def _InstallBundle(devices, apk_helper_instance, package_name, 125 command_line_flags_file, modules, fake_modules): 126 # Path Chrome creates after validating fake modules. This needs to be cleared 127 # for pushed fake modules to be picked up. 128 SPLITCOMPAT_PATH = '/data/data/' + package_name + '/files/splitcompat' 129 # Chrome command line flag needed for fake modules to work. 130 FAKE_FEATURE_MODULE_INSTALL = '--fake-feature-module-install' 131 132 def ShouldWarnFakeFeatureModuleInstallFlag(device): 133 if command_line_flags_file: 134 changer = flag_changer.FlagChanger(device, command_line_flags_file) 135 return FAKE_FEATURE_MODULE_INSTALL not in changer.GetCurrentFlags() 136 return False 137 138 def ClearFakeModules(device): 139 if device.PathExists(SPLITCOMPAT_PATH, as_root=True): 140 device.RemovePath( 141 SPLITCOMPAT_PATH, force=True, recursive=True, as_root=True) 142 logging.info('Removed %s', SPLITCOMPAT_PATH) 143 else: 144 logging.info('Skipped removing nonexistent %s', SPLITCOMPAT_PATH) 145 146 def Install(device): 147 ClearFakeModules(device) 148 if fake_modules and ShouldWarnFakeFeatureModuleInstallFlag(device): 149 # Print warning if command line is not set up for fake modules. 150 msg = ('Command line has no %s: Fake modules will be ignored.' % 151 FAKE_FEATURE_MODULE_INSTALL) 152 print(_Colorize(msg, colorama.Fore.YELLOW + colorama.Style.BRIGHT)) 153 154 device.Install( 155 apk_helper_instance, 156 permissions=[], 157 modules=modules, 158 fake_modules=fake_modules, 159 allow_downgrade=True) 160 161 # Basic checks for |modules| and |fake_modules|. 162 # * |fake_modules| cannot include 'base'. 163 # * If |fake_modules| is given, ensure |modules| includes 'base'. 164 # * They must be disjoint (checked by device.Install). 165 modules_set = set(modules) if modules else set() 166 fake_modules_set = set(fake_modules) if fake_modules else set() 167 if BASE_MODULE in fake_modules_set: 168 raise Exception('\'-f {}\' is disallowed.'.format(BASE_MODULE)) 169 if fake_modules_set and BASE_MODULE not in modules_set: 170 raise Exception( 171 '\'-f FAKE\' must be accompanied by \'-m {}\''.format(BASE_MODULE)) 172 173 logging.info('Installing bundle.') 174 device_utils.DeviceUtils.parallel(devices).pMap(Install) 175 176 177def _UninstallApk(devices, install_dict, package_name): 178 def uninstall(device): 179 if install_dict: 180 installer.Uninstall(device, package_name) 181 else: 182 device.Uninstall(package_name) 183 device_utils.DeviceUtils.parallel(devices).pMap(uninstall) 184 185 186def _IsWebViewProvider(apk_helper_instance): 187 meta_data = apk_helper_instance.GetAllMetadata() 188 meta_data_keys = [pair[0] for pair in meta_data] 189 return 'com.android.webview.WebViewLibrary' in meta_data_keys 190 191 192def _SetWebViewProvider(devices, package_name): 193 194 def switch_provider(device): 195 if device.build_version_sdk < version_codes.NOUGAT: 196 logging.error('No need to switch provider on pre-Nougat devices (%s)', 197 device.serial) 198 else: 199 device.SetWebViewImplementation(package_name) 200 201 device_utils.DeviceUtils.parallel(devices).pMap(switch_provider) 202 203 204def _NormalizeProcessName(debug_process_name, package_name): 205 if not debug_process_name: 206 debug_process_name = package_name 207 elif debug_process_name.startswith(':'): 208 debug_process_name = package_name + debug_process_name 209 elif '.' not in debug_process_name: 210 debug_process_name = package_name + ':' + debug_process_name 211 return debug_process_name 212 213 214def _LaunchUrl(devices, package_name, argv=None, command_line_flags_file=None, 215 url=None, apk=None, wait_for_java_debugger=False, 216 debug_process_name=None, nokill=None): 217 if argv and command_line_flags_file is None: 218 raise Exception('This apk does not support any flags.') 219 if url: 220 # TODO(agrieve): Launch could be changed to require only package name by 221 # parsing "dumpsys package" rather than relying on the apk. 222 if not apk: 223 raise Exception('Launching with URL is not supported when using ' 224 '--package-name. Use --apk-path instead.') 225 view_activity = apk.GetViewActivityName() 226 if not view_activity: 227 raise Exception('APK does not support launching with URLs.') 228 229 debug_process_name = _NormalizeProcessName(debug_process_name, package_name) 230 231 def launch(device): 232 # --persistent is required to have Settings.Global.DEBUG_APP be set, which 233 # we currently use to allow reading of flags. https://crbug.com/784947 234 if not nokill: 235 cmd = ['am', 'set-debug-app', '--persistent', debug_process_name] 236 if wait_for_java_debugger: 237 cmd[-1:-1] = ['-w'] 238 # Ignore error since it will fail if apk is not debuggable. 239 device.RunShellCommand(cmd, check_return=False) 240 241 # The flags are first updated with input args. 242 if command_line_flags_file: 243 changer = flag_changer.FlagChanger(device, command_line_flags_file) 244 flags = [] 245 if argv: 246 adb_command_line.CheckBuildTypeSupportsFlags(device, 247 command_line_flags_file) 248 flags = shlex.split(argv) 249 try: 250 changer.ReplaceFlags(flags) 251 except device_errors.AdbShellCommandFailedError: 252 logging.exception('Failed to set flags') 253 254 if url is None: 255 # Simulate app icon click if no url is present. 256 cmd = [ 257 'am', 'start', '-p', package_name, '-c', 258 'android.intent.category.LAUNCHER', '-a', 'android.intent.action.MAIN' 259 ] 260 device.RunShellCommand(cmd, check_return=True) 261 else: 262 launch_intent = intent.Intent(action='android.intent.action.VIEW', 263 activity=view_activity, data=url, 264 package=package_name) 265 device.StartActivity(launch_intent) 266 device_utils.DeviceUtils.parallel(devices).pMap(launch) 267 if wait_for_java_debugger: 268 print('Waiting for debugger to attach to process: ' + 269 _Colorize(debug_process_name, colorama.Fore.YELLOW)) 270 271 272def _ChangeFlags(devices, argv, command_line_flags_file): 273 if argv is None: 274 _DisplayArgs(devices, command_line_flags_file) 275 else: 276 flags = shlex.split(argv) 277 def update(device): 278 adb_command_line.CheckBuildTypeSupportsFlags(device, 279 command_line_flags_file) 280 changer = flag_changer.FlagChanger(device, command_line_flags_file) 281 changer.ReplaceFlags(flags) 282 device_utils.DeviceUtils.parallel(devices).pMap(update) 283 284 285def _TargetCpuToTargetArch(target_cpu): 286 if target_cpu == 'x64': 287 return 'x86_64' 288 if target_cpu == 'mipsel': 289 return 'mips' 290 return target_cpu 291 292 293def _RunGdb(device, package_name, debug_process_name, pid, output_directory, 294 target_cpu, port, ide, verbose): 295 if not pid: 296 debug_process_name = _NormalizeProcessName(debug_process_name, package_name) 297 pid = device.GetApplicationPids(debug_process_name, at_most_one=True) 298 if not pid: 299 # Attaching gdb makes the app run so slow that it takes *minutes* to start 300 # up (as of 2018). Better to just fail than to start & attach. 301 raise Exception('App not running.') 302 303 gdb_script_path = os.path.dirname(__file__) + '/adb_gdb' 304 cmd = [ 305 gdb_script_path, 306 '--package-name=%s' % package_name, 307 '--output-directory=%s' % output_directory, 308 '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(), 309 '--device=%s' % device.serial, 310 '--pid=%s' % pid, 311 '--port=%d' % port, 312 ] 313 if ide: 314 cmd.append('--ide') 315 # Enable verbose output of adb_gdb if it's set for this script. 316 if verbose: 317 cmd.append('--verbose') 318 if target_cpu: 319 cmd.append('--target-arch=%s' % _TargetCpuToTargetArch(target_cpu)) 320 logging.warning('Running: %s', ' '.join(pipes.quote(x) for x in cmd)) 321 print(_Colorize('All subsequent output is from adb_gdb script.', 322 colorama.Fore.YELLOW)) 323 os.execv(gdb_script_path, cmd) 324 325 326def _PrintPerDeviceOutput(devices, results, single_line=False): 327 for d, result in zip(devices, results): 328 if not single_line and d is not devices[0]: 329 sys.stdout.write('\n') 330 sys.stdout.write( 331 _Colorize('{} ({}):'.format(d, d.build_description), 332 colorama.Fore.YELLOW)) 333 sys.stdout.write(' ' if single_line else '\n') 334 yield result 335 336 337def _RunMemUsage(devices, package_name, query_app=False): 338 cmd_args = ['dumpsys', 'meminfo'] 339 if not query_app: 340 cmd_args.append('--local') 341 342 def mem_usage_helper(d): 343 ret = [] 344 for process in sorted(_GetPackageProcesses(d, package_name)): 345 meminfo = d.RunShellCommand(cmd_args + [str(process.pid)]) 346 ret.append((process.name, '\n'.join(meminfo))) 347 return ret 348 349 parallel_devices = device_utils.DeviceUtils.parallel(devices) 350 all_results = parallel_devices.pMap(mem_usage_helper).pGet(None) 351 for result in _PrintPerDeviceOutput(devices, all_results): 352 if not result: 353 print('No processes found.') 354 else: 355 for name, usage in sorted(result): 356 print(_Colorize('==== Output of "dumpsys meminfo %s" ====' % name, 357 colorama.Fore.GREEN)) 358 print(usage) 359 360 361def _DuHelper(device, path_spec, run_as=None): 362 """Runs "du -s -k |path_spec|" on |device| and returns parsed result. 363 364 Args: 365 device: A DeviceUtils instance. 366 path_spec: The list of paths to run du on. May contain shell expansions 367 (will not be escaped). 368 run_as: Package name to run as, or None to run as shell user. If not None 369 and app is not android:debuggable (run-as fails), then command will be 370 run as root. 371 372 Returns: 373 A dict of path->size in KiB containing all paths in |path_spec| that exist 374 on device. Paths that do not exist are silently ignored. 375 """ 376 # Example output for: du -s -k /data/data/org.chromium.chrome/{*,.*} 377 # 144 /data/data/org.chromium.chrome/cache 378 # 8 /data/data/org.chromium.chrome/files 379 # <snip> 380 # du: .*: No such file or directory 381 382 # The -d flag works differently across android version, so use -s instead. 383 # Without the explicit 2>&1, stderr and stdout get combined at random :(. 384 cmd_str = 'du -s -k ' + path_spec + ' 2>&1' 385 lines = device.RunShellCommand(cmd_str, run_as=run_as, shell=True, 386 check_return=False) 387 output = '\n'.join(lines) 388 # run-as: Package 'com.android.chrome' is not debuggable 389 if output.startswith('run-as:'): 390 # check_return=False needed for when some paths in path_spec do not exist. 391 lines = device.RunShellCommand(cmd_str, as_root=True, shell=True, 392 check_return=False) 393 ret = {} 394 try: 395 for line in lines: 396 # du: .*: No such file or directory 397 if line.startswith('du:'): 398 continue 399 size, subpath = line.split(None, 1) 400 ret[subpath] = int(size) 401 return ret 402 except ValueError: 403 logging.error('du command was: %s', cmd_str) 404 logging.error('Failed to parse du output:\n%s', output) 405 raise 406 407 408def _RunDiskUsage(devices, package_name): 409 # Measuring dex size is a bit complicated: 410 # https://source.android.com/devices/tech/dalvik/jit-compiler 411 # 412 # For KitKat and below: 413 # dumpsys package contains: 414 # dataDir=/data/data/org.chromium.chrome 415 # codePath=/data/app/org.chromium.chrome-1.apk 416 # resourcePath=/data/app/org.chromium.chrome-1.apk 417 # nativeLibraryPath=/data/app-lib/org.chromium.chrome-1 418 # To measure odex: 419 # ls -l /data/dalvik-cache/data@app@org.chromium.chrome-1.apk@classes.dex 420 # 421 # For Android L and M (and maybe for N+ system apps): 422 # dumpsys package contains: 423 # codePath=/data/app/org.chromium.chrome-1 424 # resourcePath=/data/app/org.chromium.chrome-1 425 # legacyNativeLibraryDir=/data/app/org.chromium.chrome-1/lib 426 # To measure odex: 427 # # Option 1: 428 # /data/dalvik-cache/arm/data@app@org.chromium.chrome-1@base.apk@classes.dex 429 # /data/dalvik-cache/arm/data@app@org.chromium.chrome-1@base.apk@classes.vdex 430 # ls -l /data/dalvik-cache/profiles/org.chromium.chrome 431 # (these profiles all appear to be 0 bytes) 432 # # Option 2: 433 # ls -l /data/app/org.chromium.chrome-1/oat/arm/base.odex 434 # 435 # For Android N+: 436 # dumpsys package contains: 437 # dataDir=/data/user/0/org.chromium.chrome 438 # codePath=/data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w== 439 # resourcePath=/data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w== 440 # legacyNativeLibraryDir=/data/app/org.chromium.chrome-GUID/lib 441 # Instruction Set: arm 442 # path: /data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w==/base.apk 443 # status: /data/.../oat/arm/base.odex[status=kOatUpToDate, compilation_f 444 # ilter=quicken] 445 # Instruction Set: arm64 446 # path: /data/app/org.chromium.chrome-UuCZ71IE-i5sZgHAkU49_w==/base.apk 447 # status: /data/.../oat/arm64/base.odex[status=..., compilation_filter=q 448 # uicken] 449 # To measure odex: 450 # ls -l /data/app/.../oat/arm/base.odex 451 # ls -l /data/app/.../oat/arm/base.vdex (optional) 452 # To measure the correct odex size: 453 # cmd package compile -m speed org.chromium.chrome # For webview 454 # cmd package compile -m speed-profile org.chromium.chrome # For others 455 def disk_usage_helper(d): 456 package_output = '\n'.join(d.RunShellCommand( 457 ['dumpsys', 'package', package_name], check_return=True)) 458 # Does not return error when apk is not installed. 459 if not package_output or 'Unable to find package:' in package_output: 460 return None 461 462 # Ignore system apks that have updates installed. 463 package_output = re.sub(r'Hidden system packages:.*?^\b', '', 464 package_output, flags=re.S | re.M) 465 466 try: 467 data_dir = re.search(r'dataDir=(.*)', package_output).group(1) 468 code_path = re.search(r'codePath=(.*)', package_output).group(1) 469 lib_path = re.search(r'(?:legacyN|n)ativeLibrary(?:Dir|Path)=(.*)', 470 package_output).group(1) 471 except AttributeError: 472 raise Exception('Error parsing dumpsys output: ' + package_output) 473 474 if code_path.startswith('/system'): 475 logging.warning('Measurement of system image apks can be innacurate') 476 477 compilation_filters = set() 478 # Match "compilation_filter=value", where a line break can occur at any spot 479 # (refer to examples above). 480 awful_wrapping = r'\s*'.join('compilation_filter=') 481 for m in re.finditer(awful_wrapping + r'([\s\S]+?)[\],]', package_output): 482 compilation_filters.add(re.sub(r'\s+', '', m.group(1))) 483 compilation_filter = ','.join(sorted(compilation_filters)) 484 485 data_dir_sizes = _DuHelper(d, '%s/{*,.*}' % data_dir, run_as=package_name) 486 # Measure code_cache separately since it can be large. 487 code_cache_sizes = {} 488 code_cache_dir = next( 489 (k for k in data_dir_sizes if k.endswith('/code_cache')), None) 490 if code_cache_dir: 491 data_dir_sizes.pop(code_cache_dir) 492 code_cache_sizes = _DuHelper(d, '%s/{*,.*}' % code_cache_dir, 493 run_as=package_name) 494 495 apk_path_spec = code_path 496 if not apk_path_spec.endswith('.apk'): 497 apk_path_spec += '/*.apk' 498 apk_sizes = _DuHelper(d, apk_path_spec) 499 if lib_path.endswith('/lib'): 500 # Shows architecture subdirectory. 501 lib_sizes = _DuHelper(d, '%s/{*,.*}' % lib_path) 502 else: 503 lib_sizes = _DuHelper(d, lib_path) 504 505 # Look at all possible locations for odex files. 506 odex_paths = [] 507 for apk_path in apk_sizes: 508 mangled_apk_path = apk_path[1:].replace('/', '@') 509 apk_basename = posixpath.basename(apk_path)[:-4] 510 for ext in ('dex', 'odex', 'vdex', 'art'): 511 # Easier to check all architectures than to determine active ones. 512 for arch in ('arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64'): 513 odex_paths.append( 514 '%s/oat/%s/%s.%s' % (code_path, arch, apk_basename, ext)) 515 # No app could possibly have more than 6 dex files. 516 for suffix in ('', '2', '3', '4', '5'): 517 odex_paths.append('/data/dalvik-cache/%s/%s@classes%s.%s' % ( 518 arch, mangled_apk_path, suffix, ext)) 519 # This path does not have |arch|, so don't repeat it for every arch. 520 if arch == 'arm': 521 odex_paths.append('/data/dalvik-cache/%s@classes%s.dex' % ( 522 mangled_apk_path, suffix)) 523 524 odex_sizes = _DuHelper(d, ' '.join(pipes.quote(p) for p in odex_paths)) 525 526 return (data_dir_sizes, code_cache_sizes, apk_sizes, lib_sizes, odex_sizes, 527 compilation_filter) 528 529 def print_sizes(desc, sizes): 530 print('%s: %d KiB' % (desc, sum(sizes.itervalues()))) 531 for path, size in sorted(sizes.iteritems()): 532 print(' %s: %s KiB' % (path, size)) 533 534 parallel_devices = device_utils.DeviceUtils.parallel(devices) 535 all_results = parallel_devices.pMap(disk_usage_helper).pGet(None) 536 for result in _PrintPerDeviceOutput(devices, all_results): 537 if not result: 538 print('APK is not installed.') 539 continue 540 541 (data_dir_sizes, code_cache_sizes, apk_sizes, lib_sizes, odex_sizes, 542 compilation_filter) = result 543 total = sum(sum(sizes.itervalues()) for sizes in result[:-1]) 544 545 print_sizes('Apk', apk_sizes) 546 print_sizes('App Data (non-code cache)', data_dir_sizes) 547 print_sizes('App Data (code cache)', code_cache_sizes) 548 print_sizes('Native Libs', lib_sizes) 549 show_warning = compilation_filter and 'speed' not in compilation_filter 550 compilation_filter = compilation_filter or 'n/a' 551 print_sizes('odex (compilation_filter=%s)' % compilation_filter, odex_sizes) 552 if show_warning: 553 logging.warning('For a more realistic odex size, run:') 554 logging.warning(' %s compile-dex [speed|speed-profile]', sys.argv[0]) 555 print('Total: %s KiB (%.1f MiB)' % (total, total / 1024.0)) 556 557 558class _LogcatProcessor(object): 559 ParsedLine = collections.namedtuple( 560 'ParsedLine', 561 ['date', 'invokation_time', 'pid', 'tid', 'priority', 'tag', 'message']) 562 563 class NativeStackSymbolizer(object): 564 """Buffers lines from native stacks and symbolizes them when done.""" 565 # E.g.: #06 pc 0x0000d519 /apex/com.android.runtime/lib/libart.so 566 # E.g.: #01 pc 00180c8d /data/data/.../lib/libbase.cr.so 567 _STACK_PATTERN = re.compile(r'\s*#\d+\s+(?:pc )?(0x)?[0-9a-f]{8,16}\s') 568 569 def __init__(self, stack_script_context, print_func): 570 # To symbolize native stacks, we need to pass all lines at once. 571 self._stack_script_context = stack_script_context 572 self._print_func = print_func 573 self._crash_lines_buffer = None 574 575 def _FlushLines(self): 576 """Prints queued lines after sending them through stack.py.""" 577 crash_lines = self._crash_lines_buffer 578 self._crash_lines_buffer = None 579 with tempfile.NamedTemporaryFile() as f: 580 f.writelines(x[0].message + '\n' for x in crash_lines) 581 f.flush() 582 proc = self._stack_script_context.Popen( 583 input_file=f.name, stdout=subprocess.PIPE) 584 lines = proc.communicate()[0].splitlines() 585 586 for i, line in enumerate(lines): 587 parsed_line, dim = crash_lines[min(i, len(crash_lines) - 1)] 588 d = parsed_line._asdict() 589 d['message'] = line 590 parsed_line = _LogcatProcessor.ParsedLine(**d) 591 self._print_func(parsed_line, dim) 592 593 def AddLine(self, parsed_line, dim): 594 # Assume all lines from DEBUG are stacks. 595 # Also look for "stack-looking" lines to catch manual stack prints. 596 # It's important to not buffer non-stack lines because stack.py does not 597 # pass them through. 598 is_crash_line = parsed_line.tag == 'DEBUG' or (self._STACK_PATTERN.match( 599 parsed_line.message)) 600 601 if is_crash_line: 602 if self._crash_lines_buffer is None: 603 self._crash_lines_buffer = [] 604 self._crash_lines_buffer.append((parsed_line, dim)) 605 return 606 607 if self._crash_lines_buffer is not None: 608 self._FlushLines() 609 610 self._print_func(parsed_line, dim) 611 612 613 # Logcat tags for messages that are generally relevant but are not from PIDs 614 # associated with the apk. 615 _WHITELISTED_TAGS = { 616 'ActivityManager', # Shows activity lifecycle messages. 617 'ActivityTaskManager', # More activity lifecycle messages. 618 'AndroidRuntime', # Java crash dumps 619 'DEBUG', # Native crash dump. 620 } 621 622 # Matches messages only on pre-L (Dalvik) that are spammy and unimportant. 623 _DALVIK_IGNORE_PATTERN = re.compile('|'.join([ 624 r'^Added shared lib', 625 r'^Could not find ', 626 r'^DexOpt:', 627 r'^GC_', 628 r'^Late-enabling CheckJNI', 629 r'^Link of class', 630 r'^No JNI_OnLoad found in', 631 r'^Trying to load lib', 632 r'^Unable to resolve superclass', 633 r'^VFY:', 634 r'^WAIT_', 635 ])) 636 637 def __init__(self, 638 device, 639 package_name, 640 stack_script_context, 641 deobfuscate=None, 642 verbose=False): 643 self._device = device 644 self._package_name = package_name 645 self._verbose = verbose 646 self._deobfuscator = deobfuscate 647 self._native_stack_symbolizer = _LogcatProcessor.NativeStackSymbolizer( 648 stack_script_context, self._PrintParsedLine) 649 # Process ID for the app's main process (with no :name suffix). 650 self._primary_pid = None 651 # Set of all Process IDs that belong to the app. 652 self._my_pids = set() 653 # Set of all Process IDs that we've parsed at some point. 654 self._seen_pids = set() 655 # Start proc 22953:com.google.chromeremotedesktop/ 656 self._pid_pattern = re.compile(r'Start proc (\d+):{}/'.format(package_name)) 657 # START u0 {act=android.intent.action.MAIN \ 658 # cat=[android.intent.category.LAUNCHER] \ 659 # flg=0x10000000 pkg=com.google.chromeremotedesktop} from uid 2000 660 self._start_pattern = re.compile(r'START .*pkg=' + package_name) 661 662 self.nonce = 'Chromium apk_operations.py nonce={}'.format(random.random()) 663 # Holds lines buffered on start-up, before we find our nonce message. 664 self._initial_buffered_lines = [] 665 self._UpdateMyPids() 666 # Give preference to PID reported by "ps" over those found from 667 # _start_pattern. There can be multiple "Start proc" messages from prior 668 # runs of the app. 669 self._found_initial_pid = self._primary_pid != None 670 671 def _UpdateMyPids(self): 672 # We intentionally do not clear self._my_pids to make sure that the 673 # ProcessLine method below also includes lines from processes which may 674 # have already exited. 675 self._primary_pid = None 676 for process in _GetPackageProcesses(self._device, self._package_name): 677 # We take only the first "main" process found in order to account for 678 # possibly forked() processes. 679 if ':' not in process.name and self._primary_pid is None: 680 self._primary_pid = process.pid 681 self._my_pids.add(process.pid) 682 683 def _GetPidStyle(self, pid, dim=False): 684 if pid == self._primary_pid: 685 return colorama.Fore.WHITE 686 elif pid in self._my_pids: 687 # TODO(wnwen): Use one separate persistent color per process, pop LRU 688 return colorama.Fore.YELLOW 689 elif dim: 690 return colorama.Style.DIM 691 return '' 692 693 def _GetPriorityStyle(self, priority, dim=False): 694 # pylint:disable=no-self-use 695 if dim: 696 return '' 697 style = colorama.Fore.BLACK 698 if priority == 'E' or priority == 'F': 699 style += colorama.Back.RED 700 elif priority == 'W': 701 style += colorama.Back.YELLOW 702 elif priority == 'I': 703 style += colorama.Back.GREEN 704 elif priority == 'D': 705 style += colorama.Back.BLUE 706 return style 707 708 def _ParseLine(self, line): 709 tokens = line.split(None, 6) 710 711 def consume_token_or_default(default): 712 return tokens.pop(0) if len(tokens) > 0 else default 713 714 date = consume_token_or_default('') 715 invokation_time = consume_token_or_default('') 716 pid = int(consume_token_or_default(-1)) 717 tid = int(consume_token_or_default(-1)) 718 priority = consume_token_or_default('') 719 tag = consume_token_or_default('') 720 original_message = consume_token_or_default('') 721 722 # Example: 723 # 09-19 06:35:51.113 9060 9154 W GCoreFlp: No location... 724 # 09-19 06:01:26.174 9060 10617 I Auth : [ReflectiveChannelBinder]... 725 # Parsing "GCoreFlp:" vs "Auth :", we only want tag to contain the word, 726 # and we don't want to keep the colon for the message. 727 if tag and tag[-1] == ':': 728 tag = tag[:-1] 729 elif len(original_message) > 2: 730 original_message = original_message[2:] 731 return self.ParsedLine( 732 date, invokation_time, pid, tid, priority, tag, original_message) 733 734 def _PrintParsedLine(self, parsed_line, dim=False): 735 tid_style = colorama.Style.NORMAL 736 # Make the main thread bright. 737 if not dim and parsed_line.pid == parsed_line.tid: 738 tid_style = colorama.Style.BRIGHT 739 pid_style = self._GetPidStyle(parsed_line.pid, dim) 740 # We have to pad before adding color as that changes the width of the tag. 741 pid_str = _Colorize('{:5}'.format(parsed_line.pid), pid_style) 742 tid_str = _Colorize('{:5}'.format(parsed_line.tid), tid_style) 743 tag = _Colorize('{:8}'.format(parsed_line.tag), 744 pid_style + ('' if dim else colorama.Style.BRIGHT)) 745 priority = _Colorize(parsed_line.priority, 746 self._GetPriorityStyle(parsed_line.priority)) 747 messages = [parsed_line.message] 748 if self._deobfuscator: 749 messages = self._deobfuscator.TransformLines(messages) 750 for message in messages: 751 message = _Colorize(message, pid_style) 752 sys.stdout.write('{} {} {} {} {} {}: {}\n'.format( 753 parsed_line.date, parsed_line.invokation_time, pid_str, tid_str, 754 priority, tag, message)) 755 756 def _TriggerNonceFound(self): 757 # Once the nonce is hit, we have confidence that we know which lines 758 # belong to the current run of the app. Process all of the buffered lines. 759 if self._primary_pid: 760 for args in self._initial_buffered_lines: 761 self._native_stack_symbolizer.AddLine(*args) 762 self._initial_buffered_lines = None 763 self.nonce = None 764 765 def ProcessLine(self, line): 766 if not line or line.startswith('------'): 767 return 768 769 if self.nonce and self.nonce in line: 770 self._TriggerNonceFound() 771 772 nonce_found = self.nonce is None 773 774 log = self._ParseLine(line) 775 if log.pid not in self._seen_pids: 776 self._seen_pids.add(log.pid) 777 if nonce_found: 778 # Update list of owned PIDs each time a new PID is encountered. 779 self._UpdateMyPids() 780 781 # Search for "Start proc $pid:$package_name/" message. 782 if not nonce_found: 783 # Capture logs before the nonce. Start with the most recent "am start". 784 if self._start_pattern.match(log.message): 785 self._initial_buffered_lines = [] 786 787 # If we didn't find the PID via "ps", then extract it from log messages. 788 # This will happen if the app crashes too quickly. 789 if not self._found_initial_pid: 790 m = self._pid_pattern.match(log.message) 791 if m: 792 # Find the most recent "Start proc" line before the nonce. 793 # Track only the primary pid in this mode. 794 # The main use-case is to find app logs when no current PIDs exist. 795 # E.g.: When the app crashes on launch. 796 self._primary_pid = m.group(1) 797 self._my_pids.clear() 798 self._my_pids.add(m.group(1)) 799 800 owned_pid = log.pid in self._my_pids 801 if owned_pid and not self._verbose and log.tag == 'dalvikvm': 802 if self._DALVIK_IGNORE_PATTERN.match(log.message): 803 return 804 805 if owned_pid or self._verbose or (log.priority == 'F' or # Java crash dump 806 log.tag in self._WHITELISTED_TAGS): 807 if nonce_found: 808 self._native_stack_symbolizer.AddLine(log, not owned_pid) 809 else: 810 self._initial_buffered_lines.append((log, not owned_pid)) 811 812 813def _RunLogcat(device, package_name, stack_script_context, deobfuscate, 814 verbose): 815 logcat_processor = _LogcatProcessor( 816 device, package_name, stack_script_context, deobfuscate, verbose) 817 device.RunShellCommand(['log', logcat_processor.nonce]) 818 for line in device.adb.Logcat(logcat_format='threadtime'): 819 try: 820 logcat_processor.ProcessLine(line) 821 except: 822 sys.stderr.write('Failed to process line: ' + line + '\n') 823 # Skip stack trace for the common case of the adb server being 824 # restarted. 825 if 'unexpected EOF' in line: 826 sys.exit(1) 827 raise 828 829 830def _GetPackageProcesses(device, package_name): 831 return [ 832 p for p in device.ListProcesses(package_name) 833 if p.name == package_name or p.name.startswith(package_name + ':')] 834 835 836def _RunPs(devices, package_name): 837 parallel_devices = device_utils.DeviceUtils.parallel(devices) 838 all_processes = parallel_devices.pMap( 839 lambda d: _GetPackageProcesses(d, package_name)).pGet(None) 840 for processes in _PrintPerDeviceOutput(devices, all_processes): 841 if not processes: 842 print('No processes found.') 843 else: 844 proc_map = collections.defaultdict(list) 845 for p in processes: 846 proc_map[p.name].append(str(p.pid)) 847 for name, pids in sorted(proc_map.items()): 848 print(name, ','.join(pids)) 849 850 851def _RunShell(devices, package_name, cmd): 852 if cmd: 853 parallel_devices = device_utils.DeviceUtils.parallel(devices) 854 outputs = parallel_devices.RunShellCommand( 855 cmd, run_as=package_name).pGet(None) 856 for output in _PrintPerDeviceOutput(devices, outputs): 857 for line in output: 858 print(line) 859 else: 860 adb_path = adb_wrapper.AdbWrapper.GetAdbPath() 861 cmd = [adb_path, '-s', devices[0].serial, 'shell'] 862 # Pre-N devices do not support -t flag. 863 if devices[0].build_version_sdk >= version_codes.NOUGAT: 864 cmd += ['-t', 'run-as', package_name] 865 else: 866 print('Upon entering the shell, run:') 867 print('run-as', package_name) 868 print() 869 os.execv(adb_path, cmd) 870 871 872def _RunCompileDex(devices, package_name, compilation_filter): 873 cmd = ['cmd', 'package', 'compile', '-f', '-m', compilation_filter, 874 package_name] 875 parallel_devices = device_utils.DeviceUtils.parallel(devices) 876 outputs = parallel_devices.RunShellCommand(cmd, timeout=120).pGet(None) 877 for output in _PrintPerDeviceOutput(devices, outputs): 878 for line in output: 879 print(line) 880 881 882def _RunProfile(device, package_name, host_build_directory, pprof_out_path, 883 process_specifier, thread_specifier, extra_args): 884 simpleperf.PrepareDevice(device) 885 device_simpleperf_path = simpleperf.InstallSimpleperf(device, package_name) 886 with tempfile.NamedTemporaryFile() as fh: 887 host_simpleperf_out_path = fh.name 888 889 with simpleperf.RunSimpleperf(device, device_simpleperf_path, package_name, 890 process_specifier, thread_specifier, 891 extra_args, host_simpleperf_out_path): 892 sys.stdout.write('Profiler is running; press Enter to stop...') 893 sys.stdin.read(1) 894 sys.stdout.write('Post-processing data...') 895 sys.stdout.flush() 896 897 simpleperf.ConvertSimpleperfToPprof(host_simpleperf_out_path, 898 host_build_directory, pprof_out_path) 899 print(textwrap.dedent(""" 900 Profile data written to %(s)s. 901 902 To view profile as a call graph in browser: 903 pprof -web %(s)s 904 905 To print the hottest methods: 906 pprof -top %(s)s 907 908 pprof has many useful customization options; `pprof --help` for details. 909 """ % {'s': pprof_out_path})) 910 911 912class _StackScriptContext(object): 913 """Maintains temporary files needed by stack.py.""" 914 915 def __init__(self, 916 output_directory, 917 apk_path, 918 bundle_generation_info, 919 quiet=False): 920 self._output_directory = output_directory 921 self._apk_path = apk_path 922 self._bundle_generation_info = bundle_generation_info 923 self._staging_dir = None 924 self._quiet = quiet 925 926 def _CreateStaging(self): 927 # In many cases, stack decoding requires APKs to map trace lines to native 928 # libraries. Create a temporary directory, and either unpack a bundle's 929 # APKS into it, or simply symlink the standalone APK into it. This 930 # provides an unambiguous set of APK files for the stack decoding process 931 # to inspect. 932 logging.debug('Creating stack staging directory') 933 self._staging_dir = tempfile.mkdtemp() 934 bundle_generation_info = self._bundle_generation_info 935 936 if bundle_generation_info: 937 # TODO(wnwen): Use apk_helper instead. 938 _GenerateBundleApks(bundle_generation_info) 939 logging.debug('Extracting .apks file') 940 with zipfile.ZipFile(bundle_generation_info.bundle_apks_path, 'r') as z: 941 files_to_extract = [ 942 f for f in z.namelist() if f.endswith('-master.apk') 943 ] 944 z.extractall(self._staging_dir, files_to_extract) 945 elif self._apk_path: 946 # Otherwise an incremental APK and an empty apks directory is correct. 947 output = os.path.join(self._staging_dir, os.path.basename(self._apk_path)) 948 os.symlink(self._apk_path, output) 949 950 def Close(self): 951 if self._staging_dir: 952 logging.debug('Clearing stack staging directory') 953 shutil.rmtree(self._staging_dir) 954 self._staging_dir = None 955 956 def Popen(self, input_file=None, **kwargs): 957 if self._staging_dir is None: 958 self._CreateStaging() 959 stack_script = os.path.join( 960 constants.host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, 961 'stack.py') 962 cmd = [ 963 stack_script, '--output-directory', self._output_directory, 964 '--apks-directory', self._staging_dir 965 ] 966 if self._quiet: 967 cmd.append('--quiet') 968 if input_file: 969 cmd.append(input_file) 970 logging.info('Running stack.py') 971 return subprocess.Popen(cmd, **kwargs) 972 973 974def _GenerateAvailableDevicesMessage(devices): 975 devices_obj = device_utils.DeviceUtils.parallel(devices) 976 descriptions = devices_obj.pMap(lambda d: d.build_description).pGet(None) 977 msg = 'Available devices:\n' 978 for d, desc in zip(devices, descriptions): 979 msg += ' %s (%s)\n' % (d, desc) 980 return msg 981 982 983# TODO(agrieve):add "--all" in the MultipleDevicesError message and use it here. 984def _GenerateMissingAllFlagMessage(devices): 985 return ('More than one device available. Use --all to select all devices, ' + 986 'or use --device to select a device by serial.\n\n' + 987 _GenerateAvailableDevicesMessage(devices)) 988 989 990def _DisplayArgs(devices, command_line_flags_file): 991 def flags_helper(d): 992 changer = flag_changer.FlagChanger(d, command_line_flags_file) 993 return changer.GetCurrentFlags() 994 995 parallel_devices = device_utils.DeviceUtils.parallel(devices) 996 outputs = parallel_devices.pMap(flags_helper).pGet(None) 997 print('Existing flags per-device (via /data/local/tmp/{}):'.format( 998 command_line_flags_file)) 999 for flags in _PrintPerDeviceOutput(devices, outputs, single_line=True): 1000 quoted_flags = ' '.join(pipes.quote(f) for f in flags) 1001 print(quoted_flags or 'No flags set.') 1002 1003 1004def _DeviceCachePath(device, output_directory): 1005 file_name = 'device_cache_%s.json' % device.serial 1006 return os.path.join(output_directory, file_name) 1007 1008 1009def _LoadDeviceCaches(devices, output_directory): 1010 if not output_directory: 1011 return 1012 for d in devices: 1013 cache_path = _DeviceCachePath(d, output_directory) 1014 if os.path.exists(cache_path): 1015 logging.debug('Using device cache: %s', cache_path) 1016 with open(cache_path) as f: 1017 d.LoadCacheData(f.read()) 1018 # Delete the cached file so that any exceptions cause it to be cleared. 1019 os.unlink(cache_path) 1020 else: 1021 logging.debug('No cache present for device: %s', d) 1022 1023 1024def _SaveDeviceCaches(devices, output_directory): 1025 if not output_directory: 1026 return 1027 for d in devices: 1028 cache_path = _DeviceCachePath(d, output_directory) 1029 with open(cache_path, 'w') as f: 1030 f.write(d.DumpCacheData()) 1031 logging.info('Wrote device cache: %s', cache_path) 1032 1033 1034class _Command(object): 1035 name = None 1036 description = None 1037 long_description = None 1038 needs_package_name = False 1039 needs_output_directory = False 1040 needs_apk_helper = False 1041 supports_incremental = False 1042 accepts_command_line_flags = False 1043 accepts_args = False 1044 need_device_args = True 1045 all_devices_by_default = False 1046 calls_exec = False 1047 supports_multiple_devices = True 1048 1049 def __init__(self, from_wrapper_script, is_bundle): 1050 self._parser = None 1051 self._from_wrapper_script = from_wrapper_script 1052 self.args = None 1053 self.apk_helper = None 1054 self.additional_apk_helpers = None 1055 self.install_dict = None 1056 self.devices = None 1057 self.is_bundle = is_bundle 1058 self.bundle_generation_info = None 1059 # Only support incremental install from APK wrapper scripts. 1060 if is_bundle or not from_wrapper_script: 1061 self.supports_incremental = False 1062 1063 def RegisterBundleGenerationInfo(self, bundle_generation_info): 1064 self.bundle_generation_info = bundle_generation_info 1065 1066 def _RegisterExtraArgs(self, subp): 1067 pass 1068 1069 def RegisterArgs(self, parser): 1070 subp = parser.add_parser( 1071 self.name, help=self.description, 1072 description=self.long_description or self.description, 1073 formatter_class=argparse.RawDescriptionHelpFormatter) 1074 self._parser = subp 1075 subp.set_defaults(command=self) 1076 if self.need_device_args: 1077 subp.add_argument('--all', 1078 action='store_true', 1079 default=self.all_devices_by_default, 1080 help='Operate on all connected devices.',) 1081 subp.add_argument('-d', 1082 '--device', 1083 action='append', 1084 default=[], 1085 dest='devices', 1086 help='Target device for script to work on. Enter ' 1087 'multiple times for multiple devices.') 1088 subp.add_argument('-v', 1089 '--verbose', 1090 action='count', 1091 default=0, 1092 dest='verbose_count', 1093 help='Verbose level (multiple times for more)') 1094 group = subp.add_argument_group('%s arguments' % self.name) 1095 1096 if self.needs_package_name: 1097 # Three cases to consider here, since later code assumes 1098 # self.args.package_name always exists, even if None: 1099 # 1100 # - Called from a bundle wrapper script, the package_name is already 1101 # set through parser.set_defaults(), so don't call add_argument() 1102 # to avoid overriding its value. 1103 # 1104 # - Called from an apk wrapper script. The --package-name argument 1105 # should not appear, but self.args.package_name will be gleaned from 1106 # the --apk-path file later. 1107 # 1108 # - Called directly, then --package-name is required on the command-line. 1109 # 1110 if not self.is_bundle: 1111 group.add_argument( 1112 '--package-name', 1113 help=argparse.SUPPRESS if self._from_wrapper_script else ( 1114 "App's package name.")) 1115 1116 if self.needs_apk_helper or self.needs_package_name: 1117 # Adding this argument to the subparser would override the set_defaults() 1118 # value set by on the parent parser (even if None). 1119 if not self._from_wrapper_script and not self.is_bundle: 1120 group.add_argument( 1121 '--apk-path', required=self.needs_apk_helper, help='Path to .apk') 1122 1123 if self.supports_incremental: 1124 group.add_argument('--incremental', 1125 action='store_true', 1126 default=False, 1127 help='Always install an incremental apk.') 1128 group.add_argument('--non-incremental', 1129 action='store_true', 1130 default=False, 1131 help='Always install a non-incremental apk.') 1132 1133 # accepts_command_line_flags and accepts_args are mutually exclusive. 1134 # argparse will throw if they are both set. 1135 if self.accepts_command_line_flags: 1136 group.add_argument( 1137 '--args', help='Command-line flags. Use = to assign args.') 1138 1139 if self.accepts_args: 1140 group.add_argument( 1141 '--args', help='Extra arguments. Use = to assign args') 1142 1143 if not self._from_wrapper_script and self.accepts_command_line_flags: 1144 # Provided by wrapper scripts. 1145 group.add_argument( 1146 '--command-line-flags-file', 1147 help='Name of the command-line flags file') 1148 1149 self._RegisterExtraArgs(group) 1150 1151 def _CreateApkHelpers(self, args, incremental_apk_path, install_dict): 1152 """Returns true iff self.apk_helper was created and assigned.""" 1153 if self.apk_helper is None: 1154 if args.apk_path: 1155 self.apk_helper = apk_helper.ToHelper(args.apk_path) 1156 elif incremental_apk_path: 1157 self.install_dict = install_dict 1158 self.apk_helper = apk_helper.ToHelper(incremental_apk_path) 1159 elif self.is_bundle: 1160 _GenerateBundleApks(self.bundle_generation_info) 1161 self.apk_helper = apk_helper.ToHelper( 1162 self.bundle_generation_info.bundle_apks_path) 1163 if args.additional_apk_paths and self.additional_apk_helpers is None: 1164 self.additional_apk_helpers = [ 1165 apk_helper.ToHelper(apk_path) 1166 for apk_path in args.additional_apk_paths 1167 ] 1168 return self.apk_helper is not None 1169 1170 def ProcessArgs(self, args): 1171 self.args = args 1172 # Ensure these keys always exist. They are set by wrapper scripts, but not 1173 # always added when not using wrapper scripts. 1174 args.__dict__.setdefault('apk_path', None) 1175 args.__dict__.setdefault('incremental_json', None) 1176 1177 incremental_apk_path = None 1178 install_dict = None 1179 if args.incremental_json and not (self.supports_incremental and 1180 args.non_incremental): 1181 with open(args.incremental_json) as f: 1182 install_dict = json.load(f) 1183 incremental_apk_path = os.path.join(args.output_directory, 1184 install_dict['apk_path']) 1185 if not os.path.exists(incremental_apk_path): 1186 incremental_apk_path = None 1187 1188 if self.supports_incremental: 1189 if args.incremental and args.non_incremental: 1190 self._parser.error('Must use only one of --incremental and ' 1191 '--non-incremental') 1192 elif args.non_incremental: 1193 if not args.apk_path: 1194 self._parser.error('Apk has not been built.') 1195 elif args.incremental: 1196 if not incremental_apk_path: 1197 self._parser.error('Incremental apk has not been built.') 1198 args.apk_path = None 1199 1200 if args.apk_path and incremental_apk_path: 1201 self._parser.error('Both incremental and non-incremental apks exist. ' 1202 'Select using --incremental or --non-incremental') 1203 1204 1205 # Gate apk_helper creation with _CreateApkHelpers since for bundles it takes 1206 # a while to unpack the apks file from the aab file, so avoid this slowdown 1207 # for simple commands that don't need apk_helper. 1208 if self.needs_apk_helper: 1209 if not self._CreateApkHelpers(args, incremental_apk_path, install_dict): 1210 self._parser.error('App is not built.') 1211 1212 if self.needs_package_name and not args.package_name: 1213 if self._CreateApkHelpers(args, incremental_apk_path, install_dict): 1214 args.package_name = self.apk_helper.GetPackageName() 1215 elif self._from_wrapper_script: 1216 self._parser.error('App is not built.') 1217 else: 1218 self._parser.error('One of --package-name or --apk-path is required.') 1219 1220 self.devices = [] 1221 if self.need_device_args: 1222 abis = None 1223 if self._CreateApkHelpers(args, incremental_apk_path, install_dict): 1224 abis = self.apk_helper.GetAbis() 1225 self.devices = device_utils.DeviceUtils.HealthyDevices( 1226 device_arg=args.devices, 1227 enable_device_files_cache=bool(args.output_directory), 1228 default_retries=0, 1229 abis=abis) 1230 # TODO(agrieve): Device cache should not depend on output directory. 1231 # Maybe put into /tmp? 1232 _LoadDeviceCaches(self.devices, args.output_directory) 1233 1234 try: 1235 if len(self.devices) > 1: 1236 if not self.supports_multiple_devices: 1237 self._parser.error(device_errors.MultipleDevicesError(self.devices)) 1238 if not args.all and not args.devices: 1239 self._parser.error(_GenerateMissingAllFlagMessage(self.devices)) 1240 # Save cache now if command will not get a chance to afterwards. 1241 if self.calls_exec: 1242 _SaveDeviceCaches(self.devices, args.output_directory) 1243 except: 1244 _SaveDeviceCaches(self.devices, args.output_directory) 1245 raise 1246 1247 1248class _DevicesCommand(_Command): 1249 name = 'devices' 1250 description = 'Describe attached devices.' 1251 all_devices_by_default = True 1252 1253 def Run(self): 1254 print(_GenerateAvailableDevicesMessage(self.devices)) 1255 1256 1257class _PackageInfoCommand(_Command): 1258 name = 'package-info' 1259 description = 'Show various attributes of this app.' 1260 need_device_args = False 1261 needs_package_name = True 1262 needs_apk_helper = True 1263 1264 def Run(self): 1265 # Format all (even ints) as strings, to handle cases where APIs return None 1266 print('Package name: "%s"' % self.args.package_name) 1267 print('versionCode: %s' % self.apk_helper.GetVersionCode()) 1268 print('versionName: "%s"' % self.apk_helper.GetVersionName()) 1269 print('minSdkVersion: %s' % self.apk_helper.GetMinSdkVersion()) 1270 print('targetSdkVersion: %s' % self.apk_helper.GetTargetSdkVersion()) 1271 print('Supported ABIs: %r' % self.apk_helper.GetAbis()) 1272 1273 1274class _InstallCommand(_Command): 1275 name = 'install' 1276 description = 'Installs the APK or bundle to one or more devices.' 1277 needs_apk_helper = True 1278 supports_incremental = True 1279 1280 def _RegisterExtraArgs(self, group): 1281 if self.is_bundle: 1282 group.add_argument( 1283 '-m', 1284 '--module', 1285 action='append', 1286 help='Module to install. Can be specified multiple times. ' + 1287 'One of them has to be \'{}\''.format(BASE_MODULE)) 1288 group.add_argument( 1289 '-f', 1290 '--fake', 1291 action='append', 1292 help='Fake bundle module install. Can be specified multiple times. ' 1293 'Requires \'-m {0}\' to be given, and \'-f {0}\' is illegal.'.format( 1294 BASE_MODULE)) 1295 1296 def Run(self): 1297 if self.additional_apk_helpers: 1298 for additional_apk_helper in self.additional_apk_helpers: 1299 _InstallApk(self.devices, additional_apk_helper, None) 1300 if self.is_bundle: 1301 _InstallBundle(self.devices, self.apk_helper, self.args.package_name, 1302 self.args.command_line_flags_file, self.args.module, 1303 self.args.fake) 1304 else: 1305 _InstallApk(self.devices, self.apk_helper, self.install_dict) 1306 1307 1308class _UninstallCommand(_Command): 1309 name = 'uninstall' 1310 description = 'Removes the APK or bundle from one or more devices.' 1311 needs_package_name = True 1312 1313 def Run(self): 1314 _UninstallApk(self.devices, self.install_dict, self.args.package_name) 1315 1316 1317class _SetWebViewProviderCommand(_Command): 1318 name = 'set-webview-provider' 1319 description = ("Sets the device's WebView provider to this APK's " 1320 "package name.") 1321 needs_package_name = True 1322 needs_apk_helper = True 1323 1324 def Run(self): 1325 if not _IsWebViewProvider(self.apk_helper): 1326 raise Exception('This package does not have a WebViewLibrary meta-data ' 1327 'tag. Are you sure it contains a WebView implementation?') 1328 _SetWebViewProvider(self.devices, self.args.package_name) 1329 1330 1331class _LaunchCommand(_Command): 1332 name = 'launch' 1333 description = ('Sends a launch intent for the APK or bundle after first ' 1334 'writing the command-line flags file.') 1335 needs_package_name = True 1336 accepts_command_line_flags = True 1337 all_devices_by_default = True 1338 1339 def _RegisterExtraArgs(self, group): 1340 group.add_argument('-w', '--wait-for-java-debugger', action='store_true', 1341 help='Pause execution until debugger attaches. Applies ' 1342 'only to the main process. To have renderers wait, ' 1343 'use --args="--renderer-wait-for-java-debugger"') 1344 group.add_argument('--debug-process-name', 1345 help='Name of the process to debug. ' 1346 'E.g. "privileged_process0", or "foo.bar:baz"') 1347 group.add_argument('--nokill', action='store_true', 1348 help='Do not set the debug-app, nor set command-line ' 1349 'flags. Useful to load a URL without having the ' 1350 'app restart.') 1351 group.add_argument('url', nargs='?', help='A URL to launch with.') 1352 1353 def Run(self): 1354 if self.args.url and self.is_bundle: 1355 # TODO(digit): Support this, maybe by using 'dumpsys' as described 1356 # in the _LaunchUrl() comment. 1357 raise Exception('Launching with URL not supported for bundles yet!') 1358 _LaunchUrl(self.devices, self.args.package_name, argv=self.args.args, 1359 command_line_flags_file=self.args.command_line_flags_file, 1360 url=self.args.url, apk=self.apk_helper, 1361 wait_for_java_debugger=self.args.wait_for_java_debugger, 1362 debug_process_name=self.args.debug_process_name, 1363 nokill=self.args.nokill) 1364 1365 1366class _StopCommand(_Command): 1367 name = 'stop' 1368 description = 'Force-stops the app.' 1369 needs_package_name = True 1370 all_devices_by_default = True 1371 1372 def Run(self): 1373 device_utils.DeviceUtils.parallel(self.devices).ForceStop( 1374 self.args.package_name) 1375 1376 1377class _ClearDataCommand(_Command): 1378 name = 'clear-data' 1379 descriptions = 'Clears all app data.' 1380 needs_package_name = True 1381 all_devices_by_default = True 1382 1383 def Run(self): 1384 device_utils.DeviceUtils.parallel(self.devices).ClearApplicationState( 1385 self.args.package_name) 1386 1387 1388class _ArgvCommand(_Command): 1389 name = 'argv' 1390 description = 'Display and optionally update command-line flags file.' 1391 needs_package_name = True 1392 accepts_command_line_flags = True 1393 all_devices_by_default = True 1394 1395 def Run(self): 1396 _ChangeFlags(self.devices, self.args.args, 1397 self.args.command_line_flags_file) 1398 1399 1400class _GdbCommand(_Command): 1401 name = 'gdb' 1402 description = 'Runs //build/android/adb_gdb with apk-specific args.' 1403 long_description = description + """ 1404 1405To attach to a process other than the APK's main process, use --pid=1234. 1406To list all PIDs, use the "ps" command. 1407 1408If no apk process is currently running, sends a launch intent. 1409""" 1410 needs_package_name = True 1411 needs_output_directory = True 1412 calls_exec = True 1413 supports_multiple_devices = False 1414 1415 def Run(self): 1416 _RunGdb(self.devices[0], self.args.package_name, 1417 self.args.debug_process_name, self.args.pid, 1418 self.args.output_directory, self.args.target_cpu, self.args.port, 1419 self.args.ide, bool(self.args.verbose_count)) 1420 1421 def _RegisterExtraArgs(self, group): 1422 pid_group = group.add_mutually_exclusive_group() 1423 pid_group.add_argument('--debug-process-name', 1424 help='Name of the process to attach to. ' 1425 'E.g. "privileged_process0", or "foo.bar:baz"') 1426 pid_group.add_argument('--pid', 1427 help='The process ID to attach to. Defaults to ' 1428 'the main process for the package.') 1429 group.add_argument('--ide', action='store_true', 1430 help='Rather than enter a gdb prompt, set up the ' 1431 'gdb connection and wait for an IDE to ' 1432 'connect.') 1433 # Same default port that ndk-gdb.py uses. 1434 group.add_argument('--port', type=int, default=5039, 1435 help='Use the given port for the GDB connection') 1436 1437 1438class _LogcatCommand(_Command): 1439 name = 'logcat' 1440 description = 'Runs "adb logcat" with filters relevant the current APK.' 1441 long_description = description + """ 1442 1443"Relevant filters" means: 1444 * Log messages from processes belonging to the apk, 1445 * Plus log messages from log tags: ActivityManager|DEBUG, 1446 * Plus fatal logs from any process, 1447 * Minus spamy dalvikvm logs (for pre-L devices). 1448 1449Colors: 1450 * Primary process is white 1451 * Other processes (gpu, renderer) are yellow 1452 * Non-apk processes are grey 1453 * UI thread has a bolded Thread-ID 1454 1455Java stack traces are detected and deobfuscated (for release builds). 1456 1457To disable filtering, (but keep coloring), use --verbose. 1458""" 1459 needs_package_name = True 1460 supports_multiple_devices = False 1461 1462 def Run(self): 1463 deobfuscate = None 1464 if self.args.proguard_mapping_path and not self.args.no_deobfuscate: 1465 deobfuscate = deobfuscator.Deobfuscator(self.args.proguard_mapping_path) 1466 1467 stack_script_context = _StackScriptContext( 1468 self.args.output_directory, 1469 self.args.apk_path, 1470 self.bundle_generation_info, 1471 quiet=True) 1472 try: 1473 _RunLogcat(self.devices[0], self.args.package_name, stack_script_context, 1474 deobfuscate, bool(self.args.verbose_count)) 1475 except KeyboardInterrupt: 1476 pass # Don't show stack trace upon Ctrl-C 1477 finally: 1478 stack_script_context.Close() 1479 if deobfuscate: 1480 deobfuscate.Close() 1481 1482 def _RegisterExtraArgs(self, group): 1483 if self._from_wrapper_script: 1484 group.add_argument('--no-deobfuscate', action='store_true', 1485 help='Disables ProGuard deobfuscation of logcat.') 1486 else: 1487 group.set_defaults(no_deobfuscate=False) 1488 group.add_argument('--proguard-mapping-path', 1489 help='Path to ProGuard map (enables deobfuscation)') 1490 1491 1492class _PsCommand(_Command): 1493 name = 'ps' 1494 description = 'Show PIDs of any APK processes currently running.' 1495 needs_package_name = True 1496 all_devices_by_default = True 1497 1498 def Run(self): 1499 _RunPs(self.devices, self.args.package_name) 1500 1501 1502class _DiskUsageCommand(_Command): 1503 name = 'disk-usage' 1504 description = 'Show how much device storage is being consumed by the app.' 1505 needs_package_name = True 1506 all_devices_by_default = True 1507 1508 def Run(self): 1509 _RunDiskUsage(self.devices, self.args.package_name) 1510 1511 1512class _MemUsageCommand(_Command): 1513 name = 'mem-usage' 1514 description = 'Show memory usage of currently running APK processes.' 1515 needs_package_name = True 1516 all_devices_by_default = True 1517 1518 def _RegisterExtraArgs(self, group): 1519 group.add_argument('--query-app', action='store_true', 1520 help='Do not add --local to "dumpsys meminfo". This will output ' 1521 'additional metrics (e.g. Context count), but also cause memory ' 1522 'to be used in order to gather the metrics.') 1523 1524 def Run(self): 1525 _RunMemUsage(self.devices, self.args.package_name, 1526 query_app=self.args.query_app) 1527 1528 1529class _ShellCommand(_Command): 1530 name = 'shell' 1531 description = ('Same as "adb shell <command>", but runs as the apk\'s uid ' 1532 '(via run-as). Useful for inspecting the app\'s data ' 1533 'directory.') 1534 needs_package_name = True 1535 1536 @property 1537 def calls_exec(self): 1538 return not self.args.cmd 1539 1540 @property 1541 def supports_multiple_devices(self): 1542 return not self.args.cmd 1543 1544 def _RegisterExtraArgs(self, group): 1545 group.add_argument( 1546 'cmd', nargs=argparse.REMAINDER, help='Command to run.') 1547 1548 def Run(self): 1549 _RunShell(self.devices, self.args.package_name, self.args.cmd) 1550 1551 1552class _CompileDexCommand(_Command): 1553 name = 'compile-dex' 1554 description = ('Applicable only for Android N+. Forces .odex files to be ' 1555 'compiled with the given compilation filter. To see existing ' 1556 'filter, use "disk-usage" command.') 1557 needs_package_name = True 1558 all_devices_by_default = True 1559 1560 def _RegisterExtraArgs(self, group): 1561 group.add_argument( 1562 'compilation_filter', 1563 choices=['verify', 'quicken', 'space-profile', 'space', 1564 'speed-profile', 'speed'], 1565 help='For WebView/Monochrome, use "speed". For other apks, use ' 1566 '"speed-profile".') 1567 1568 def Run(self): 1569 _RunCompileDex(self.devices, self.args.package_name, 1570 self.args.compilation_filter) 1571 1572 1573class _PrintCertsCommand(_Command): 1574 name = 'print-certs' 1575 description = 'Print info about certificates used to sign this APK.' 1576 need_device_args = False 1577 needs_apk_helper = True 1578 1579 def _RegisterExtraArgs(self, group): 1580 group.add_argument( 1581 '--full-cert', 1582 action='store_true', 1583 help=("Print the certificate's full signature, Base64-encoded. " 1584 "Useful when configuring an Android image's " 1585 "config_webview_packages.xml.")) 1586 1587 def Run(self): 1588 keytool = os.path.join(_JAVA_HOME, 'bin', 'keytool') 1589 if self.is_bundle: 1590 # Bundles are not signed until converted to .apks. The wrapper scripts 1591 # record which key will be used to sign though. 1592 with tempfile.NamedTemporaryFile() as f: 1593 logging.warning('Bundles are not signed until turned into .apk files.') 1594 logging.warning('Showing signing info based on associated keystore.') 1595 cmd = [ 1596 keytool, '-exportcert', '-keystore', 1597 self.bundle_generation_info.keystore_path, '-storepass', 1598 self.bundle_generation_info.keystore_password, '-alias', 1599 self.bundle_generation_info.keystore_alias, '-file', f.name 1600 ] 1601 subprocess.check_output(cmd, stderr=subprocess.STDOUT) 1602 cmd = [keytool, '-printcert', '-file', f.name] 1603 logging.warning('Running: %s', ' '.join(cmd)) 1604 subprocess.check_call(cmd) 1605 if self.args.full_cert: 1606 # Redirect stderr to hide a keytool warning about using non-standard 1607 # keystore format. 1608 full_output = subprocess.check_output( 1609 cmd + ['-rfc'], stderr=subprocess.STDOUT) 1610 else: 1611 cmd = [ 1612 build_tools.GetPath('apksigner'), 'verify', '--print-certs', 1613 '--verbose', self.apk_helper.path 1614 ] 1615 logging.warning('Running: %s', ' '.join(cmd)) 1616 stdout = subprocess.check_output(cmd) 1617 print(stdout) 1618 if self.args.full_cert: 1619 if 'v1 scheme (JAR signing): true' not in stdout: 1620 raise Exception( 1621 'Cannot print full certificate because apk is not V1 signed.') 1622 1623 cmd = [keytool, '-printcert', '-jarfile', '-rfc', self.apk_helper.path] 1624 # Redirect stderr to hide a keytool warning about using non-standard 1625 # keystore format. 1626 full_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 1627 1628 if self.args.full_cert: 1629 m = re.search( 1630 r'-+BEGIN CERTIFICATE-+([\r\n0-9A-Za-z+/=]+)-+END CERTIFICATE-+', 1631 full_output, re.MULTILINE) 1632 if not m: 1633 raise Exception('Unable to parse certificate:\n{}'.format(full_output)) 1634 signature = re.sub(r'[\r\n]+', '', m.group(1)) 1635 print() 1636 print('Full Signature:') 1637 print(signature) 1638 1639 1640class _ProfileCommand(_Command): 1641 name = 'profile' 1642 description = ('Run the simpleperf sampling CPU profiler on the currently-' 1643 'running APK. If --args is used, the extra arguments will be ' 1644 'passed on to simpleperf; otherwise, the following default ' 1645 'arguments are used: -g -f 1000 -o /data/local/tmp/perf.data') 1646 needs_package_name = True 1647 needs_output_directory = True 1648 supports_multiple_devices = False 1649 accepts_args = True 1650 1651 def _RegisterExtraArgs(self, group): 1652 group.add_argument( 1653 '--profile-process', default='browser', 1654 help=('Which process to profile. This may be a process name or pid ' 1655 'such as you would get from running `%s ps`; or ' 1656 'it can be one of (browser, renderer, gpu).' % sys.argv[0])) 1657 group.add_argument( 1658 '--profile-thread', default=None, 1659 help=('(Optional) Profile only a single thread. This may be either a ' 1660 'thread ID such as you would get by running `adb shell ps -t` ' 1661 '(pre-Oreo) or `adb shell ps -e -T` (Oreo and later); or it may ' 1662 'be one of (io, compositor, main, render), in which case ' 1663 '--profile-process is also required. (Note that "render" thread ' 1664 'refers to a thread in the browser process that manages a ' 1665 'renderer; to profile the main thread of the renderer process, ' 1666 'use --profile-thread=main).')) 1667 group.add_argument('--profile-output', default='profile.pb', 1668 help='Output file for profiling data') 1669 1670 def Run(self): 1671 extra_args = shlex.split(self.args.args or '') 1672 _RunProfile(self.devices[0], self.args.package_name, 1673 self.args.output_directory, self.args.profile_output, 1674 self.args.profile_process, self.args.profile_thread, 1675 extra_args) 1676 1677 1678class _RunCommand(_InstallCommand, _LaunchCommand, _LogcatCommand): 1679 name = 'run' 1680 description = 'Install, launch, and show logcat (when targeting one device).' 1681 all_devices_by_default = False 1682 supports_multiple_devices = True 1683 1684 def _RegisterExtraArgs(self, group): 1685 _InstallCommand._RegisterExtraArgs(self, group) 1686 _LaunchCommand._RegisterExtraArgs(self, group) 1687 _LogcatCommand._RegisterExtraArgs(self, group) 1688 group.add_argument('--no-logcat', action='store_true', 1689 help='Install and launch, but do not enter logcat.') 1690 1691 def Run(self): 1692 logging.warning('Installing...') 1693 _InstallCommand.Run(self) 1694 logging.warning('Sending launch intent...') 1695 _LaunchCommand.Run(self) 1696 if len(self.devices) == 1 and not self.args.no_logcat: 1697 logging.warning('Entering logcat...') 1698 _LogcatCommand.Run(self) 1699 1700 1701class _BuildBundleApks(_Command): 1702 name = 'build-bundle-apks' 1703 description = ('Build the .apks archive from an Android app bundle, and ' 1704 'optionally copy it to a specific destination.') 1705 need_device_args = False 1706 1707 def _RegisterExtraArgs(self, group): 1708 group.add_argument( 1709 '--output-apks', required=True, help='Destination path for .apks file.') 1710 group.add_argument( 1711 '--minimal', 1712 action='store_true', 1713 help='Build .apks archive that targets the bundle\'s minSdkVersion and ' 1714 'contains only english splits. It still contains optional splits.') 1715 group.add_argument( 1716 '--sdk-version', help='The sdkVersion to build the .apks for.') 1717 group.add_argument( 1718 '--build-mode', 1719 choices=app_bundle_utils.BUILD_APKS_MODES, 1720 help='Specify which type of APKs archive to build. "default" ' 1721 'generates regular splits, "universal" generates an archive with a ' 1722 'single universal APK, "system" generates an archive with a system ' 1723 'image APK, while "system_compressed" generates a compressed system ' 1724 'APK, with an additional stub APK for the system image.') 1725 1726 def Run(self): 1727 _GenerateBundleApks( 1728 self.bundle_generation_info, 1729 output_path=self.args.output_apks, 1730 minimal=self.args.minimal, 1731 minimal_sdk_version=self.args.sdk_version, 1732 mode=self.args.build_mode) 1733 1734 1735class _ManifestCommand(_Command): 1736 name = 'dump-manifest' 1737 description = 'Dump the android manifest from this bundle, as XML, to stdout.' 1738 need_device_args = False 1739 1740 def Run(self): 1741 bundletool.RunBundleTool([ 1742 'dump', 'manifest', '--bundle', self.bundle_generation_info.bundle_path 1743 ]) 1744 1745 1746class _StackCommand(_Command): 1747 name = 'stack' 1748 description = 'Decodes an Android stack.' 1749 need_device_args = False 1750 1751 def _RegisterExtraArgs(self, group): 1752 group.add_argument( 1753 'file', 1754 nargs='?', 1755 help='File to decode. If not specified, stdin is processed.') 1756 1757 def Run(self): 1758 context = _StackScriptContext(self.args.output_directory, 1759 self.args.apk_path, 1760 self.bundle_generation_info) 1761 try: 1762 proc = context.Popen(input_file=self.args.file) 1763 if proc.wait(): 1764 raise Exception('stack script returned {}'.format(proc.returncode)) 1765 finally: 1766 context.Close() 1767 1768 1769# Shared commands for regular APKs and app bundles. 1770_COMMANDS = [ 1771 _DevicesCommand, 1772 _PackageInfoCommand, 1773 _InstallCommand, 1774 _UninstallCommand, 1775 _SetWebViewProviderCommand, 1776 _LaunchCommand, 1777 _StopCommand, 1778 _ClearDataCommand, 1779 _ArgvCommand, 1780 _GdbCommand, 1781 _LogcatCommand, 1782 _PsCommand, 1783 _DiskUsageCommand, 1784 _MemUsageCommand, 1785 _ShellCommand, 1786 _CompileDexCommand, 1787 _PrintCertsCommand, 1788 _ProfileCommand, 1789 _RunCommand, 1790 _StackCommand, 1791] 1792 1793# Commands specific to app bundles. 1794_BUNDLE_COMMANDS = [ 1795 _BuildBundleApks, 1796 _ManifestCommand, 1797] 1798 1799 1800def _ParseArgs(parser, from_wrapper_script, is_bundle): 1801 subparsers = parser.add_subparsers() 1802 command_list = _COMMANDS + (_BUNDLE_COMMANDS if is_bundle else []) 1803 commands = [clazz(from_wrapper_script, is_bundle) for clazz in command_list] 1804 1805 for command in commands: 1806 if from_wrapper_script or not command.needs_output_directory: 1807 command.RegisterArgs(subparsers) 1808 1809 # Show extended help when no command is passed. 1810 argv = sys.argv[1:] 1811 if not argv: 1812 argv = ['--help'] 1813 1814 return parser.parse_args(argv) 1815 1816 1817def _RunInternal(parser, output_directory=None, bundle_generation_info=None): 1818 colorama.init() 1819 parser.set_defaults(output_directory=output_directory) 1820 from_wrapper_script = bool(output_directory) 1821 args = _ParseArgs(parser, from_wrapper_script, bool(bundle_generation_info)) 1822 run_tests_helper.SetLogLevel(args.verbose_count) 1823 if bundle_generation_info: 1824 args.command.RegisterBundleGenerationInfo(bundle_generation_info) 1825 args.command.ProcessArgs(args) 1826 args.command.Run() 1827 # Incremental install depends on the cache being cleared when uninstalling. 1828 if args.command.name != 'uninstall': 1829 _SaveDeviceCaches(args.command.devices, output_directory) 1830 1831 1832def Run(output_directory, apk_path, additional_apk_paths, incremental_json, 1833 command_line_flags_file, target_cpu, proguard_mapping_path): 1834 """Entry point for generated wrapper scripts.""" 1835 constants.SetOutputDirectory(output_directory) 1836 devil_chromium.Initialize(output_directory=output_directory) 1837 parser = argparse.ArgumentParser() 1838 exists_or_none = lambda p: p if p and os.path.exists(p) else None 1839 1840 for path in additional_apk_paths: 1841 if not path or not os.path.exists(path): 1842 raise Exception('Invalid additional APK path "{}"'.format(path)) 1843 parser.set_defaults( 1844 command_line_flags_file=command_line_flags_file, 1845 target_cpu=target_cpu, 1846 apk_path=exists_or_none(apk_path), 1847 additional_apk_paths=additional_apk_paths, 1848 incremental_json=exists_or_none(incremental_json), 1849 proguard_mapping_path=proguard_mapping_path) 1850 _RunInternal(parser, output_directory=output_directory) 1851 1852 1853def RunForBundle(output_directory, bundle_path, bundle_apks_path, 1854 additional_apk_paths, aapt2_path, keystore_path, 1855 keystore_password, keystore_alias, package_name, 1856 command_line_flags_file, proguard_mapping_path, target_cpu, 1857 system_image_locales): 1858 """Entry point for generated app bundle wrapper scripts. 1859 1860 Args: 1861 output_dir: Chromium output directory path. 1862 bundle_path: Input bundle path. 1863 bundle_apks_path: Output bundle .apks archive path. 1864 additional_apk_paths: Additional APKs to install prior to bundle install. 1865 aapt2_path: Aapt2 tool path. 1866 keystore_path: Keystore file path. 1867 keystore_password: Keystore password. 1868 keystore_alias: Signing key name alias in keystore file. 1869 package_name: Application's package name. 1870 command_line_flags_file: Optional. Name of an on-device file that will be 1871 used to store command-line flags for this bundle. 1872 proguard_mapping_path: Input path to the Proguard mapping file, used to 1873 deobfuscate Java stack traces. 1874 target_cpu: Chromium target CPU name, used by the 'gdb' command. 1875 system_image_locales: List of Chromium locales that should be included in 1876 system image APKs. 1877 """ 1878 constants.SetOutputDirectory(output_directory) 1879 devil_chromium.Initialize(output_directory=output_directory) 1880 bundle_generation_info = BundleGenerationInfo( 1881 bundle_path=bundle_path, 1882 bundle_apks_path=bundle_apks_path, 1883 aapt2_path=aapt2_path, 1884 keystore_path=keystore_path, 1885 keystore_password=keystore_password, 1886 keystore_alias=keystore_alias, 1887 system_image_locales=system_image_locales) 1888 1889 for path in additional_apk_paths: 1890 if not path or not os.path.exists(path): 1891 raise Exception('Invalid additional APK path "{}"'.format(path)) 1892 parser = argparse.ArgumentParser() 1893 parser.set_defaults( 1894 additional_apk_paths=additional_apk_paths, 1895 package_name=package_name, 1896 command_line_flags_file=command_line_flags_file, 1897 proguard_mapping_path=proguard_mapping_path, 1898 target_cpu=target_cpu) 1899 _RunInternal(parser, output_directory=output_directory, 1900 bundle_generation_info=bundle_generation_info) 1901 1902 1903def main(): 1904 devil_chromium.Initialize() 1905 _RunInternal(argparse.ArgumentParser(), output_directory=None) 1906 1907 1908if __name__ == '__main__': 1909 main() 1910