| Index: build/android/pylib/android_commands.py
|
| diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py
|
| index 4cf070d0711a9626edf1ba6eae35efe0f7bb7e40..cb72fffba55ae46025c824bdd0e060fa52e658ed 100644
|
| --- a/build/android/pylib/android_commands.py
|
| +++ b/build/android/pylib/android_commands.py
|
| @@ -184,14 +184,15 @@ def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
|
| return files
|
|
|
|
|
| -def _ComputeFileListHash(md5sum_output):
|
| +def _ParseMd5SumOutput(md5sum_output):
|
| """Returns a list of tuples from the provided md5sum output.
|
|
|
| Args:
|
| md5sum_output: output directly from md5sum binary.
|
|
|
| Returns:
|
| - List of namedtuples (hash, path).
|
| + List of namedtuples with attributes |hash| and |path|, where |path| is the
|
| + absolute path to the file with an Md5Sum of |hash|.
|
| """
|
| HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
|
| split_lines = [line.split(' ') for line in md5sum_output]
|
| @@ -418,7 +419,8 @@ class AndroidCommands(object):
|
| # Check if package is already installed and up to date.
|
| if package_name:
|
| installed_apk_path = self.GetApplicationPath(package_name)
|
| - if installed_apk_path and self.CheckMd5Sum(apk_path, installed_apk_path):
|
| + if (installed_apk_path and
|
| + not self.GetFilesChanged(apk_path, installed_apk_path)):
|
| logging.info('Skipped install: identical %s APK already installed' %
|
| package_name)
|
| return
|
| @@ -738,15 +740,16 @@ class AndroidCommands(object):
|
| """
|
| self.RunShellCommand('input keyevent %d' % keycode)
|
|
|
| - def CheckMd5Sum(self, local_path, device_path):
|
| - """Compares the md5sum of a local path against a device path.
|
| + def _RunMd5Sum(self, host_path, device_path):
|
| + """Gets the md5sum of a host path and device path.
|
|
|
| Args:
|
| - local_path: Path (file or directory) on the host.
|
| + host_path: Path (file or directory) on the host.
|
| device_path: Path on the device.
|
|
|
| Returns:
|
| - True if the md5sums match.
|
| + A tuple containing lists of the host and device md5sum results as
|
| + created by _ParseMd5SumOutput().
|
| """
|
| if not self._md5sum_build_dir:
|
| default_build_type = os.environ.get('BUILD_TYPE', 'Debug')
|
| @@ -763,73 +766,112 @@ class AndroidCommands(object):
|
|
|
| cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' +
|
| MD5SUM_DEVICE_PATH + ' ' + device_path)
|
| - device_hash_tuples = _ComputeFileListHash(
|
| + device_hash_tuples = _ParseMd5SumOutput(
|
| self.RunShellCommand(cmd, timeout_time=2 * 60))
|
| - assert os.path.exists(local_path), 'Local path not found %s' % local_path
|
| + assert os.path.exists(host_path), 'Local path not found %s' % host_path
|
| md5sum_output = cmd_helper.GetCmdOutput(
|
| - ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path])
|
| - host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines())
|
| + ['%s/md5sum_bin_host' % self._md5sum_build_dir, host_path])
|
| + host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
|
| + return (host_hash_tuples, device_hash_tuples)
|
| +
|
| + def GetFilesChanged(self, host_path, device_path):
|
| + """Compares the md5sum of a host path against a device path.
|
| +
|
| + Note: Ignores extra files on the device.
|
| +
|
| + Args:
|
| + host_path: Path (file or directory) on the host.
|
| + device_path: Path on the device.
|
| +
|
| + Returns:
|
| + A list of tuples of the form (host_path, device_path) for files whose
|
| + md5sums do not match.
|
| + """
|
| + host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
|
| + host_path, device_path)
|
|
|
| # Ignore extra files on the device.
|
| if len(device_hash_tuples) > len(host_hash_tuples):
|
| host_files = [os.path.relpath(os.path.normpath(p.path),
|
| - os.path.normpath(local_path)) for p in host_hash_tuples]
|
| + os.path.normpath(host_path)) for p in host_hash_tuples]
|
|
|
| - def _host_has(fname):
|
| + def HostHas(fname):
|
| return any(path in fname for path in host_files)
|
|
|
| - hashes_on_device = [h.hash for h in device_hash_tuples if
|
| - _host_has(h.path)]
|
| - else:
|
| - hashes_on_device = [h.hash for h in device_hash_tuples]
|
| + device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)]
|
| +
|
| + # Constructs the target device path from a given host path. Don't use when
|
| + # only a single file is given as the base name given in device_path may
|
| + # differ from that in host_path.
|
| + def HostToDevicePath(host_file_path):
|
| + return os.path.join(os.path.dirname(device_path), os.path.relpath(
|
| + host_file_path, os.path.dirname(os.path.normpath(host_path))))
|
|
|
| - # Compare md5sums between host and device files.
|
| - hashes_on_host = [h.hash for h in host_hash_tuples]
|
| - hashes_on_device.sort()
|
| - hashes_on_host.sort()
|
| - return hashes_on_device == hashes_on_host
|
| + device_hashes = [h.hash for h in device_hash_tuples]
|
| + return [(t.path, HostToDevicePath(t.path) if os.path.isdir(host_path) else
|
| + device_path)
|
| + for t in host_hash_tuples if t.hash not in device_hashes]
|
|
|
| - def PushIfNeeded(self, local_path, device_path):
|
| - """Pushes |local_path| to |device_path|.
|
| + def PushIfNeeded(self, host_path, device_path):
|
| + """Pushes |host_path| to |device_path|.
|
|
|
| Works for files and directories. This method skips copying any paths in
|
| |test_data_paths| that already exist on the device with the same hash.
|
|
|
| All pushed files can be removed by calling RemovePushedFiles().
|
| """
|
| - assert os.path.exists(local_path), 'Local path not found %s' % local_path
|
| - size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0])
|
| + MAX_INDIVIDUAL_PUSHES = 50
|
| + assert os.path.exists(host_path), 'Local path not found %s' % host_path
|
| +
|
| + def GetHostSize(path):
|
| + return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0])
|
| +
|
| + size = GetHostSize(host_path)
|
| self._pushed_files.append(device_path)
|
| self._potential_push_size += size
|
|
|
| - if self.CheckMd5Sum(local_path, device_path):
|
| + changed_files = self.GetFilesChanged(host_path, device_path)
|
| + if not changed_files:
|
| return
|
|
|
| - self._actual_push_size += size
|
| - # They don't match, so remove everything first and then create it.
|
| - if os.path.isdir(local_path):
|
| - self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60)
|
| - self.RunShellCommand('mkdir -p %s' % device_path)
|
| -
|
| - # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of
|
| - # 60 seconds which isn't sufficient for a lot of users of this method.
|
| - push_command = 'push %s %s' % (local_path, device_path)
|
| - self._LogShell(push_command)
|
| -
|
| - # Retry push with increasing backoff if the device is busy.
|
| - retry = 0
|
| - while True:
|
| - output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
|
| - if _HasAdbPushSucceeded(output):
|
| - return
|
| - if 'resource busy' in output and retry < 3:
|
| - retry += 1
|
| - wait_time = 5 * retry
|
| - logging.error('Push failed, retrying in %d seconds: %s' %
|
| - (wait_time, output))
|
| - time.sleep(wait_time)
|
| - else:
|
| - raise Exception('Push failed: %s' % output)
|
| + def Push(host, device):
|
| + # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
|
| + # of 60 seconds which isn't sufficient for a lot of users of this method.
|
| + push_command = 'push %s %s' % (host, device)
|
| + self._LogShell(push_command)
|
| +
|
| + # Retry push with increasing backoff if the device is busy.
|
| + retry = 0
|
| + while True:
|
| + output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
|
| + if _HasAdbPushSucceeded(output):
|
| + return
|
| + if retry < 3:
|
| + retry += 1
|
| + wait_time = 5 * retry
|
| + logging.error('Push failed, retrying in %d seconds: %s' %
|
| + (wait_time, output))
|
| + time.sleep(wait_time)
|
| + else:
|
| + raise Exception('Push failed: %s' % output)
|
| +
|
| + diff_size = 0
|
| + if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
|
| + diff_size = sum(GetHostSize(f[0]) for f in changed_files)
|
| +
|
| + # TODO(craigdh): Replace this educated guess with a heuristic that
|
| + # approximates the push time for each method.
|
| + if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
|
| + # We're pushing everything, remove everything first and then create it.
|
| + self._actual_push_size += size
|
| + if os.path.isdir(host_path):
|
| + self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60)
|
| + self.RunShellCommand('mkdir -p %s' % device_path)
|
| + Push(host_path, device_path)
|
| + else:
|
| + for f in changed_files:
|
| + Push(f[0], f[1])
|
| + self._actual_push_size += diff_size
|
|
|
| def GetPushSizeInfo(self):
|
| """Get total size of pushes to the device done via PushIfNeeded()
|
|
|