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