1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-06-19 06:46:12 +01:00

Merge pull request from marcbonnici/exact_abi

Modified how apks containing native code are matched to a target devices supported abi(s).
This commit is contained in:
setrofim
2016-12-14 10:32:56 +00:00
committed by GitHub
8 changed files with 97 additions and 35 deletions
doc/source
wlauto
common
resource_getters
utils
workloads

@ -13,15 +13,15 @@ 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 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. 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. ``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 within the file name If this setting is enabled WA's resource resolvers will look for the devices ABI with any native
e.g. ``calculator_arm65.apk``. By default this setting is disabled since most apks will work across all 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 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 .. confval:: force_install

@ -30,7 +30,7 @@ from wlauto.common.resources import Executable
from wlauto.core.resource import NO_ONE from wlauto.core.resource import NO_ONE
from wlauto.common.linux.device import BaseLinuxDevice, PsEntry from wlauto.common.linux.device import BaseLinuxDevice, PsEntry
from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError 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.types import boolean, regex
from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices, from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices,
adb_command, AndroidProperties, ANDROID_VERSION_MAP) adb_command, AndroidProperties, ANDROID_VERSION_MAP)
@ -108,19 +108,34 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
@property @property
def abi(self): 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 @property
def supported_eabi(self): def supported_abi(self):
props = self.getprop() props = self.getprop()
result = [props['ro.product.cpu.abi']] result = [props['ro.product.cpu.abi']]
if 'ro.product.cpu.abi2' in props: if 'ro.product.cpu.abi2' in props:
result.append(props['ro.product.cpu.abi2']) result.append(props['ro.product.cpu.abi2'])
if 'ro.product.cpu.abilist' in props: if 'ro.product.cpu.abilist' in props:
for eabi in props['ro.product.cpu.abilist'].split(','): for abi in props['ro.product.cpu.abilist'].split(','):
if eabi not in result: if abi not in result:
result.append(eabi) result.append(abi)
return result
mapped_result = []
for supported_abi in result:
for abi, architectures in ABI_MAP.iteritems():
found = False
if supported_abi in architectures and abi not in mapped_result:
mapped_result.append(abi)
found = True
break
if not found and supported_abi not in mapped_result:
mapped_result.append(supported_abi)
return mapped_result
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(AndroidDevice, self).__init__(**kwargs) super(AndroidDevice, self).__init__(**kwargs)
@ -262,6 +277,24 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
return line.split('=', 1)[1] return line.split('=', 1)[1]
return None 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): def list_packages(self):
""" """
List packages installed on the device. List packages installed on the device.

