From eb239c65d028c9e7fcc3d0db3754e9be242dc2eb Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Tue, 6 Dec 2016 11:01:09 +0000 Subject: [PATCH 1/7] LinuxDevice: Added supported eabi property. Added property to linux device to return list of supported abis to be consistent with android devices. Currently only returns a list containing the primary abi. --- wlauto/common/linux/device.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wlauto/common/linux/device.py b/wlauto/common/linux/device.py index db76f2ca..c6ce9e4f 100644 --- a/wlauto/common/linux/device.py +++ b/wlauto/common/linux/device.py @@ -136,6 +136,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method self._abi = val return self._abi + @property + def supported_abi(self): + return [self.abi] + @property def online_cpus(self): val = self.get_sysfile_value('/sys/devices/system/cpu/online') From 693d0544a33c8f4eb9c450c7de89430072fff4fa Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Wed, 7 Dec 2016 13:26:51 +0000 Subject: [PATCH 2/7] ABI_MAP: Added armeabi-v7a as armeabi --- wlauto/utils/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wlauto/utils/misc.py b/wlauto/utils/misc.py index 51829ebe..98e79e05 100644 --- a/wlauto/utils/misc.py +++ b/wlauto/utils/misc.py @@ -47,7 +47,7 @@ from dateutil import tz # ABI --> architectures list ABI_MAP = { - 'armeabi': ['armeabi', 'armv7', 'armv7l', 'armv7el', 'armv7lh'], + 'armeabi': ['armeabi', 'armv7', 'armv7l', 'armv7el', 'armv7lh', 'armeabi-v7a'], 'arm64': ['arm64', 'armv8', 'arm64-v8a', 'aarch64'], } From 4352e028067d3275d96cd5cdad074c3a68c805cc Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Wed, 7 Dec 2016 16:31:47 +0000 Subject: [PATCH 3/7] APK Info: Added property to extract native code from an apk Looks for any native code present in an apk and stores the mapped abi(s) for use when selecting appropriate apks. --- wlauto/utils/android.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/wlauto/utils/android.py b/wlauto/utils/android.py index 0ce7d328..affb8ade 100644 --- a/wlauto/utils/android.py +++ b/wlauto/utils/android.py @@ -28,7 +28,7 @@ import re from wlauto.exceptions import DeviceError, ConfigError, HostError, WAError from wlauto.utils.misc import (check_output, escape_single_quotes, escape_double_quotes, get_null, - CalledProcessErrorWithStderr) + CalledProcessErrorWithStderr, ABI_MAP) MAX_TRIES = 5 @@ -164,6 +164,7 @@ class ApkInfo(object): self.label = None self.version_name = None self.version_code = None + self.native_code = [] self.parse(path) def parse(self, apk_path): @@ -183,6 +184,19 @@ class ApkInfo(object): elif line.startswith('launchable-activity:'): match = self.name_regex.search(line) self.activity = match.group('name') + elif line.startswith('native-code'): + apk_abis = [entry.strip() for entry in line.split(':')[1].split("'") if entry.strip()] + mapped_abis = [] + for apk_abi in apk_abis: + found = False + for abi, architectures in ABI_MAP.iteritems(): + if apk_abi in architectures: + mapped_abis.append(abi) + found = True + break + if not found: + mapped_abis.append(apk_abi) + self.native_code = mapped_abis else: pass # not interested From f467f6f99159bd90405fad032761d87a9e9e4f8d Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Thu, 8 Dec 2016 15:55:18 +0000 Subject: [PATCH 4/7] AndroidDevice: Added method to retrive primary abi of installed package Tries to retireve the primary abi of a currently installed package on device. --- wlauto/common/android/device.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/wlauto/common/android/device.py b/wlauto/common/android/device.py index 31362173..8feb184d 100644 --- a/wlauto/common/android/device.py +++ b/wlauto/common/android/device.py @@ -262,6 +262,24 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223 return line.split('=', 1)[1] return None + def get_installed_package_abi(self, package): + """ + Returns the primary abi of the specified package if it is installed + on the device, or ``None`` otherwise. + """ + output = self.execute('dumpsys package {}'.format(package)) + val = None + for line in convert_new_lines(output).split('\n'): + if 'primaryCpuAbi' in line: + val = line.split('=', 1)[1] + break + if val == 'null': + return None + for abi, architectures in ABI_MAP.iteritems(): + if val in architectures: + return abi + return val + def list_packages(self): """ List packages installed on the device. From a8a8d21de6b6b9767b4bd70270008a1852d6ed94 Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Wed, 7 Dec 2016 16:34:15 +0000 Subject: [PATCH 5/7] AndroidWorkload: Modified workload to properly check for an apks abi Previously when retrieving apks only it's name would be used to choose an apk. Now the native code reported by the apk is used to determine the correct version to run for the specific device. It tries to match the primary abi of device with native code before falling back to using a compatible apk. When using the check_abi parameter it no longer relies on naming convention and only allows apks with native code supporting a devices primary abi to be used. Updated the relevant documentation. --- doc/source/apk_workloads.rst | 6 +++--- wlauto/common/android/device.py | 21 ++++++++++++++++++--- wlauto/common/android/workload.py | 27 +++++++++++++++++++++++++-- wlauto/resource_getters/standard.py | 20 ++++---------------- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/doc/source/apk_workloads.rst b/doc/source/apk_workloads.rst index 5f5c8fab..9872bc64 100644 --- a/doc/source/apk_workloads.rst +++ b/doc/source/apk_workloads.rst @@ -18,10 +18,10 @@ All ApkWorkloads have parameters that affect the way in which APK files are reso .. confval:: check_abi - If this setting is enabled WA's resource resolvers will look for the devices ABI within the file name - e.g. ``calculator_arm65.apk``. By default this setting is disabled since most apks will work across all + If this setting is enabled WA's resource resolvers will look for the devices ABI with any native + code present in the apk. By default this setting is disabled since most apks will work across all devices. You may wish to enable this feature when working with devices that support multiple ABI's (like - 64-bit devices that can run 32-bit APK files) and are specifically trying to test one or the other. + 64-bit devices that can run 32-bit APK files) and are specifically trying to test one or the other. .. confval:: force_install diff --git a/wlauto/common/android/device.py b/wlauto/common/android/device.py index 8feb184d..ddedf7e6 100644 --- a/wlauto/common/android/device.py +++ b/wlauto/common/android/device.py @@ -30,7 +30,7 @@ from wlauto.common.resources import Executable from wlauto.core.resource import NO_ONE from wlauto.common.linux.device import BaseLinuxDevice, PsEntry from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError -from wlauto.utils.misc import convert_new_lines +from wlauto.utils.misc import convert_new_lines, ABI_MAP from wlauto.utils.types import boolean, regex from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices, adb_command, AndroidProperties, ANDROID_VERSION_MAP) @@ -108,7 +108,11 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223 @property def abi(self): - return self.getprop()['ro.product.cpu.abi'].split('-')[0] + val = self.getprop()['ro.product.cpu.abi'].split('-')[0] + for abi, architectures in ABI_MAP.iteritems(): + if val in architectures: + return abi + return val @property def supported_eabi(self): @@ -120,7 +124,18 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223 for eabi in props['ro.product.cpu.abilist'].split(','): if eabi not in result: result.append(eabi) - return result + + mapped_result = [] + for supported_eabi in result: + for abi, architectures in ABI_MAP.iteritems(): + found = False + if supported_eabi in architectures and abi not in mapped_result: + mapped_result.append(abi) + found = True + break + if not found and supported_eabi not in mapped_result: + mapped_result.append(supported_eabi) + return mapped_result def __init__(self, **kwargs): super(AndroidDevice, self).__init__(**kwargs) diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index 1d48796e..3eb835cb 100755 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -200,8 +200,9 @@ class ApkWorkload(Workload): self.apk_version = None self.logcat_log = None self.exact_apk_version = None + self.check_abi = kwargs.get('check_abi') - def setup(self, context): + def setup(self, context): # pylint: disable=too-many-branches Workload.setup(self, context) # Get target version @@ -213,9 +214,25 @@ class ApkWorkload(Workload): # Get host version self.apk_file = context.resolver.get(ApkFile(self, self.device.abi), version=getattr(self, 'version', None), - check_abi=getattr(self, 'check_abi', False), variant_name=getattr(self, 'variant_name', None), strict=False) + + # Get target abi + target_abi = self.device.get_installed_package_abi(self.package) + if target_abi: + self.logger.debug("Found apk with primary abi '{}' on target device".format(target_abi)) + + # Get host version, primary abi is first, and then try to find supported. + for abi in self.device.supported_eabi: + self.apk_file = context.resolver.get(ApkFile(self, abi), + version=getattr(self, 'version', None), + variant_name=getattr(self, 'variant_name', None), + strict=False) + + # Stop if apk found, or if check_abi is set only look for primary abi. + if self.apk_file or self.check_abi: + break + host_version = None if self.apk_file is not None: host_version = ApkInfo(self.apk_file).version_name @@ -233,6 +250,12 @@ class ApkWorkload(Workload): msg = "APK version '{}' not found on the host '{}' or target '{}'" raise ResourceError(msg.format(self.exact_apk_version, host_version, target_version)) + # Error if check_abi and suitable apk not found on host and incorrect version on device + if self.check_abi and host_version is None: + if target_abi != self.device.abi: + msg = "APK abi '{}' not found on the host and target is '{}'" + raise ResourceError(msg.format(self.device.abi, target_abi)) + # Ensure the apk is setup on the device if self.force_install: self.force_install_apk(context, host_version) diff --git a/wlauto/resource_getters/standard.py b/wlauto/resource_getters/standard.py index eb0c7101..b5c392d5 100644 --- a/wlauto/resource_getters/standard.py +++ b/wlauto/resource_getters/standard.py @@ -112,19 +112,13 @@ class PackageApkGetter(PackageFileGetter): extension = 'apk' description = """ - Uses the same dependency resolution mechanism as ``PackageFileGetter`` with one addition. - If an ABI is specified in the resource request, then the getter will try to locate the file in - the ABI-specific folder in the form ``/apk//``. Where ``root`` is the base - resource location e.g. ``~/.workload_automation/dependencies/`` and ```` - is the ABI for which the APK has been compiled, as returned by ``resource.platform``. + Uses the same dependency resolution mechanism as ``PackageFileGetter``. """ def get(self, resource, **kwargs): resource_dir = os.path.dirname(sys.modules[resource.owner.__module__].__file__) version = kwargs.get('version') variant = kwargs.get('variant_name') - if kwargs.get('check_abi', False): - resource_dir = os.path.join(resource_dir, self.extension, resource.platform) return get_from_location_by_extension(resource, resource_dir, self.extension, version, variant=variant) @@ -146,19 +140,13 @@ class EnvironmentApkGetter(EnvironmentFileGetter): extension = 'apk' description = """ - Uses the same dependency resolution mechanism as ``EnvironmentFileGetter`` with one addition. - If an ABI is specified in the resource request, then the getter will try to locate the file in - the ABI-specific folder in the form ``/apk//``. Where ``root`` is the base - resource location e.g. ``~/.workload_automation/dependencies/`` and ```` - is the ABI for which the APK has been compiled, as returned by ``resource.platform``. + Uses the same dependency resolution mechanism as ``EnvironmentFileGetter``. """ def get(self, resource, **kwargs): resource_dir = resource.owner.dependencies_directory version = kwargs.get('version') variant = kwargs.get('variant_name') - if kwargs.get('check_abi', False): - resource_dir = os.path.join(resource_dir, self.extension, resource.platform) return get_from_location_by_extension(resource, resource_dir, self.extension, version, variant=variant) @@ -478,8 +466,6 @@ class RemoteFilerGetter(ResourceGetter): if resource.owner: remote_path = os.path.join(self.remote_path, resource.owner.name) local_path = os.path.join(settings.environment_root, '__filer', resource.owner.dependencies_directory) - if resource.name == 'apk' and kwargs.get('check_abi', False): - local_path = os.path.join(local_path, 'apk', resource.platform) message = 'resource={}, version={}, remote_path={}, local_path={}' self.logger.debug(message.format(resource, version, remote_path, local_path)) return self.try_get_resource(resource, version, remote_path, local_path) @@ -574,6 +560,8 @@ def get_from_list_by_extension(resource, filelist, extension, version=None, vari filelist = [ff for ff in filelist if version.lower() in ApkInfo(ff).version_name.lower()] else: filelist = [ff for ff in filelist if version.lower() in os.path.basename(ff).lower()] + if extension == 'apk': + filelist = [ff for ff in filelist if not ApkInfo(ff).native_code or resource.platform in ApkInfo(ff).native_code] if len(filelist) == 1: return filelist[0] elif not filelist: From 0dfbbae7b6c3ea4ed687d3c134f976cb2f70a4a1 Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Thu, 8 Dec 2016 16:19:05 +0000 Subject: [PATCH 6/7] Renamed 'check_abi' parameter to 'exact_abi' --- doc/source/apk_workloads.rst | 4 ++-- wlauto/common/android/workload.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/source/apk_workloads.rst b/doc/source/apk_workloads.rst index 9872bc64..da3b28b6 100644 --- a/doc/source/apk_workloads.rst +++ b/doc/source/apk_workloads.rst @@ -13,10 +13,10 @@ to chnage the location of this folder. The APK files need to be put into the cor for the workload they belong to. The name of the file can be anything but as explained below may need to contain certain peices of information. -All ApkWorkloads have parameters that affect the way in which APK files are resolved, ``check_abi``, +All ApkWorkloads have parameters that affect the way in which APK files are resolved, ``exact_abi``, ``force_install`` and ``check_apk``. Their exact behaviours are outlined below. -.. confval:: check_abi +.. confval:: exact_abi If this setting is enabled WA's resource resolvers will look for the devices ABI with any native code present in the apk. By default this setting is disabled since most apks will work across all diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index 3eb835cb..e581f200 100755 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -186,7 +186,7 @@ class ApkWorkload(Workload): '''), Parameter('uninstall_apk', kind=boolean, default=False, description='If ``True``, will uninstall workload\'s APK as part of teardown.'), - Parameter('check_abi', kind=bool, default=False, + Parameter('exact_abi', kind=bool, default=False, description=''' If ``True``, workload will check that the APK matches the target device ABI, otherwise any APK found will be used. @@ -200,7 +200,7 @@ class ApkWorkload(Workload): self.apk_version = None self.logcat_log = None self.exact_apk_version = None - self.check_abi = kwargs.get('check_abi') + self.exact_abi = kwargs.get('exact_abi') def setup(self, context): # pylint: disable=too-many-branches Workload.setup(self, context) @@ -229,8 +229,8 @@ class ApkWorkload(Workload): variant_name=getattr(self, 'variant_name', None), strict=False) - # Stop if apk found, or if check_abi is set only look for primary abi. - if self.apk_file or self.check_abi: + # Stop if apk found, or if exact_abi is set only look for primary abi. + if self.apk_file or self.exact_abi: break host_version = None @@ -250,8 +250,8 @@ class ApkWorkload(Workload): msg = "APK version '{}' not found on the host '{}' or target '{}'" raise ResourceError(msg.format(self.exact_apk_version, host_version, target_version)) - # Error if check_abi and suitable apk not found on host and incorrect version on device - if self.check_abi and host_version is None: + # Error if exact_abi and suitable apk not found on host and incorrect version on device + if self.exact_abi and host_version is None: if target_abi != self.device.abi: msg = "APK abi '{}' not found on the host and target is '{}'" raise ResourceError(msg.format(self.device.abi, target_abi)) From 1477a89ee431f9fd14cc75b05b57603561284d13 Mon Sep 17 00:00:00 2001 From: Marc Bonnici Date: Fri, 9 Dec 2016 13:49:39 +0000 Subject: [PATCH 7/7] AndroidDevice: Renamed 'supported_eabis' property to 'supported_abis' Renamed android device property from 'supported_eabis' to 'supported_abis' to be consistent with linux device. Updated dex2oat workload to use new property name. --- wlauto/common/android/device.py | 16 ++++++++-------- wlauto/common/android/workload.py | 2 +- wlauto/workloads/dex2oat/__init__.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wlauto/common/android/device.py b/wlauto/common/android/device.py index ddedf7e6..351f7d8e 100644 --- a/wlauto/common/android/device.py +++ b/wlauto/common/android/device.py @@ -115,26 +115,26 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223 return val @property - def supported_eabi(self): + def supported_abi(self): props = self.getprop() result = [props['ro.product.cpu.abi']] if 'ro.product.cpu.abi2' in props: result.append(props['ro.product.cpu.abi2']) if 'ro.product.cpu.abilist' in props: - for eabi in props['ro.product.cpu.abilist'].split(','): - if eabi not in result: - result.append(eabi) + for abi in props['ro.product.cpu.abilist'].split(','): + if abi not in result: + result.append(abi) mapped_result = [] - for supported_eabi in result: + for supported_abi in result: for abi, architectures in ABI_MAP.iteritems(): found = False - if supported_eabi in architectures and abi not in mapped_result: + if supported_abi in architectures and abi not in mapped_result: mapped_result.append(abi) found = True break - if not found and supported_eabi not in mapped_result: - mapped_result.append(supported_eabi) + if not found and supported_abi not in mapped_result: + mapped_result.append(supported_abi) return mapped_result def __init__(self, **kwargs): diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index e581f200..1a4673f4 100755 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -223,7 +223,7 @@ class ApkWorkload(Workload): self.logger.debug("Found apk with primary abi '{}' on target device".format(target_abi)) # Get host version, primary abi is first, and then try to find supported. - for abi in self.device.supported_eabi: + for abi in self.device.supported_abi: self.apk_file = context.resolver.get(ApkFile(self, abi), version=getattr(self, 'version', None), variant_name=getattr(self, 'variant_name', None), diff --git a/wlauto/workloads/dex2oat/__init__.py b/wlauto/workloads/dex2oat/__init__.py index 40621ce6..f93af6f4 100644 --- a/wlauto/workloads/dex2oat/__init__.py +++ b/wlauto/workloads/dex2oat/__init__.py @@ -60,8 +60,8 @@ class Dex2oatBenchmark(Workload): def setup(self, context): if self.device.getprop('persist.sys.dalvik.vm.lib.2') != 'libart.so': raise WorkloadError('Android system must be using ART (rather than Dalvik) in order for dex2oat to work.') - supported = [eabi == 'armeabi' and 'arm' or eabi.split('-')[0] - for eabi in self.device.supported_eabi] + supported = [abi == 'armeabi' and 'arm' or abi.split('-')[0] + for abi in self.device.supported_abi] if self.instruction_set not in supported: message = 'Instruction set "{}" is not supported by the device; (supported: {})' raise WorkloadError(message.format(self.instruction_set, supported))