diff options
Diffstat (limited to 'catapult/devil/devil/android/sdk/adb_wrapper.py')
-rw-r--r-- | catapult/devil/devil/android/sdk/adb_wrapper.py | 268 |
1 files changed, 196 insertions, 72 deletions
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper.py b/catapult/devil/devil/android/sdk/adb_wrapper.py index 13c0f520..8cd73136 100644 --- a/catapult/devil/devil/android/sdk/adb_wrapper.py +++ b/catapult/devil/devil/android/sdk/adb_wrapper.py @@ -1,7 +1,6 @@ # Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """This module wraps Android's adb tool. This is a thin wrapper around the adb interface. Any additional complexity @@ -32,7 +31,6 @@ with devil_env.SysPath(devil_env.DEPENDENCY_MANAGER_PATH): logger = logging.getLogger(__name__) - ADB_KEYS_FILE = '/data/misc/adb/adb_keys' ADB_HOST_KEYS_DIR = os.path.join(os.path.expanduser('~'), '.android') @@ -76,8 +74,8 @@ def _FindAdb(): pass try: - return os.path.join(devil_env.config.LocalPath('android_sdk'), - 'platform-tools', 'adb') + return os.path.join( + devil_env.config.LocalPath('android_sdk'), 'platform-tools', 'adb') except dependency_manager.NoPathFoundError: pass @@ -101,8 +99,8 @@ def _ShouldRetryAdbCmd(exc): # Errors are potentially transient and should be retried, with the exception # of NoAdbError. Exceptions [e.g. generated from SIGTERM handler] should be # raised. - return (isinstance(exc, base_error.BaseError) and - not isinstance(exc, device_errors.NoAdbError)) + return (isinstance(exc, base_error.BaseError) + and not isinstance(exc, device_errors.NoAdbError)) DeviceStat = collections.namedtuple('DeviceStat', @@ -157,6 +155,7 @@ class AdbWrapper(object): pshell.RunCommand('which ls') pshell.RunCommand('echo TEST', close=True) ''' + def __init__(self, serial): """Initialization function: @@ -179,11 +178,12 @@ class AdbWrapper(object): if self._process is not None: raise RuntimeError('Persistent shell already running.') # pylint: disable=protected-access - self._process = subprocess.Popen(self._cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - shell=False, - env=AdbWrapper._ADB_ENV) + self._process = subprocess.Popen( + self._cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + shell=False, + env=AdbWrapper._ADB_ENV) def WaitForReady(self): """Wait for the shell to be ready after starting. @@ -210,6 +210,7 @@ class AdbWrapper(object): """ if close: + def run_cmd(cmd): send_cmd = '( %s ); echo $?; exit;\n' % cmd.rstrip() (output, _) = self._process.communicate(send_cmd) @@ -218,6 +219,7 @@ class AdbWrapper(object): yield x else: + def run_cmd(cmd): send_cmd = '( %s ); echo DONE:$?;\n' % cmd.rstrip() self._process.stdin.write(send_cmd) @@ -228,8 +230,10 @@ class AdbWrapper(object): break yield output_line - result = [line for line in run_cmd(command) - if not _IsExtraneousLine(line, command)] + result = [ + line for line in run_cmd(command) + if not _IsExtraneousLine(line, command) + ] return (result[:-1], int(result[-1])) @@ -262,8 +266,14 @@ class AdbWrapper(object): # pylint: disable=unused-argument @classmethod @decorators.WithTimeoutAndConditionalRetries(_ShouldRetryAdbCmd) - def _RunAdbCmd(cls, args, timeout=None, retries=None, device_serial=None, - check_error=True, cpu_affinity=None, additional_env=None): + def _RunAdbCmd(cls, + args, + timeout=None, + retries=None, + device_serial=None, + check_error=True, + cpu_affinity=None, + additional_env=None): if timeout: remaining = timeout_retry.CurrentTimeoutThreadGroup().GetRemainingTime() if remaining: @@ -276,14 +286,18 @@ class AdbWrapper(object): if additional_env: env.update(additional_env) try: - status, output = cmd_helper.GetCmdStatusAndOutputWithTimeout( - cls._BuildAdbCmd(args, device_serial, cpu_affinity=cpu_affinity), - timeout, env=env) + adb_cmd = cls._BuildAdbCmd(args, device_serial, cpu_affinity=cpu_affinity) + status, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(adb_cmd, + timeout, + env=env) except OSError as e: if e.errno in (errno.ENOENT, errno.ENOEXEC): raise device_errors.NoAdbError(msg=str(e)) else: raise + except cmd_helper.TimeoutError: + logger.error('Timeout on adb command: %r', adb_cmd) + raise # Best effort to catch errors from adb; unfortunately adb is very # inconsistent with error reporting so many command failures present @@ -292,14 +306,15 @@ class AdbWrapper(object): not_found_m = _DEVICE_NOT_FOUND_RE.match(output) device_waiting_m = _WAITING_FOR_DEVICE_RE.match(output) if (device_waiting_m is not None - or (not_found_m is not None and - not_found_m.group('serial') == device_serial)): + or (not_found_m is not None + and not_found_m.group('serial') == device_serial)): raise device_errors.DeviceUnreachableError(device_serial) else: - raise device_errors.AdbCommandFailedError( - args, output, status, device_serial) + raise device_errors.AdbCommandFailedError(args, output, status, + device_serial) return output + # pylint: enable=unused-argument def _RunDeviceAdbCmd(self, args, timeout, retries, check_error=True): @@ -316,12 +331,14 @@ class AdbWrapper(object): Returns: The output of the command. """ - return self._RunAdbCmd(args, timeout=timeout, retries=retries, - device_serial=self._device_serial, - check_error=check_error) + return self._RunAdbCmd( + args, + timeout=timeout, + retries=retries, + device_serial=self._device_serial, + check_error=check_error) - def _IterRunDeviceAdbCmd(self, args, iter_timeout, timeout, - check_error=True): + def _IterRunDeviceAdbCmd(self, args, iter_timeout, timeout, check_error=True): """Runs an adb command and returns an iterator over its output lines. Args: @@ -371,6 +388,7 @@ class AdbWrapper(object): output = [int(x) for x in output.split()] logger.info('PIDs for adb found: %r', output) return status == 0 + # pylint: enable=unused-argument @classmethod @@ -378,7 +396,9 @@ class AdbWrapper(object): cls._RunAdbCmd(['kill-server'], timeout=timeout, retries=retries) @classmethod - def StartServer(cls, keys=None, timeout=DEFAULT_TIMEOUT, + def StartServer(cls, + keys=None, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Starts the ADB server. @@ -391,8 +411,11 @@ class AdbWrapper(object): if keys: additional_env['ADB_VENDOR_KEYS'] = ':'.join(keys) # CPU affinity is used to reduce adb instability http://crbug.com/268450 - cls._RunAdbCmd(['start-server'], timeout=timeout, retries=retries, - cpu_affinity=0, additional_env=additional_env) + cls._RunAdbCmd(['start-server'], + timeout=timeout, + retries=retries, + cpu_affinity=0, + additional_env=additional_env) @classmethod def GetDevices(cls, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): @@ -401,8 +424,11 @@ class AdbWrapper(object): return cls.Devices(timeout=timeout, retries=retries) @classmethod - def Devices(cls, desired_state=_READY_STATE, long_list=False, - timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): + def Devices(cls, + desired_state=_READY_STATE, + long_list=False, + timeout=DEFAULT_TIMEOUT, + retries=DEFAULT_RETRIES): """Get the list of active attached devices. Args: @@ -415,23 +441,22 @@ class AdbWrapper(object): Yields: AdbWrapper instances. """ - lines = cls._RawDevices(long_list=long_list, timeout=timeout, - retries=retries) + lines = cls._RawDevices( + long_list=long_list, timeout=timeout, retries=retries) if long_list: - return [ - [AdbWrapper(line[0])] + line[1:] - for line in lines - if (len(line) >= 2 and (not desired_state or line[1] == desired_state)) - ] + return [[AdbWrapper(line[0])] + line[1:] for line in lines if ( + len(line) >= 2 and (not desired_state or line[1] == desired_state))] else: return [ - AdbWrapper(line[0]) - for line in lines - if (len(line) == 2 and (not desired_state or line[1] == desired_state)) + AdbWrapper(line[0]) for line in lines + if (len(line) == 2 and (not desired_state or line[1] == desired_state) + ) ] @classmethod - def _RawDevices(cls, long_list=False, timeout=DEFAULT_TIMEOUT, + def _RawDevices(cls, + long_list=False, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): cmd = ['devices'] if long_list: @@ -447,14 +472,24 @@ class AdbWrapper(object): """ return self._device_serial - def Push(self, local, remote, timeout=60 * 5, retries=DEFAULT_RETRIES): + def Push(self, + local, + remote, + sync=False, + timeout=60 * 5, + retries=DEFAULT_RETRIES): """Pushes a file from the host to the device. Args: local: Path on the host filesystem. remote: Path on the device filesystem. + sync: (optional) Whether to only push files that are newer on the host. + Not supported when using adb prior to 1.0.39. timeout: (optional) Timeout per try in seconds. retries: (optional) Number of retries to attempt. + + Raises: + AdbVersionError if sync=True with versions of adb prior to 1.0.39. """ VerifyLocalFileExists(local) @@ -495,7 +530,24 @@ class AdbWrapper(object): # without modification. pass - self._RunDeviceAdbCmd(['push', local, remote], timeout, retries) + push_cmd = ['push'] + + if sync: + push_cmd += ['--sync'] + if (du_version.LooseVersion(self.Version()) < + du_version.LooseVersion('1.0.39')): + # The --sync flag for `adb push` is a relatively recent addition. + # We're not sure exactly which release first contained it, but it + # exists at least as far back as 1.0.39. + raise device_errors.AdbVersionError( + push_cmd, + desc='--sync not supported', + actual_version=self.Version(), + min_version='1.0.39') + + push_cmd += [local, remote] + + self._RunDeviceAdbCmd(push_cmd, timeout, retries) def Pull(self, remote, local, timeout=60 * 5, retries=DEFAULT_RETRIES): """Pulls a file from the device to the host. @@ -529,7 +581,10 @@ class AdbWrapper(object): return cmd_helper.StartCmd( self._BuildAdbCmd(['shell'] + cmd, self._device_serial)) - def Shell(self, command, expect_status=0, timeout=DEFAULT_TIMEOUT, + def Shell(self, + command, + expect_status=0, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Runs a shell command on the device. @@ -582,8 +637,9 @@ class AdbWrapper(object): """ args = ['shell', command] return cmd_helper.IterCmdOutputLines( - self._BuildAdbCmd(args, self._device_serial), timeout=timeout, - env=self._ADB_ENV) + self._BuildAdbCmd(args, self._device_serial), + timeout=timeout, + env=self._ADB_ENV) def Ls(self, path, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """List the contents of a directory on the device. @@ -602,12 +658,16 @@ class AdbWrapper(object): directory in the device, or the output of "adb ls" command is less than four columns """ + def ParseLine(line, cmd): cols = line.split(None, 3) if len(cols) < 4: raise device_errors.AdbCommandFailedError( - cmd, line, "the output should be 4 columns, but is only %d columns" - % len(cols), device_serial=self._device_serial) + cmd, + line, + "the output should be 4 columns, but is only %d columns" % + len(cols), + device_serial=self._device_serial) filename = cols.pop() stat = DeviceStat(*[int(num, base=16) for num in cols]) return (filename, stat) @@ -619,12 +679,20 @@ class AdbWrapper(object): return [ParseLine(line, cmd) for line in lines] else: raise device_errors.AdbCommandFailedError( - cmd, 'path does not specify an accessible directory in the device', + cmd, + 'path does not specify an accessible directory in the device', device_serial=self._device_serial) - def Logcat(self, clear=False, dump=False, filter_specs=None, - logcat_format=None, ring_buffer=None, iter_timeout=None, - check_error=True, timeout=None, retries=DEFAULT_RETRIES): + def Logcat(self, + clear=False, + dump=False, + filter_specs=None, + logcat_format=None, + ring_buffer=None, + iter_timeout=None, + check_error=True, + timeout=None, + retries=DEFAULT_RETRIES): """Get an iterable over the logcat output. Args: @@ -674,8 +742,12 @@ class AdbWrapper(object): cmd, timeout, retries, check_error=check_error) return output.splitlines() - def Forward(self, local, remote, allow_rebind=False, - timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): + def Forward(self, + local, + remote, + allow_rebind=False, + timeout=DEFAULT_TIMEOUT, + retries=DEFAULT_RETRIES): """Forward socket connections from the local socket to the remote socket. Sockets are specified by one of: @@ -703,7 +775,9 @@ class AdbWrapper(object): if output: logger.warning('Unexpected output from "adb forward": %s', output) - def ForwardRemove(self, local, timeout=DEFAULT_TIMEOUT, + def ForwardRemove(self, + local, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Remove a forward socket connection. @@ -712,8 +786,7 @@ class AdbWrapper(object): timeout: (optional) Timeout per try in seconds. retries: (optional) Number of retries to attempt. """ - self._RunDeviceAdbCmd(['forward', '--remove', str(local)], timeout, - retries) + self._RunDeviceAdbCmd(['forward', '--remove', str(local)], timeout, retries) def ForwardList(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """List all currently forwarded socket connections. @@ -747,11 +820,19 @@ class AdbWrapper(object): Returns: A list of PIDs as strings. """ - return [a.strip() for a in - self._RunDeviceAdbCmd(['jdwp'], timeout, retries).split('\n')] - - def Install(self, apk_path, forward_lock=False, allow_downgrade=False, - reinstall=False, sd_card=False, timeout=60 * 2, + return [ + a.strip() + for a in self._RunDeviceAdbCmd(['jdwp'], timeout, retries).split('\n') + ] + + def Install(self, + apk_path, + forward_lock=False, + allow_downgrade=False, + reinstall=False, + sd_card=False, + streaming=None, + timeout=60 * 2, retries=DEFAULT_RETRIES): """Install an apk on the device. @@ -761,6 +842,10 @@ class AdbWrapper(object): allow_downgrade: (optional) If set, allows for downgrades. reinstall: (optional) If set reinstalls the app, keeping its data. sd_card: (optional) If set installs on the SD card. + streaming: (optional) If not set, use default way to install. + If True, performs streaming install. + If False, app is pushed to device and be installed from there. + Note this option is not supported prior to adb version 1.0.40 timeout: (optional) Timeout per try in seconds. retries: (optional) Number of retries to attempt. """ @@ -774,15 +859,32 @@ class AdbWrapper(object): cmd.append('-s') if allow_downgrade: cmd.append('-d') + if streaming in (True, False): + if (du_version.LooseVersion(self.Version()) < + du_version.LooseVersion('1.0.40')): + logging.warning( + 'adb: streaming options not supported prior to version 1.0.40 ' + '(current: %s)', self.Version()) + elif streaming: + cmd.append('--streaming') + else: + cmd.append('--no-streaming') cmd.append(apk_path) output = self._RunDeviceAdbCmd(cmd, timeout, retries) if 'Success' not in output: raise device_errors.AdbCommandFailedError( cmd, output, device_serial=self._device_serial) - def InstallMultiple(self, apk_paths, forward_lock=False, reinstall=False, - sd_card=False, allow_downgrade=False, partial=False, - timeout=60 * 2, retries=DEFAULT_RETRIES): + def InstallMultiple(self, + apk_paths, + forward_lock=False, + reinstall=False, + sd_card=False, + allow_downgrade=False, + partial=False, + streaming=None, + timeout=60 * 2, + retries=DEFAULT_RETRIES): """Install an apk with splits on the device. Args: @@ -792,6 +894,10 @@ class AdbWrapper(object): sd_card: (optional) If set installs on the SD card. allow_downgrade: (optional) Allow versionCode downgrade. partial: (optional) Package ID if apk_paths doesn't include all .apks. + streaming: (optional) If not set, use default way to install. + If True, performs streaming install. + If False, app is pushed to device and be installed from there. + Note this option is not supported prior to adb version 1.0.40 timeout: (optional) Timeout per try in seconds. retries: (optional) Number of retries to attempt. """ @@ -806,6 +912,16 @@ class AdbWrapper(object): cmd.append('-s') if allow_downgrade: cmd.append('-d') + if streaming in (True, False): + if (du_version.LooseVersion(self.Version()) < + du_version.LooseVersion('1.0.40')): + logging.warning( + 'adb: streaming options not supported prior to version 1.0.40 ' + '(current: %s)', self.Version()) + elif streaming: + cmd.append('--streaming') + else: + cmd.append('--no-streaming') if partial: cmd.extend(('-p', partial)) cmd.extend(apk_paths) @@ -814,7 +930,10 @@ class AdbWrapper(object): raise device_errors.AdbCommandFailedError( cmd, output, device_serial=self._device_serial) - def Uninstall(self, package, keep_data=False, timeout=DEFAULT_TIMEOUT, + def Uninstall(self, + package, + keep_data=False, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Remove the app |package| from the device. @@ -833,8 +952,14 @@ class AdbWrapper(object): raise device_errors.AdbCommandFailedError( cmd, output, device_serial=self._device_serial) - def Backup(self, path, packages=None, apk=False, shared=False, - nosystem=True, include_all=False, timeout=DEFAULT_TIMEOUT, + def Backup(self, + path, + packages=None, + apk=False, + shared=False, + nosystem=True, + include_all=False, + timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Write an archive of the device's data to |path|. @@ -949,8 +1074,7 @@ class AdbWrapper(object): raise device_errors.AdbCommandFailedError( ['root'], output, device_serial=self._device_serial) - def Emu(self, cmd, timeout=DEFAULT_TIMEOUT, - retries=DEFAULT_RETRIES): + def Emu(self, cmd, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES): """Runs an emulator console command. See http://developer.android.com/tools/devices/emulator.html#console |