mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-06-19 06:46:12 +01:00
Merge pull request #307 from marcbonnici/exact_abi
Modified how apks containing native code are matched to a target devices supported abi(s).
This commit is contained in:
doc/source
wlauto
common
resource_getters
utils
workloads
dex2oat
@ -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))
|
||||||
|
Reference in New Issue
Block a user