Index: build/android/devil/android/device_utils.py |
diff --git a/build/android/devil/android/device_utils.py b/build/android/devil/android/device_utils.py |
index 96de1f88ede2a293f61d19c1d0940c4614ea2082..08458ae9561c360f9e6165e1c3d16162bb64dd4b 100644 |
--- a/build/android/devil/android/device_utils.py |
+++ b/build/android/devil/android/device_utils.py |
@@ -10,6 +10,7 @@ Eventually, this will be based on adb_wrapper. |
import collections |
import itertools |
+import json |
import logging |
import multiprocessing |
import os |
@@ -163,19 +164,20 @@ class DeviceUtils(object): |
# Property in /data/local.prop that controls Java assertions. |
JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
- def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, |
+ def __init__(self, device, enable_device_files_cache=False, |
+ default_timeout=_DEFAULT_TIMEOUT, |
default_retries=_DEFAULT_RETRIES): |
"""DeviceUtils constructor. |
Args: |
device: Either a device serial, an existing AdbWrapper instance, or an |
- an existing AndroidCommands instance. |
+ an existing AndroidCommands instance. |
+ enable_device_files_cache: For PushChangedFiles(), cache checksums of |
+ pushed files rather than recomputing them on a subsequent call. |
default_timeout: An integer containing the default number of seconds to |
- wait for an operation to complete if no explicit value |
- is provided. |
+ wait for an operation to complete if no explicit value is provided. |
default_retries: An integer containing the default number or times an |
- operation should be retried on failure if no explicit |
- value is provided. |
+ operation should be retried on failure if no explicit value is provided. |
""" |
self.adb = None |
if isinstance(device, basestring): |
@@ -187,6 +189,7 @@ class DeviceUtils(object): |
self._commands_installed = None |
self._default_timeout = default_timeout |
self._default_retries = default_retries |
+ self._enable_device_files_cache = enable_device_files_cache |
self._cache = {} |
self._client_caches = {} |
assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
@@ -1068,11 +1071,13 @@ class DeviceUtils(object): |
all_changed_files = [] |
all_stale_files = [] |
missing_dirs = [] |
+ cache_commit_funcs = [] |
for h, d in host_device_tuples: |
- changed_files, up_to_date_files, stale_files = ( |
+ changed_files, up_to_date_files, stale_files, cache_commit_func = ( |
self._GetChangedAndStaleFiles(h, d, delete_device_stale)) |
all_changed_files += changed_files |
all_stale_files += stale_files |
+ cache_commit_funcs.append(cache_commit_func) |
if (os.path.isdir(h) and changed_files and not up_to_date_files |
and not stale_files): |
missing_dirs.append(d) |
@@ -1085,6 +1090,8 @@ class DeviceUtils(object): |
if missing_dirs: |
self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) |
self._PushFilesImpl(host_device_tuples, all_changed_files) |
+ for func in cache_commit_funcs: |
+ func() |
def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): |
"""Get files to push and delete |
@@ -1102,20 +1109,38 @@ class DeviceUtils(object): |
track_stale == False |
""" |
try: |
+ # Length calculations below assume no trailing /. |
+ host_path = host_path.rstrip('/') |
+ device_path = device_path.rstrip('/') |
+ |
specific_device_paths = [device_path] |
- if not track_stale and os.path.isdir(host_path): |
+ ignore_other_files = not track_stale and os.path.isdir(host_path) |
+ if ignore_other_files: |
specific_device_paths = [] |
for root, _, filenames in os.walk(host_path): |
relative_dir = root[len(host_path) + 1:] |
specific_device_paths.extend( |
posixpath.join(device_path, relative_dir, f) for f in filenames) |
+ def device_sums_helper(): |
+ if self._enable_device_files_cache: |
+ cache_entry = self._cache['device_path_checksums'].get(device_path) |
+ if cache_entry and cache_entry[0] == ignore_other_files: |
+ return dict(cache_entry[1]) |
+ |
+ sums = md5sum.CalculateDeviceMd5Sums(specific_device_paths, self) |
+ |
+ if self._enable_device_files_cache: |
+ cache_entry = [ignore_other_files, sums] |
+ self._cache['device_path_checksums'][device_path] = cache_entry |
+ return dict(sums) |
+ |
host_checksums, device_checksums = reraiser_thread.RunAsync(( |
lambda: md5sum.CalculateHostMd5Sums([host_path]), |
- lambda: md5sum.CalculateDeviceMd5Sums(specific_device_paths, self))) |
+ device_sums_helper)) |
except EnvironmentError as e: |
logging.warning('Error calculating md5: %s', e) |
- return ([(host_path, device_path)], [], []) |
+ return ([(host_path, device_path)], [], [], lambda: 0) |
to_push = [] |
up_to_date = [] |
@@ -1137,7 +1162,16 @@ class DeviceUtils(object): |
else: |
to_push.append((host_abs_path, device_abs_path)) |
to_delete = device_checksums.keys() |
- return (to_push, up_to_date, to_delete) |
+ |
+ def cache_commit_func(): |
+ if not self._enable_device_files_cache: |
+ return |
+ new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val |
+ for path, val in host_checksums.iteritems()} |
+ cache_entry = [ignore_other_files, new_sums] |
+ self._cache['device_path_checksums'][device_path] = cache_entry |
+ |
+ return (to_push, up_to_date, to_delete, cache_commit_func) |
def _ComputeDeviceChecksumsForApks(self, package_name): |
ret = self._cache['package_apk_checksums'].get(package_name) |
@@ -1920,8 +1954,32 @@ class DeviceUtils(object): |
'package_apk_checksums': {}, |
# Map of property_name -> value |
'getprop': {}, |
+ # Map of device_path -> [ignore_other_files, map of path->checksum] |
+ 'device_path_checksums': {}, |
} |
+ def LoadCacheData(self, data): |
+ """Initializes the cache from data created using DumpCacheData.""" |
+ obj = json.loads(data) |
+ self._cache['package_apk_paths'] = obj.get('package_apk_paths', {}) |
+ package_apk_checksums = obj.get('package_apk_checksums', {}) |
+ for k, v in package_apk_checksums.iteritems(): |
+ package_apk_checksums[k] = set(v) |
+ self._cache['package_apk_checksums'] = package_apk_checksums |
+ device_path_checksums = obj.get('device_path_checksums', {}) |
+ self._cache['device_path_checksums'] = device_path_checksums |
+ |
+ def DumpCacheData(self): |
+ """Dumps the current cache state to a string.""" |
+ obj = {} |
+ obj['package_apk_paths'] = self._cache['package_apk_paths'] |
+ obj['package_apk_checksums'] = self._cache['package_apk_checksums'] |
+ # JSON can't handle sets. |
+ for k, v in obj['package_apk_checksums'].iteritems(): |
+ obj['package_apk_checksums'][k] = list(v) |
+ obj['device_path_checksums'] = self._cache['device_path_checksums'] |
+ return json.dumps(obj, separators=(',', ':')) |
+ |
@classmethod |
def parallel(cls, devices, async=False): |
"""Creates a Parallelizer to operate over the provided list of devices. |