@ -186,7 +186,7 @@ class ApkWorkload(Workload):
'''), '''),
Parameter('uninstall_apk', kind=boolean, default=False, Parameter('uninstall_apk', kind=boolean, default=False,
description='If ``True``, will uninstall workload\'s APK as part of teardown.'), 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=''' description='''
If ``True``, workload will check that the APK matches the target If ``True``, workload will check that the APK matches the target
device ABI, otherwise any APK found will be used. device ABI, otherwise any APK found will be used.
@ -200,8 +200,9 @@ class ApkWorkload(Workload):
self.apk_version = None self.apk_version = None
self.logcat_log = None self.logcat_log = None
self.exact_apk_version = None self.exact_apk_version = None
self.exact_abi = kwargs.get('exact_abi')
def setup(self, context): def setup(self, context): # pylint: disable=too-many-branches
Workload.setup(self, context) Workload.setup(self, context)
# Get target version # Get target version
@ -213,9 +214,25 @@ class ApkWorkload(Workload):
# Get host version # Get host version
self.apk_file = context.resolver.get(ApkFile(self, self.device.abi), self.apk_file = context.resolver.get(ApkFile(self, self.device.abi),
version=getattr(self, 'version', None), version=getattr(self, 'version', None),
check_abi=getattr(self, 'check_abi', False),
variant_name=getattr(self, 'variant_name', None), variant_name=getattr(self, 'variant_name', None),
strict=False) 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_abi:
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 exact_abi is set only look for primary abi.
if self.apk_file or self.exact_abi:
break
host_version = None host_version = None
if self.apk_file is not None: if self.apk_file is not None:
host_version = ApkInfo(self.apk_file).version_name 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 '{}'" msg = "APK version '{}' not found on the host '{}' or target '{}'"
raise ResourceError(msg.format(self.exact_apk_version, host_version, target_version)) raise ResourceError(msg.format(self.exact_apk_version, host_version, target_version))
# 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))
# Ensure the apk is setup on the device # Ensure the apk is setup on the device
if self.force_install: if self.force_install:
self.force_install_apk(context, host_version) self.force_install_apk(context, host_version)

@ -136,6 +136,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
self._abi = val self._abi = val
return self._abi return self._abi
@property
def supported_abi(self):
return [self.abi]
@property @property
def online_cpus(self): def online_cpus(self):
val = self.get_sysfile_value('/sys/devices/system/cpu/online') val = self.get_sysfile_value('/sys/devices/system/cpu/online')

@ -112,19 +112,13 @@ class PackageApkGetter(PackageFileGetter):
extension = 'apk' extension = 'apk'
description = """ description = """
Uses the same dependency resolution mechanism as ``PackageFileGetter`` with one addition. Uses the same dependency resolution mechanism as ``PackageFileGetter``.
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 ``<root>/apk/<abi>/<apk_name>``. Where ``root`` is the base
resource location e.g. ``~/.workload_automation/dependencies/<extension_name>`` and ``<abi>``
is the ABI for which the APK has been compiled, as returned by ``resource.platform``.
""" """
def get(self, resource, **kwargs): def get(self, resource, **kwargs):
resource_dir = os.path.dirname(sys.modules[resource.owner.__module__].__file__) resource_dir = os.path.dirname(sys.modules[resource.owner.__module__].__file__)
version = kwargs.get('version') version = kwargs.get('version')
variant = kwargs.get('variant_name') 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) return get_from_location_by_extension(resource, resource_dir, self.extension, version, variant=variant)
@ -146,19 +140,13 @@ class EnvironmentApkGetter(EnvironmentFileGetter):
extension = 'apk' extension = 'apk'
description = """ description = """
Uses the same dependency resolution mechanism as ``EnvironmentFileGetter`` with one addition. Uses the same dependency resolution mechanism as ``EnvironmentFileGetter``.
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 ``<root>/apk/<abi>/<apk_name>``. Where ``root`` is the base
resource location e.g. ``~/.workload_automation/dependencies/<extension_name>`` and ``<abi>``
is the ABI for which the APK has been compiled, as returned by ``resource.platform``.
""" """
def get(self, resource, **kwargs): def get(self, resource, **kwargs):
resource_dir = resource.owner.dependencies_directory resource_dir = resource.owner.dependencies_directory
version = kwargs.get('version') version = kwargs.get('version')
variant = kwargs.get('variant_name') 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) return get_from_location_by_extension(resource, resource_dir, self.extension, version, variant=variant)
@ -478,8 +466,6 @@ class RemoteFilerGetter(ResourceGetter):
if resource.owner: if resource.owner:
remote_path = os.path.join(self.remote_path, resource.owner.name) remote_path = os.path.join(self.remote_path, resource.owner.name)
local_path = os.path.join(settings.environment_root, '__filer', resource.owner.dependencies_directory) 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={}' message = 'resource={}, version={}, remote_path={}, local_path={}'
self.logger.debug(message.format(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) 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()] filelist = [ff for ff in filelist if version.lower() in ApkInfo(ff).version_name.lower()]
else: else:
filelist = [ff for ff in filelist if version.lower() in os.path.basename(ff).lower()] 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: if len(filelist) == 1:
return filelist[0] return filelist[0]
elif not filelist: elif not filelist:

@ -28,7 +28,7 @@ import re
from wlauto.exceptions import DeviceError, ConfigError, HostError, WAError from wlauto.exceptions import DeviceError, ConfigError, HostError, WAError
from wlauto.utils.misc import (check_output, escape_single_quotes, from wlauto.utils.misc import (check_output, escape_single_quotes,
escape_double_quotes, get_null, escape_double_quotes, get_null,
CalledProcessErrorWithStderr) CalledProcessErrorWithStderr, ABI_MAP)
MAX_TRIES = 5 MAX_TRIES = 5
@ -164,6 +164,7 @@ class ApkInfo(object):
self.label = None self.label = None
self.version_name = None self.version_name = None
self.version_code = None self.version_code = None
self.native_code = []
self.parse(path) self.parse(path)
def parse(self, apk_path): def parse(self, apk_path):
@ -183,6 +184,19 @@ class ApkInfo(object):
elif line.startswith('launchable-activity:'): elif line.startswith('launchable-activity:'):
match = self.name_regex.search(line) match = self.name_regex.search(line)
self.activity = match.group('name') 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: else:
pass # not interested pass # not interested

@ -47,7 +47,7 @@ from dateutil import tz
# ABI --> architectures list # ABI --> architectures list
ABI_MAP = { ABI_MAP = {
'armeabi': ['armeabi', 'armv7', 'armv7l', 'armv7el', 'armv7lh'], 'armeabi': ['armeabi', 'armv7', 'armv7l', 'armv7el', 'armv7lh', 'armeabi-v7a'],
'arm64': ['arm64', 'armv8', 'arm64-v8a', 'aarch64'], 'arm64': ['arm64', 'armv8', 'arm64-v8a', 'aarch64'],
} }

@ -60,8 +60,8 @@ class Dex2oatBenchmark(Workload):
def setup(self, context): def setup(self, context):
if self.device.getprop('persist.sys.dalvik.vm.lib.2') != 'libart.so': 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.') 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] supported = [abi == 'armeabi' and 'arm' or abi.split('-')[0]
for eabi in self.device.supported_eabi] for abi in self.device.supported_abi]
if self.instruction_set not in supported: if self.instruction_set not in supported:
message = 'Instruction set "{}" is not supported by the device; (supported: {})' message = 'Instruction set "{}" is not supported by the device; (supported: {})'
raise WorkloadError(message.format(self.instruction_set, supported)) raise WorkloadError(message.format(self.instruction_set, supported))