Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1410)

Unified Diff: build/android/pylib/android_commands.py

Issue 21307002: [android] Push only updated files in PushIfNeeded when few files have changed. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: nits Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698