diff --git a/external/devlib/devlib/collector/ftrace.py b/external/devlib/devlib/collector/ftrace.py index 292aa8271ab038d386a8e8f5f836a63828b3af22..9849f4e2158bb362565784c688e6ef0a93db2210 100644 --- a/external/devlib/devlib/collector/ftrace.py +++ b/external/devlib/devlib/collector/ftrace.py @@ -28,7 +28,6 @@ from devlib.collector import (CollectorBase, CollectorOutput, from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.exception import TargetStableError, HostError from devlib.utils.misc import check_output, which, memoized -from devlib.utils.asyn import asyncf TRACE_MARKER_START = 'TRACE_MARKER_START' @@ -244,8 +243,7 @@ class FtraceCollector(CollectorBase): self.target.write_value(self.function_profile_file, 0, verify=False) self._reset_needed = False - @asyncf - async def start(self): + def start(self): self.start_time = time.time() if self._reset_needed: self.reset() @@ -284,17 +282,14 @@ class FtraceCollector(CollectorBase): self.target.cpuidle.perturb_cpus() # Enable kernel function profiling if self.functions and self.tracer is None: - target = self.target - await target.async_manager.concurrently( - execute.asyn('echo nop > {}'.format(self.current_tracer_file), - as_root=True), - execute.asyn('echo 0 > {}'.format(self.function_profile_file), - as_root=True), - execute.asyn('echo {} > {}'.format(self.function_string, self.ftrace_filter_file), - as_root=True), - execute.asyn('echo 1 > {}'.format(self.function_profile_file), - as_root=True), - ) + self.target.execute('echo nop > {}'.format(self.current_tracer_file), + as_root=True) + self.target.execute('echo 0 > {}'.format(self.function_profile_file), + as_root=True) + self.target.execute('echo {} > {}'.format(self.function_string, self.ftrace_filter_file), + as_root=True) + self.target.execute('echo 1 > {}'.format(self.function_profile_file), + as_root=True) def stop(self): diff --git a/external/devlib/devlib/module/cgroups.py b/external/devlib/devlib/module/cgroups.py index ea71e248752681b0e937fd16aaf6365a117997be..6cbab19e08900bd4ab4af4a5a988ca986ce3533c 100644 --- a/external/devlib/devlib/module/cgroups.py +++ b/external/devlib/devlib/module/cgroups.py @@ -19,13 +19,11 @@ from collections import namedtuple from shlex import quote import itertools import warnings -import asyncio from devlib.module import Module from devlib.exception import TargetStableError from devlib.utils.misc import list_to_ranges, isiterable from devlib.utils.types import boolean -from devlib.utils.asyn import asyncf class Controller(object): @@ -57,8 +55,7 @@ class Controller(object): self.mount_point = None self._cgroups = {} - @asyncf - async def mount(self, target, mount_root): + def mount(self, target, mount_root): mounted = target.list_file_systems() if self.mount_name in [e.device for e in mounted]: @@ -71,16 +68,16 @@ class Controller(object): else: # Mount the controller if not already in use self.mount_point = target.path.join(mount_root, self.mount_name) - await target.execute.asyn('mkdir -p {} 2>/dev/null'\ + target.execute('mkdir -p {} 2>/dev/null'\ .format(self.mount_point), as_root=True) - await target.execute.asyn('mount -t cgroup -o {} {} {}'\ + target.execute('mount -t cgroup -o {} {} {}'\ .format(','.join(self.clist), self.mount_name, self.mount_point), as_root=True) # Check if this controller uses "noprefix" option - output = await target.execute.asyn('mount | grep "{} "'.format(self.mount_name)) + output = target.execute('mount | grep "{} "'.format(self.mount_name)) if 'noprefix' in output: self._noprefix = True # self.logger.debug('Controller %s using "noprefix" option', @@ -397,12 +394,11 @@ class CgroupsModule(Module): # Initialize controllers self.logger.info('Available controllers:') self.controllers = {} - - async def register_controller(ss): + for ss in subsys: hid = ss.hierarchy controller = Controller(ss.name, hid, hierarchy[hid]) try: - await controller.mount.asyn(self.target, self.cgroup_root) + controller.mount(self.target, self.cgroup_root) except TargetStableError: message = 'Failed to mount "{}" controller' raise TargetStableError(message.format(controller.kind)) @@ -410,14 +406,6 @@ class CgroupsModule(Module): controller.mount_point) self.controllers[ss.name] = controller - asyncio.run( - target.async_manager.map_concurrently( - register_controller, - subsys, - ) - ) - - def list_subsystems(self): subsystems = [] for line in self.target.execute('{} cat /proc/cgroups'\ diff --git a/external/devlib/devlib/module/cpufreq.py b/external/devlib/devlib/module/cpufreq.py index 1ffa4d8ca32c95433c02042e733118e9f68521e6..f559ef6ccd922e2589616ab628d9c8673abd18e5 100644 --- a/external/devlib/devlib/module/cpufreq.py +++ b/external/devlib/devlib/module/cpufreq.py @@ -13,12 +13,10 @@ # limitations under the License. # from contextlib import contextmanager -from operator import itemgetter from devlib.module import Module from devlib.exception import TargetStableError from devlib.utils.misc import memoized -import devlib.utils.asyn as asyn # a dict of governor name and a list of it tunables that can't be read @@ -32,52 +30,44 @@ class CpufreqModule(Module): name = 'cpufreq' @staticmethod - @asyn.asyncf - async def probe(target): - paths = [ - # x86 with Intel P-State driver - (target.abi == 'x86_64', '/sys/devices/system/cpu/intel_pstate'), - # Generic CPUFreq support (single policy) - (True, '/sys/devices/system/cpu/cpufreq/policy0'), - # Generic CPUFreq support (per CPU policy) - (True, '/sys/devices/system/cpu/cpu0/cpufreq'), - ] - paths = [ - path[1] for path in paths - if path[0] - ] - - exists = await target.async_manager.map_concurrently( - target.file_exists.asyn, - paths, - ) - - return any(exists.values()) + def probe(target): + + # x86 with Intel P-State driver + if target.abi == 'x86_64': + path = '/sys/devices/system/cpu/intel_pstate' + if target.file_exists(path): + return True + + # Generic CPUFreq support (single policy) + path = '/sys/devices/system/cpu/cpufreq/policy0' + if target.file_exists(path): + return True + + # Generic CPUFreq support (per CPU policy) + path = '/sys/devices/system/cpu/cpu0/cpufreq' + return target.file_exists(path) def __init__(self, target): super(CpufreqModule, self).__init__(target) self._governor_tunables = {} @memoized - @asyn.asyncf - async def list_governors(self, cpu): + def list_governors(self, cpu): """Returns a list of governors supported by the cpu.""" if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu) - output = await self.target.read_value.asyn(sysfile) + output = self.target.read_value(sysfile) return output.strip().split() - @asyn.asyncf - async def get_governor(self, cpu): + def get_governor(self, cpu): """Returns the governor currently set for the specified CPU.""" if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) - return await self.target.read_value.asyn(sysfile) + return self.target.read_value(sysfile) - @asyn.asyncf - async def set_governor(self, cpu, governor, **kwargs): + def set_governor(self, cpu, governor, **kwargs): """ Set the governor for the specified CPU. See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt @@ -100,15 +90,15 @@ class CpufreqModule(Module): """ if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) - supported = await self.list_governors.asyn(cpu) + supported = self.list_governors(cpu) if governor not in supported: raise TargetStableError('Governor {} not supported for cpu {}'.format(governor, cpu)) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) - await self.target.write_value.asyn(sysfile, governor) - return await self.set_governor_tunables.asyn(cpu, governor, **kwargs) + self.target.write_value(sysfile, governor) + self.set_governor_tunables(cpu, governor, **kwargs) - @asyn.asynccontextmanager - async def use_governor(self, governor, cpus=None, **kwargs): + @contextmanager + def use_governor(self, governor, cpus=None, **kwargs): """ Use a given governor, then restore previous governor(s) @@ -121,97 +111,66 @@ class CpufreqModule(Module): :Keyword Arguments: Governor tunables, See :meth:`set_governor_tunables` """ if not cpus: - cpus = await self.target.list_online_cpus.asyn() - - async def get_cpu_info(cpu): - return await self.target.async_manager.concurrently(( - self.get_affected_cpus.asyn(cpu), - self.get_governor.asyn(cpu), - self.get_governor_tunables.asyn(cpu), - # We won't always use the frequency, but it's much quicker to - # do concurrently anyway so do it now - self.get_frequency.asyn(cpu), - )) - - cpus_infos = await self.target.async_manager.map_concurrently(get_cpu_info, cpus) - - # Setting a governor & tunables for a cpu will set them for all cpus in - # the same cpufreq policy, so only manipulating one cpu per domain is - # enough - domains = set( - info[0][0] - for info in cpus_infos.values() - ) - - await self.target.async_manager.concurrently( - self.set_governor.asyn(cpu, governor, **kwargs) - for cpu in domains - ) + cpus = self.target.list_online_cpus() + + # Setting a governor & tunables for a cpu will set them for all cpus + # in the same clock domain, so only manipulating one cpu per domain + # is enough + domains = set(self.get_affected_cpus(cpu)[0] for cpu in cpus) + prev_governors = {cpu : (self.get_governor(cpu), self.get_governor_tunables(cpu)) + for cpu in domains} + + # Special case for userspace, frequency is not seen as a tunable + userspace_freqs = {} + for cpu, (prev_gov, _) in prev_governors.items(): + if prev_gov == "userspace": + userspace_freqs[cpu] = self.get_frequency(cpu) + + for cpu in domains: + self.set_governor(cpu, governor, **kwargs) try: yield + finally: - async def set_gov(cpu): - domain, prev_gov, tunables, freq = cpus_infos[cpu] - await self.set_governor.asyn(cpu, prev_gov, **tunables) - # Special case for userspace, frequency is not seen as a tunable + for cpu, (prev_gov, tunables) in prev_governors.items(): + self.set_governor(cpu, prev_gov, **tunables) if prev_gov == "userspace": - await self.set_frequency.asyn(cpu, freq) - - await self.target.async_manager.concurrently( - set_gov(cpu) - for cpu in domains - ) + self.set_frequency(cpu, userspace_freqs[cpu]) - @asyn.asyncf - async def list_governor_tunables(self, cpu): + def list_governor_tunables(self, cpu): """Returns a list of tunables available for the governor on the specified CPU.""" if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) - governor = await self.get_governor.asyn(cpu) + governor = self.get_governor(cpu) if governor not in self._governor_tunables: try: tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor) - self._governor_tunables[governor] = await self.target.list_directory.asyn(tunables_path) + self._governor_tunables[governor] = self.target.list_directory(tunables_path) except TargetStableError: # probably an older kernel try: tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor) - self._governor_tunables[governor] = await self.target.list_directory.asyn(tunables_path) + self._governor_tunables[governor] = self.target.list_directory(tunables_path) except TargetStableError: # governor does not support tunables self._governor_tunables[governor] = [] return self._governor_tunables[governor] - @asyn.asyncf - async def get_governor_tunables(self, cpu): + def get_governor_tunables(self, cpu): if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) - governor, tunable_list = await self.target.async_manager.concurrently(( - self.get_governor.asyn(cpu), - self.list_governor_tunables.asyn(cpu) - )) - - write_only = set(WRITE_ONLY_TUNABLES.get(governor, [])) - tunable_list = [ - tunable - for tunable in tunable_list - if tunable not in write_only - ] - + governor = self.get_governor(cpu) tunables = {} - async def get_tunable(tunable): - try: - path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) - x = await self.target.read_value.asyn(path) - except TargetStableError: # May be an older kernel - path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) - x = await self.target.read_value.asyn(path) - return x - - tunables = await self.target.async_manager.map_concurrently(get_tunable, tunable_list) + for tunable in self.list_governor_tunables(cpu): + if tunable not in WRITE_ONLY_TUNABLES.get(governor, []): + try: + path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) + tunables[tunable] = self.target.read_value(path) + except TargetStableError: # May be an older kernel + path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) + tunables[tunable] = self.target.read_value(path) return tunables - @asyn.asyncf - async def set_governor_tunables(self, cpu, governor=None, **kwargs): + def set_governor_tunables(self, cpu, governor=None, **kwargs): """ Set tunables for the specified governor. Tunables should be specified as keyword arguments. Which tunables and values are valid depends on the @@ -232,35 +191,34 @@ class CpufreqModule(Module): if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) if governor is None: - governor = await self.get_governor.asyn(cpu) - valid_tunables = await self.list_governor_tunables.asyn(cpu) + governor = self.get_governor(cpu) + valid_tunables = self.list_governor_tunables(cpu) for tunable, value in kwargs.items(): if tunable in valid_tunables: path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) try: - await self.target.write_value.asyn(path, value) + self.target.write_value(path, value) except TargetStableError: - if await self.target.file_exists.asyn(path): + if self.target.file_exists(path): # File exists but we did something wrong raise # Expected file doesn't exist, try older sysfs layout. path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) - await self.target.write_value.asyn(path, value) + self.target.write_value(path, value) else: message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu) message += 'Available tunables are: {}'.format(valid_tunables) raise TargetStableError(message) @memoized - @asyn.asyncf - async def list_frequencies(self, cpu): + def list_frequencies(self, cpu): """Returns a sorted list of frequencies supported by the cpu or an empty list if not could be found.""" if isinstance(cpu, int): cpu = 'cpu{}'.format(cpu) try: cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu) - output = await self.target.execute.asyn(cmd) + output = self.target.execute(cmd) available_frequencies = list(map(int, output.strip().split())) # pylint: disable=E1103 except TargetStableError: # On some devices scaling_frequencies is not generated. @@ -268,7 +226,7 @@ class CpufreqModule(Module): # Fall back to parsing stats/time_in_state path = '/sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu) try: - out_iter = (await self.target.read_value.asyn(path)).split() + out_iter = iter(self.target.read_value(path).split()) except TargetStableError: if not self.target.file_exists(path): # Probably intel_pstate. Can't get available freqs. @@ -343,8 +301,7 @@ class CpufreqModule(Module): except ValueError: raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) - @asyn.asyncf - async def get_frequency(self, cpu, cpuinfo=False): + def get_frequency(self, cpu, cpuinfo=False): """ Returns the current frequency currently set for the specified CPU. @@ -364,10 +321,9 @@ class CpufreqModule(Module): sysfile = '/sys/devices/system/cpu/{}/cpufreq/{}'.format( cpu, 'cpuinfo_cur_freq' if cpuinfo else 'scaling_cur_freq') - return await self.target.read_int.asyn(sysfile) + return self.target.read_int(sysfile) - @asyn.asyncf - async def set_frequency(self, cpu, frequency, exact=True): + def set_frequency(self, cpu, frequency, exact=True): """ Set's the minimum value for CPU frequency. Actual frequency will depend on the Governor used and may vary during execution. The value should be @@ -391,16 +347,16 @@ class CpufreqModule(Module): try: value = int(frequency) if exact: - available_frequencies = await self.list_frequencies.asyn(cpu) + available_frequencies = self.list_frequencies(cpu) if available_frequencies and value not in available_frequencies: raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, value, available_frequencies)) - if await self.get_governor.asyn(cpu) != 'userspace': + if self.get_governor(cpu) != 'userspace': raise TargetStableError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu)) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu) - await self.target.write_value.asyn(sysfile, value, verify=False) - cpuinfo = await self.get_frequency.asyn(cpu, cpuinfo=True) + self.target.write_value(sysfile, value, verify=False) + cpuinfo = self.get_frequency(cpu, cpuinfo=True) if cpuinfo != value: self.logger.warning( 'The cpufreq value has not been applied properly cpuinfo={} request={}'.format(cpuinfo, value)) @@ -539,8 +495,7 @@ class CpufreqModule(Module): # pylint: disable=protected-access return self.target._execute_util('cpufreq_trace_all_frequencies', as_root=True) - @asyn.asyncf - async def get_affected_cpus(self, cpu): + def get_affected_cpus(self, cpu): """ Get the online CPUs that share a frequency domain with the given CPU """ @@ -549,8 +504,7 @@ class CpufreqModule(Module): sysfile = '/sys/devices/system/cpu/{}/cpufreq/affected_cpus'.format(cpu) - content = await self.target.read_value.asyn(sysfile) - return [int(c) for c in content.split()] + return [int(c) for c in self.target.read_value(sysfile).split()] @memoized def get_related_cpus(self, cpu): diff --git a/external/devlib/devlib/module/cpuidle.py b/external/devlib/devlib/module/cpuidle.py index 9213ad082c7dba906ff05f7c17c1cdff807ee188..93774bee016adafbc5ddd491c22a816fc4352e98 100644 --- a/external/devlib/devlib/module/cpuidle.py +++ b/external/devlib/devlib/module/cpuidle.py @@ -20,7 +20,6 @@ from pprint import pformat from devlib.module import Module from devlib.utils.types import integer, boolean -import devlib.utils.asyn as asyn class CpuidleState(object): @@ -58,23 +57,19 @@ class CpuidleState(object): self.id = self.target.path.basename(self.path) self.cpu = self.target.path.basename(self.target.path.dirname(path)) - @asyn.asyncf - async def enable(self): - await self.set.asyn('disable', 0) + def enable(self): + self.set('disable', 0) - @asyn.asyncf - async def disable(self): - await self.set.asyn('disable', 1) + def disable(self): + self.set('disable', 1) - @asyn.asyncf - async def get(self, prop): + def get(self, prop): property_path = self.target.path.join(self.path, prop) - return await self.target.read_value.asyn(property_path) + return self.target.read_value(property_path) - @asyn.asyncf - async def set(self, prop, value): + def set(self, prop, value): property_path = self.target.path.join(self.path, prop) - await self.target.write_value.asyn(property_path, value) + self.target.write_value(property_path, value) def __eq__(self, other): if isinstance(other, CpuidleState): @@ -99,9 +94,8 @@ class Cpuidle(Module): root_path = '/sys/devices/system/cpu/cpuidle' @staticmethod - @asyn.asyncf - async def probe(target): - return await target.file_exists.asyn(Cpuidle.root_path) + def probe(target): + return target.file_exists(Cpuidle.root_path) def __init__(self, target): super(Cpuidle, self).__init__(target) @@ -152,43 +146,32 @@ class Cpuidle(Module): return s raise ValueError('Cpuidle state {} does not exist'.format(state)) - @asyn.asyncf - async def enable(self, state, cpu=0): - await self.get_state(state, cpu).enable.asyn() - - @asyn.asyncf - async def disable(self, state, cpu=0): - await self.get_state(state, cpu).disable.asyn() - - @asyn.asyncf - async def enable_all(self, cpu=0): - await self.target.async_manager.concurrently( - state.enable.asyn() - for state in self.get_states(cpu) - ) - - @asyn.asyncf - async def disable_all(self, cpu=0): - await self.target.async_manager.concurrently( - state.disable.asyn() - for state in self.get_states(cpu) - ) - - @asyn.asyncf - async def perturb_cpus(self): + def enable(self, state, cpu=0): + self.get_state(state, cpu).enable() + + def disable(self, state, cpu=0): + self.get_state(state, cpu).disable() + + def enable_all(self, cpu=0): + for state in self.get_states(cpu): + state.enable() + + def disable_all(self, cpu=0): + for state in self.get_states(cpu): + state.disable() + + def perturb_cpus(self): """ Momentarily wake each CPU. Ensures cpu_idle events in trace file. """ # pylint: disable=protected-access - await self.target._execute_util.asyn('cpuidle_wake_all_cpus') + self.target._execute_util('cpuidle_wake_all_cpus') - @asyn.asyncf - async def get_driver(self): - return await self.target.read_value.asyn(self.target.path.join(self.root_path, 'current_driver')) + def get_driver(self): + return self.target.read_value(self.target.path.join(self.root_path, 'current_driver')) - @asyn.asyncf - async def get_governor(self): + def get_governor(self): path = self.target.path.join(self.root_path, 'current_governor_ro') - if not await self.target.file_exists.asyn(path): + if not self.target.file_exists(path): path = self.target.path.join(self.root_path, 'current_governor') - return await self.target.read_value.asyn(path) + return self.target.read_value(path) diff --git a/external/devlib/devlib/target.py b/external/devlib/devlib/target.py index fc521c6df6c1a4f3c1779a03879a9e87188cda58..8fb5ce5682c3eabf3f4d772bee86d1ef0c1101d2 100644 --- a/external/devlib/devlib/target.py +++ b/external/devlib/devlib/target.py @@ -13,7 +13,6 @@ # limitations under the License. # -import asyncio import io import base64 import functools @@ -32,8 +31,6 @@ import threading import uuid import xml.dom.minidom import copy -import inspect -import itertools from collections import namedtuple, defaultdict from contextlib import contextmanager from pipes import quote @@ -46,7 +43,6 @@ except ImportError: from collections import Mapping from enum import Enum -from concurrent.futures import ThreadPoolExecutor from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY from devlib.module import get_module @@ -60,10 +56,8 @@ from devlib.utils.android import AdbConnection, AndroidProperties, LogcatMonitor from devlib.utils.misc import memoized, isiterable, convert_new_lines, groupby_value from devlib.utils.misc import commonprefix, merge_lists from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list -from devlib.utils.misc import batch_contextmanager, tls_property, _BoundTLSProperty, nullcontext -from devlib.utils.misc import strip_bash_colors +from devlib.utils.misc import batch_contextmanager, tls_property, nullcontext from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string, bytes_regex -import devlib.utils.asyn as asyn FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)') @@ -281,21 +275,11 @@ class Target(object): @tls_property def _conn(self): - try: - return self._unused_conns.pop() - except KeyError: - return self.get_connection() + return self.get_connection() # Add a basic property that does not require calling to get the value conn = _conn.basic_property - @tls_property - def _async_manager(self): - return asyn.AsyncManager() - - # Add a basic property that does not require calling to get the value - async_manager = _async_manager.basic_property - def __init__(self, connection_settings=None, platform=None, @@ -306,12 +290,8 @@ class Target(object): load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=None, - is_container=False, - max_async=50, + is_container=False ): - self._async_pool = None - self._async_pool_size = None - self._unused_conns = set() self._is_rooted = None self.connection_settings = connection_settings or {} @@ -354,40 +334,20 @@ class Target(object): self.modules = merge_lists(*module_lists, duplicates='first') self._update_modules('early') if connect: - self.connect(max_async=max_async) + self.connect() def __getstate__(self): - # tls_property will recreate the underlying value automatically upon - # access and is typically used for dynamic content that cannot be - # pickled or should not transmitted to another thread. - ignored = { - k - for k, v in inspect.getmembers(self.__class__) - if isinstance(v, _BoundTLSProperty) - } - ignored.update(( - '_async_pool', - '_unused_conns', - )) return { k: v for k, v in self.__dict__.items() - if k not in ignored + # Avoid sharing the connection instance with the original target, + # so that each target can live its own independent life + if k != '_conn' } - def __setstate__(self, dct): - self.__dict__ = dct - pool_size = self._async_pool_size - if pool_size is None: - self._async_pool = None - else: - self._async_pool = ThreadPoolExecutor(pool_size) - self._unused_conns = set() - # connection and initialization - @asyn.asyncf - async def connect(self, timeout=None, check_boot_completed=True, max_async=50): + def connect(self, timeout=None, check_boot_completed=True): self.platform.init_target_connection(self) # Forcefully set the thread-local value for the connection, with the # timeout we want @@ -400,90 +360,37 @@ class Target(object): self.execute('mkdir -p {}'.format(quote(self.executables_directory))) self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox'), timeout=30) self.conn.busybox = self.busybox - self._detect_max_async(max_async) self.platform.update_from_target(self) self._update_modules('connected') if self.platform.big_core and self.load_default_modules: self._install_module(get_module('bl')) - def _detect_max_async(self, max_async): - self.logger.debug('Detecting max number of async commands ...') - - def make_conn(_): - try: - conn = self.get_connection() - except Exception: - return None - else: - payload = 'hello' - # Sanity check the connection, in case we managed to connect - # but it's actually unusable. - try: - res = conn.execute(f'echo {quote(payload)}') - except Exception: - return None - else: - if res.strip() == payload: - return conn - else: - return None - - # Logging needs to be disabled before the thread pool is created, - # otherwise the logging config will not be taken into account - logging.disable() - try: - # Aggressively attempt to create all the connections in parallel, - # so that this setup step does not take too much time. - with ThreadPoolExecutor(max_async) as pool: - conns = pool.map(make_conn, range(max_async)) - # Avoid polluting the log with errors coming from broken - # connections. - finally: - logging.disable(logging.NOTSET) - - conns = {conn for conn in conns if conn is not None} - - # Keep the connection so it can be reused by future threads - self._unused_conns.update(conns) - max_conns = len(conns) - - self.logger.debug(f'Detected max number of async commands: {max_conns}') - self._async_pool_size = max_conns - self._async_pool = ThreadPoolExecutor(max_conns) - - @asyn.asyncf - async def check_connection(self): + def check_connection(self): """ Check that the connection works without obvious issues. """ - out = await self.execute.asyn('true', as_root=False) + out = self.execute('true', as_root=False) if out.strip(): raise TargetStableError('The shell seems to not be functional and adds content to stderr: {}'.format(out)) def disconnect(self): connections = self._conn.get_all_values() - for conn in itertools.chain(connections, self._unused_conns): + for conn in connections: conn.close() - if self._async_pool is not None: - self._async_pool.__exit__(None, None, None) def get_connection(self, timeout=None): if self.conn_cls is None: raise ValueError('Connection class not specified on Target creation.') - conn = self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable - # This allows forwarding the detected busybox for connections created in new threads. - conn.busybox = self.busybox - return conn + return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable def wait_boot_complete(self, timeout=10): raise NotImplementedError() - @asyn.asyncf - async def setup(self, executables=None): - await self._setup_shutils.asyn() + def setup(self, executables=None): + self._setup_shutils() for host_exe in (executables or []): # pylint: disable=superfluous-parens - await self.install.asyn(host_exe) + self.install(host_exe) # Check for platform dependent setup procedures self.platform.setup(self) @@ -491,7 +398,7 @@ class Target(object): # Initialize modules which requires Buxybox (e.g. shutil dependent tasks) self._update_modules('setup') - await self.execute.asyn('mkdir -p {}'.format(quote(self._file_transfer_cache))) + self.execute('mkdir -p {}'.format(quote(self._file_transfer_cache))) def reboot(self, hard=False, connect=True, timeout=180): if hard: @@ -520,8 +427,8 @@ class Target(object): # file transfer - @asyn.asynccontextmanager - async def _xfer_cache_path(self, name): + @contextmanager + def _xfer_cache_path(self, name): """ Context manager to provide a unique path in the transfer cache with the basename of the given name. @@ -537,16 +444,15 @@ class Target(object): check_rm = False try: - await self.makedirs.asyn(folder) + self.makedirs(folder) # Don't check the exit code as the folder might not even exist # before this point, if creating it failed check_rm = True yield self.path.join(folder, name) finally: - await self.execute.asyn('rm -rf -- {}'.format(quote(folder)), check_exit_code=check_rm) + self.execute('rm -rf -- {}'.format(quote(folder)), check_exit_code=check_rm) - @asyn.asyncf - async def _prepare_xfer(self, action, sources, dest, pattern=None, as_root=False): + def _prepare_xfer(self, action, sources, dest, pattern=None, as_root=False): """ Check the sanity of sources and destination and prepare the ground for transfering multiple sources. @@ -687,37 +593,28 @@ class Target(object): for src in sources }) - @asyn.asyncf @call_conn - async def push(self, source, dest, as_root=False, timeout=None, globbing=False): # pylint: disable=arguments-differ + def push(self, source, dest, as_root=False, timeout=None, globbing=False): # pylint: disable=arguments-differ source = str(source) dest = str(dest) sources = glob.glob(source) if globbing else [source] - mapping = await self._prepare_xfer.asyn('push', sources, dest, pattern=source if globbing else None, as_root=as_root) + mapping = self._prepare_xfer('push', sources, dest, pattern=source if globbing else None, as_root=as_root) def do_push(sources, dest): - for src in sources: - self.async_manager.track_access( - asyn.PathAccess(namespace='host', path=src, mode='r') - ) - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=dest, mode='w') - ) return self.conn.push(sources, dest, timeout=timeout) if as_root: for sources, dest in mapping.items(): for source in sources: - async with self._xfer_cache_path(source) as device_tempfile: + with self._xfer_cache_path(source) as device_tempfile: do_push([source], device_tempfile) - await self.execute.asyn("mv -f -- {} {}".format(quote(device_tempfile), quote(dest)), as_root=True) + self.execute("mv -f -- {} {}".format(quote(device_tempfile), quote(dest)), as_root=True) else: for sources, dest in mapping.items(): do_push(sources, dest) - @asyn.asyncf - async def _expand_glob(self, pattern, **kwargs): + def _expand_glob(self, pattern, **kwargs): """ Expand the given path globbing pattern on the target using the shell globbing. @@ -750,21 +647,20 @@ class Target(object): cmd = '{} sh -c {} 2>/dev/null'.format(quote(self.busybox), quote(cmd)) # On some shells, match failure will make the command "return" a # non-zero code, even though the command was not actually called - result = await self.execute.asyn(cmd, strip_colors=False, check_exit_code=False, **kwargs) + result = self.execute(cmd, strip_colors=False, check_exit_code=False, **kwargs) paths = result.splitlines() if not paths: raise TargetStableError('No file matching: {}'.format(pattern)) return paths - @asyn.asyncf @call_conn - async def pull(self, source, dest, as_root=False, timeout=None, globbing=False, via_temp=False): # pylint: disable=arguments-differ + def pull(self, source, dest, as_root=False, timeout=None, globbing=False, via_temp=False): # pylint: disable=arguments-differ source = str(source) dest = str(dest) if globbing: - sources = await self._expand_glob.asyn(source, as_root=as_root) + sources = self._expand_glob(source, as_root=as_root) else: sources = [source] @@ -772,31 +668,23 @@ class Target(object): # so use a temporary copy instead. via_temp |= as_root - mapping = await self._prepare_xfer.asyn('pull', sources, dest, pattern=source if globbing else None, as_root=as_root) + mapping = self._prepare_xfer('pull', sources, dest, pattern=source if globbing else None, as_root=as_root) def do_pull(sources, dest): - for src in sources: - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=src, mode='r') - ) - self.async_manager.track_access( - asyn.PathAccess(namespace='host', path=dest, mode='w') - ) self.conn.pull(sources, dest, timeout=timeout) if via_temp: for sources, dest in mapping.items(): for source in sources: - async with self._xfer_cache_path(source) as device_tempfile: - await self.execute.asyn("cp -r -- {} {}".format(quote(source), quote(device_tempfile)), as_root=as_root) - await self.execute.asyn("{} chmod 0644 -- {}".format(self.busybox, quote(device_tempfile)), as_root=as_root) + with self._xfer_cache_path(source) as device_tempfile: + self.execute("cp -r -- {} {}".format(quote(source), quote(device_tempfile)), as_root=as_root) + self.execute("{} chmod 0644 -- {}".format(self.busybox, quote(device_tempfile)), as_root=as_root) do_pull([device_tempfile], dest) else: for sources, dest in mapping.items(): do_pull(sources, dest) - @asyn.asyncf - async def get_directory(self, source_dir, dest, as_root=False): + def get_directory(self, source_dir, dest, as_root=False): """ Pull a directory from the device, after compressing dir """ # Create all file names tar_file_name = source_dir.lstrip(self.path.sep).replace(self.path.sep, '.') @@ -810,12 +698,12 @@ class Target(object): tar_file_cm = self._xfer_cache_path if as_root else nullcontext # Does the folder exist? - await self.execute.asyn('ls -la {}'.format(quote(source_dir)), as_root=as_root) + self.execute('ls -la {}'.format(quote(source_dir)), as_root=as_root) - async with tar_file_cm(tar_file_name) as tar_file_name: + with tar_file_cm(tar_file_name) as tar_file_name: # Try compressing the folder try: - await self.execute.asyn('{} tar -cvf {} {}'.format( + self.execute('{} tar -cvf {} {}'.format( quote(self.busybox), quote(tar_file_name), quote(source_dir) ), as_root=as_root) except TargetStableError: @@ -824,7 +712,7 @@ class Target(object): # Pull the file if not os.path.exists(dest): os.mkdir(dest) - await self.pull.asyn(tar_file_name, tmpfile) + self.pull(tar_file_name, tmpfile) # Decompress with tarfile.open(tmpfile, 'r') as f: f.extractall(outdir) @@ -845,41 +733,8 @@ class Target(object): return command - class _BrokenConnection(Exception): - pass - - @asyn.asyncf - @call_conn - async def _execute_async(self, *args, **kwargs): - execute = functools.partial( - self._execute, - *args, **kwargs - ) - pool = self._async_pool - - if pool is None: - return execute() - else: - - def thread_f(): - # If we cannot successfully connect from the thread, it might - # mean that something external opened a connection on the - # target, so we just revert to the blocking path. - try: - self.conn - except Exception: - raise self._BrokenConnection - else: - return execute() - - loop = asyncio.get_running_loop() - try: - return await loop.run_in_executor(pool, thread_f) - except self._BrokenConnection: - return execute() - @call_conn - def _execute(self, command, timeout=None, check_exit_code=True, + def execute(self, command, timeout=None, check_exit_code=True, as_root=False, strip_colors=True, will_succeed=False, force_locale='C'): @@ -888,15 +743,9 @@ class Target(object): check_exit_code=check_exit_code, as_root=as_root, strip_colors=strip_colors, will_succeed=will_succeed) - execute = asyn._AsyncPolymorphicFunction( - asyn=_execute_async.asyn, - blocking=_execute, - ) - @call_conn def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False, force_locale='C', timeout=None): - conn = self.conn command = self._prepare_cmd(command, force_locale) bg_cmd = self.conn.background(command, stdout, stderr, as_root) if timeout is not None: @@ -978,43 +827,32 @@ class Target(object): # sysfs interaction - @asyn.asyncf - async def read_value(self, path, kind=None): - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=path, mode='r') - ) - output = await self.execute.asyn('cat {}'.format(quote(path)), as_root=self.needs_su) # pylint: disable=E1103 - output = output.strip() + def read_value(self, path, kind=None): + output = self.execute('cat {}'.format(quote(path)), as_root=self.needs_su).strip() # pylint: disable=E1103 if kind: return kind(output) else: return output - @asyn.asyncf - async def read_int(self, path): - return await self.read_value.asyn(path, kind=integer) + def read_int(self, path): + return self.read_value(path, kind=integer) - @asyn.asyncf - async def read_bool(self, path): - return await self.read_value.asyn(path, kind=boolean) + def read_bool(self, path): + return self.read_value(path, kind=boolean) - @asyn.asynccontextmanager - async def revertable_write_value(self, path, value, verify=True): + @contextmanager + def revertable_write_value(self, path, value, verify=True): orig_value = self.read_value(path) try: - await self.write_value.asyn(path, value, verify) + self.write_value(path, value, verify) yield finally: - await self.write_value.asyn(path, orig_value, verify) + self.write_value(path, orig_value, verify) def batch_revertable_write_value(self, kwargs_list): return batch_contextmanager(self.revertable_write_value, kwargs_list) - @asyn.asyncf - async def write_value(self, path, value, verify=True): - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=path, mode='w') - ) + def write_value(self, path, value, verify=True): value = str(value) if verify: @@ -1043,7 +881,7 @@ fi cmd = cmd.format(busybox=quote(self.busybox), path=quote(path), value=quote(value)) try: - await self.execute.asyn(cmd, check_exit_code=True, as_root=True) + self.execute(cmd, check_exit_code=True, as_root=True) except TargetCalledProcessError as e: if e.returncode == 10: raise TargetStableError('Could not write "{value}" to {path}: {e.output}'.format( @@ -1094,26 +932,22 @@ fi # files - @asyn.asyncf - async def makedirs(self, path, as_root=False): - await self.execute.asyn('mkdir -p {}'.format(quote(path)), as_root=as_root) + def makedirs(self, path, as_root=False): + self.execute('mkdir -p {}'.format(quote(path)), as_root=as_root) - @asyn.asyncf - async def file_exists(self, filepath): + def file_exists(self, filepath): command = 'if [ -e {} ]; then echo 1; else echo 0; fi' - output = await self.execute.asyn(command.format(quote(filepath)), as_root=self.is_rooted) + output = self.execute(command.format(quote(filepath)), as_root=self.is_rooted) return boolean(output.strip()) - @asyn.asyncf - async def directory_exists(self, filepath): - output = await self.execute.asyn('if [ -d {} ]; then echo 1; else echo 0; fi'.format(quote(filepath))) + def directory_exists(self, filepath): + output = self.execute('if [ -d {} ]; then echo 1; else echo 0; fi'.format(quote(filepath))) # output from ssh my contain part of the expression in the buffer, # split out everything except the last word. return boolean(output.split()[-1]) # pylint: disable=maybe-no-member - @asyn.asyncf - async def list_file_systems(self): - output = await self.execute.asyn('mount') + def list_file_systems(self): + output = self.execute('mount') fstab = [] for line in output.split('\n'): line = line.strip() @@ -1128,44 +962,31 @@ fi fstab.append(FstabEntry(*line.split())) return fstab - @asyn.asyncf - async def list_directory(self, path, as_root=False): - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=path, mode='r') - ) - return await self._list_directory(path, as_root=as_root) - - def _list_directory(self, path, as_root=False): + def list_directory(self, path, as_root=False): raise NotImplementedError() def get_workpath(self, name): return self.path.join(self.working_directory, name) - @asyn.asyncf - async def tempfile(self, prefix='', suffix=''): - name = '{prefix}_{uuid}_{suffix}'.format( - prefix=prefix, - uuid=uuid.uuid4().hex, - suffix=suffix, - ) - path = self.get_workpath(name) - if (await self.file_exists.asyn(path)): - raise FileExistsError('Path already exists on the target: {}'.format(path)) - else: - return path + def tempfile(self, prefix='', suffix=''): + names = tempfile._get_candidate_names() # pylint: disable=W0212 + for _ in range(tempfile.TMP_MAX): + name = next(names) + path = self.get_workpath(prefix + name + suffix) + if not self.file_exists(path): + return path + raise IOError('No usable temporary filename found') - @asyn.asyncf - async def remove(self, path, as_root=False): - await self.execute.asyn('rm -rf -- {}'.format(quote(path)), as_root=as_root) + def remove(self, path, as_root=False): + self.execute('rm -rf -- {}'.format(quote(path)), as_root=as_root) # misc def core_cpus(self, core): return [i for i, c in enumerate(self.core_names) if c == core] - @asyn.asyncf - async def list_online_cpus(self, core=None): + def list_online_cpus(self, core=None): path = self.path.join('/sys/devices/system/cpu/online') - output = await self.read_value.asyn(path) + output = self.read_value(path) all_online = ranges_to_list(output) if core: cpus = self.core_cpus(core) @@ -1175,16 +996,13 @@ fi else: return all_online - @asyn.asyncf - async def list_offline_cpus(self): - online = await self.list_online_cpus.asyn() + def list_offline_cpus(self): + online = self.list_online_cpus() return [c for c in range(self.number_of_cpus) if c not in online] - @asyn.asyncf - async def getenv(self, variable): - var = await self.execute.asyn('printf "%s" ${}'.format(variable)) - return var.rstrip('\r\n') + def getenv(self, variable): + return self.execute('echo ${}'.format(variable)).rstrip('\r\n') def capture_screen(self, filepath): raise NotImplementedError() @@ -1195,36 +1013,32 @@ fi def uninstall(self, name): raise NotImplementedError() - @asyn.asyncf - async def get_installed(self, name, search_system_binaries=True): + def get_installed(self, name, search_system_binaries=True): # Check user installed binaries first if self.file_exists(self.executables_directory): - if name in (await self.list_directory.asyn(self.executables_directory)): + if name in self.list_directory(self.executables_directory): return self.path.join(self.executables_directory, name) # Fall back to binaries in PATH if search_system_binaries: - PATH = await self.getenv.asyn('PATH') - for path in PATH.split(self.path.pathsep): + for path in self.getenv('PATH').split(self.path.pathsep): try: - if name in (await self.list_directory.asyn(path)): + if name in self.list_directory(path): return self.path.join(path, name) except TargetStableError: pass # directory does not exist or no executable permissions which = get_installed - @asyn.asyncf - async def install_if_needed(self, host_path, search_system_binaries=True, timeout=None): + def install_if_needed(self, host_path, search_system_binaries=True, timeout=None): - binary_path = await self.get_installed.asyn(os.path.split(host_path)[1], + binary_path = self.get_installed(os.path.split(host_path)[1], search_system_binaries=search_system_binaries) if not binary_path: - binary_path = await self.install.asyn(host_path, timeout=timeout) + binary_path = self.install(host_path, timeout=timeout) return binary_path - @asyn.asyncf - async def is_installed(self, name): - return bool(await self.get_installed.asyn(name)) + def is_installed(self, name): + return bool(self.get_installed(name)) def bin(self, name): return self._installed_binaries.get(name, name) @@ -1232,29 +1046,30 @@ fi def has(self, modname): return hasattr(self, identifier(modname)) - @asyn.asyncf - async def lsmod(self): - lines = (await self.execute.asyn('lsmod')).splitlines() + def lsmod(self): + lines = self.execute('lsmod').splitlines() entries = [] for line in lines[1:]: # first line is the header if not line.strip(): continue - name, size, use_count, *remainder = line.split() - if remainder: - used_by = ''.join(remainder).split(',') + parts = line.split() + name = parts[0] + size = int(parts[1]) + use_count = int(parts[2]) + if len(parts) > 3: + used_by = ''.join(parts[3:]).split(',') else: used_by = [] entries.append(LsmodEntry(name, size, use_count, used_by)) return entries - @asyn.asyncf - async def insmod(self, path): + def insmod(self, path): target_path = self.get_workpath(os.path.basename(path)) - await self.push.asyn(path, target_path) - await self.execute.asyn('insmod {}'.format(quote(target_path)), as_root=True) + self.push(path, target_path) + self.execute('insmod {}'.format(quote(target_path)), as_root=True) - @asyn.asyncf - async def extract(self, path, dest=None): + + def extract(self, path, dest=None): """ Extract the specified on-target file. The extraction method to be used (unzip, gunzip, bunzip2, or tar) will be based on the file's extension. @@ -1275,32 +1090,27 @@ fi for ending in ['.tar.gz', '.tar.bz', '.tar.bz2', '.tgz', '.tbz', '.tbz2']: if path.endswith(ending): - return await self._extract_archive(path, 'tar xf {} -C {}', dest) + return self._extract_archive(path, 'tar xf {} -C {}', dest) ext = self.path.splitext(path)[1] if ext in ['.bz', '.bz2']: - return await self._extract_file(path, 'bunzip2 -f {}', dest) + return self._extract_file(path, 'bunzip2 -f {}', dest) elif ext == '.gz': - return await self._extract_file(path, 'gunzip -f {}', dest) + return self._extract_file(path, 'gunzip -f {}', dest) elif ext == '.zip': - return await self._extract_archive(path, 'unzip {} -d {}', dest) + return self._extract_archive(path, 'unzip {} -d {}', dest) else: raise ValueError('Unknown compression format: {}'.format(ext)) - @asyn.asyncf - async def sleep(self, duration): + def sleep(self, duration): timeout = duration + 10 - await self.execute.asyn('sleep {}'.format(duration), timeout=timeout) + self.execute('sleep {}'.format(duration), timeout=timeout) - @asyn.asyncf - async def read_tree_tar_flat(self, path, depth=1, check_exit_code=True, + def read_tree_tar_flat(self, path, depth=1, check_exit_code=True, decode_unicode=True, strip_null_chars=True): - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=path, mode='r') - ) command = 'read_tree_tgz_b64 {} {} {}'.format(quote(path), depth, quote(self.working_directory)) - output = await self._execute_util.asyn(command, as_root=self.is_rooted, + output = self._execute_util(command, as_root=self.is_rooted, check_exit_code=check_exit_code) result = {} @@ -1333,13 +1143,9 @@ fi return result - @asyn.asyncf - async def read_tree_values_flat(self, path, depth=1, check_exit_code=True): - self.async_manager.track_access( - asyn.PathAccess(namespace='target', path=path, mode='r') - ) + def read_tree_values_flat(self, path, depth=1, check_exit_code=True): command = 'read_tree_values {} {}'.format(quote(path), depth) - output = await self._execute_util.asyn(command, as_root=self.is_rooted, + output = self._execute_util(command, as_root=self.is_rooted, check_exit_code=check_exit_code) accumulator = defaultdict(list) @@ -1352,8 +1158,7 @@ fi result = {k: '\n'.join(v).strip() for k, v in accumulator.items()} return result - @asyn.asyncf - async def read_tree_values(self, path, depth=1, dictcls=dict, + def read_tree_values(self, path, depth=1, dictcls=dict, check_exit_code=True, tar=False, decode_unicode=True, strip_null_chars=True): """ @@ -1372,9 +1177,9 @@ fi :returns: a tree-like dict with the content of files as leafs """ if not tar: - value_map = await self.read_tree_values_flat.asyn(path, depth, check_exit_code) + value_map = self.read_tree_values_flat(path, depth, check_exit_code) else: - value_map = await self.read_tree_tar_flat.asyn(path, depth, check_exit_code, + value_map = self.read_tree_tar_flat(path, depth, check_exit_code, decode_unicode, strip_null_chars) return _build_path_tree(value_map, path, self.path.sep, dictcls) @@ -1393,47 +1198,42 @@ fi # internal methods - @asyn.asyncf - async def _setup_shutils(self): + def _setup_shutils(self): shutils_ifile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils.in') + tmp_dir = tempfile.mkdtemp() + shutils_ofile = os.path.join(tmp_dir, 'shutils') with open(shutils_ifile) as fh: lines = fh.readlines() - with tempfile.TemporaryDirectory() as folder: - shutils_ofile = os.path.join(folder, 'shutils') - with open(shutils_ofile, 'w') as ofile: - for line in lines: - line = line.replace("__DEVLIB_BUSYBOX__", self.busybox) - ofile.write(line) - self._shutils = await self.install.asyn(shutils_ofile) - - @asyn.asyncf + with open(shutils_ofile, 'w') as ofile: + for line in lines: + line = line.replace("__DEVLIB_BUSYBOX__", self.busybox) + ofile.write(line) + self._shutils = self.install(shutils_ofile) + os.remove(shutils_ofile) + os.rmdir(tmp_dir) + @call_conn - async def _execute_util(self, command, timeout=None, check_exit_code=True, as_root=False): + def _execute_util(self, command, timeout=None, check_exit_code=True, as_root=False): command = '{} sh {} {}'.format(quote(self.busybox), quote(self.shutils), command) - return await self.execute.asyn( - command, - timeout=timeout, - check_exit_code=check_exit_code, - as_root=as_root - ) + return self.conn.execute(command, timeout, check_exit_code, as_root) - async def _extract_archive(self, path, cmd, dest=None): + def _extract_archive(self, path, cmd, dest=None): cmd = '{} ' + cmd # busybox if dest: extracted = dest else: extracted = self.path.dirname(path) cmdtext = cmd.format(quote(self.busybox), quote(path), quote(extracted)) - await self.execute.asyn(cmdtext) + self.execute(cmdtext) return extracted - async def _extract_file(self, path, cmd, dest=None): + def _extract_file(self, path, cmd, dest=None): cmd = '{} ' + cmd # busybox cmdtext = cmd.format(quote(self.busybox), quote(path)) - await self.execute.asyn(cmdtext) + self.execute(cmdtext) extracted = self.path.splitext(path)[0] if dest: - await self.execute.asyn('mv -f {} {}'.format(quote(extracted), quote(dest))) + self.execute('mv -f {} {}'.format(quote(extracted), quote(dest))) if dest.endswith('/'): extracted = self.path.join(dest, self.path.basename(extracted)) else: @@ -1477,8 +1277,7 @@ fi def _resolve_paths(self): raise NotImplementedError() - @asyn.asyncf - async def is_network_connected(self): + def is_network_connected(self): self.logger.debug('Checking for internet connectivity...') timeout_s = 5 @@ -1493,7 +1292,7 @@ fi attempts = 5 for _ in range(attempts): try: - await self.execute.asyn(command) + self.execute(command) return True except TargetStableError as e: err = str(e).lower() @@ -1563,7 +1362,6 @@ class LinuxTarget(Target): shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=SshConnection, is_container=False, - max_async=50, ): super(LinuxTarget, self).__init__(connection_settings=connection_settings, platform=platform, @@ -1574,8 +1372,7 @@ class LinuxTarget(Target): load_default_modules=load_default_modules, shell_prompt=shell_prompt, conn_cls=conn_cls, - is_container=is_container, - max_async=max_async) + is_container=is_container) def wait_boot_complete(self, timeout=10): pass @@ -1585,33 +1382,29 @@ class LinuxTarget(Target): command = 'sh -c {} 1>/dev/null 2>/dev/null &'.format(quote(command)) return self.conn.execute(command, as_root=as_root) - @asyn.asyncf - async def get_pids_of(self, process_name): + def get_pids_of(self, process_name): """Returns a list of PIDs of all processes with the specified name.""" # result should be a column of PIDs with the first row as "PID" header - result = await self.execute.asyn('ps -C {} -o pid'.format(quote(process_name)), # NOQA - check_exit_code=False) - result = result.strip().split() + result = self.execute('ps -C {} -o pid'.format(quote(process_name)), # NOQA + check_exit_code=False).strip().split() if len(result) >= 2: # at least one row besides the header return list(map(int, result[1:])) else: return [] - @asyn.asyncf - async def ps(self, threads=False, **kwargs): + def ps(self, threads=False, **kwargs): ps_flags = '-eo' if threads: ps_flags = '-eLo' command = 'ps {} user,pid,tid,ppid,vsize,rss,wchan,pcpu,state,fname'.format(ps_flags) - out = await self.execute.asyn(command) + lines = iter(convert_new_lines(self.execute(command)).split('\n')) + next(lines) # header result = [] - lines = convert_new_lines(out).splitlines() - # Skip header - for line in lines[1:]: + for line in lines: parts = re.split(r'\s+', line, maxsplit=9) - if parts: + if parts and parts != ['']: result.append(PsEntry(*(parts[0:1] + list(map(int, parts[1:6])) + parts[6:]))) if not kwargs: @@ -1623,37 +1416,34 @@ class LinuxTarget(Target): filtered_result.append(entry) return filtered_result - async def _list_directory(self, path, as_root=False): - contents = await self.execute.asyn('ls -1 {}'.format(quote(path)), as_root=as_root) + def list_directory(self, path, as_root=False): + contents = self.execute('ls -1 {}'.format(quote(path)), as_root=as_root) return [x.strip() for x in contents.split('\n') if x.strip()] - @asyn.asyncf - async def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 + def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 destpath = self.path.join(self.executables_directory, with_name and with_name or self.path.basename(filepath)) - await self.push.asyn(filepath, destpath, timeout=timeout) - await self.execute.asyn('chmod a+x {}'.format(quote(destpath)), timeout=timeout) + self.push(filepath, destpath, timeout=timeout) + self.execute('chmod a+x {}'.format(quote(destpath)), timeout=timeout) self._installed_binaries[self.path.basename(destpath)] = destpath return destpath - @asyn.asyncf - async def uninstall(self, name): + def uninstall(self, name): path = self.path.join(self.executables_directory, name) - await self.remove.asyn(path) + self.remove(path) - @asyn.asyncf - async def capture_screen(self, filepath): - if not (await self.is_installed.asyn('scrot')): + def capture_screen(self, filepath): + if not self.is_installed('scrot'): self.logger.debug('Could not take screenshot as scrot is not installed.') return try: - tmpfile = await self.tempfile.asyn() + tmpfile = self.tempfile() cmd = 'DISPLAY=:0.0 scrot {} && {} date -u -Iseconds' - ts = (await self.execute.asyn(cmd.format(quote(tmpfile), quote(self.busybox)))).strip() + ts = self.execute(cmd.format(quote(tmpfile), quote(self.busybox))).strip() filepath = filepath.format(ts=ts) - await self.pull.asyn(tmpfile, filepath) - await self.remove.asyn(tmpfile) + self.pull(tmpfile, filepath) + self.remove(tmpfile) except TargetStableError as e: if "Can't open X dispay." not in e.message: raise e @@ -1770,7 +1560,6 @@ class AndroidTarget(Target): conn_cls=AdbConnection, package_data_directory="/data/data", is_container=False, - max_async=50, ): super(AndroidTarget, self).__init__(connection_settings=connection_settings, platform=platform, @@ -1781,8 +1570,7 @@ class AndroidTarget(Target): load_default_modules=load_default_modules, shell_prompt=shell_prompt, conn_cls=conn_cls, - is_container=is_container, - max_async=max_async) + is_container=is_container) self.package_data_directory = package_data_directory self._init_logcat_lock() @@ -1801,35 +1589,31 @@ class AndroidTarget(Target): self.__dict__.update(dct) self._init_logcat_lock() - @asyn.asyncf - async def reset(self, fastboot=False): # pylint: disable=arguments-differ + def reset(self, fastboot=False): # pylint: disable=arguments-differ try: - await self.execute.asyn('reboot {}'.format(fastboot and 'fastboot' or ''), + self.execute('reboot {}'.format(fastboot and 'fastboot' or ''), as_root=self.needs_su, timeout=2) except (DevlibTransientError, subprocess.CalledProcessError): # on some targets "reboot" doesn't return gracefully pass self.conn.connected_as_root = None - @asyn.asyncf - async def wait_boot_complete(self, timeout=10): + def wait_boot_complete(self, timeout=10): start = time.time() - boot_completed = boolean(await self.getprop.asyn('sys.boot_completed')) + boot_completed = boolean(self.getprop('sys.boot_completed')) while not boot_completed and timeout >= time.time() - start: time.sleep(5) - boot_completed = boolean(await self.getprop.asyn('sys.boot_completed')) + boot_completed = boolean(self.getprop('sys.boot_completed')) if not boot_completed: # Raise a TargetStableError as this usually happens because of # an issue with Android more than a timeout that is too small. raise TargetStableError('Connected but Android did not fully boot.') - @asyn.asyncf - async def connect(self, timeout=30, check_boot_completed=True): # pylint: disable=arguments-differ + def connect(self, timeout=30, check_boot_completed=True): # pylint: disable=arguments-differ device = self.connection_settings.get('device') - await super(AndroidTarget, self).connect.asyn(timeout=timeout, check_boot_completed=check_boot_completed) + super(AndroidTarget, self).connect(timeout=timeout, check_boot_completed=check_boot_completed) - @asyn.asyncf - async def kick_off(self, command, as_root=None): + def kick_off(self, command, as_root=None): """ Like execute but closes adb session and returns immediately, leaving the command running on the device (this is different from execute(background=True) which keeps adb connection open and returns @@ -1839,12 +1623,11 @@ class AndroidTarget(Target): as_root = self.needs_su try: command = 'cd {} && {} nohup {} &'.format(quote(self.working_directory), quote(self.busybox), command) - await self.execute.asyn(command, timeout=1, as_root=as_root) + self.execute(command, timeout=1, as_root=as_root) except TimeoutError: pass - @asyn.asyncf - async def __setup_list_directory(self): + def __setup_list_directory(self): # In at least Linaro Android 16.09 (which was their first Android 7 release) and maybe # AOSP 7.0 as well, the ls command was changed. # Previous versions default to a single column listing, which is nice and easy to parse. @@ -1853,48 +1636,44 @@ class AndroidTarget(Target): # so we try the new version, and if it fails we use the old version. self.ls_command = 'ls -1' try: - await self.execute.asyn('ls -1 {}'.format(quote(self.working_directory)), as_root=False) + self.execute('ls -1 {}'.format(quote(self.working_directory)), as_root=False) except TargetStableError: self.ls_command = 'ls' - async def _list_directory(self, path, as_root=False): + def list_directory(self, path, as_root=False): if self.ls_command == '': - await self.__setup_list_directory.asyn() - contents = await self.execute.asyn('{} {}'.format(self.ls_command, quote(path)), as_root=as_root) + self.__setup_list_directory() + contents = self.execute('{} {}'.format(self.ls_command, quote(path)), as_root=as_root) return [x.strip() for x in contents.split('\n') if x.strip()] - @asyn.asyncf - async def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 + def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221 ext = os.path.splitext(filepath)[1].lower() if ext == '.apk': - return await self.install_apk.asyn(filepath, timeout) + return self.install_apk(filepath, timeout) else: - return await self.install_executable.asyn(filepath, with_name, timeout) + return self.install_executable(filepath, with_name, timeout) - @asyn.asyncf - async def uninstall(self, name): - if await self.package_is_installed.asyn(name): - await self.uninstall_package.asyn(name) + def uninstall(self, name): + if self.package_is_installed(name): + self.uninstall_package(name) else: - await self.uninstall_executable.asyn(name) + self.uninstall_executable(name) - @asyn.asyncf - async def get_pids_of(self, process_name): + def get_pids_of(self, process_name): result = [] search_term = process_name[-15:] - for entry in await self.ps.asyn(): + for entry in self.ps(): if search_term in entry.name: result.append(entry.pid) return result - @asyn.asyncf - async def ps(self, threads=False, **kwargs): + def ps(self, threads=False, **kwargs): maxsplit = 9 if threads else 8 command = 'ps' if threads: command = 'ps -AT' - lines = iter(convert_new_lines(await self.execute.asyn(command)).split('\n')) + lines = iter(convert_new_lines(self.execute(command)).split('\n')) next(lines) # header result = [] for line in lines: @@ -1923,42 +1702,37 @@ class AndroidTarget(Target): filtered_result.append(entry) return filtered_result - @asyn.asyncf - async def capture_screen(self, filepath): + def capture_screen(self, filepath): on_device_file = self.path.join(self.working_directory, 'screen_capture.png') cmd = 'screencap -p {} && {} date -u -Iseconds' - ts = (await self.execute.asyn(cmd.format(quote(on_device_file), quote(self.busybox)))).strip() + ts = self.execute(cmd.format(quote(on_device_file), quote(self.busybox))).strip() filepath = filepath.format(ts=ts) - await self.pull.asyn(on_device_file, filepath) - await self.remove.asyn(on_device_file) + self.pull(on_device_file, filepath) + self.remove(on_device_file) # Android-specific - @asyn.asyncf - async def input_tap(self, x, y): + def input_tap(self, x, y): command = 'input tap {} {}' - await self.execute.asyn(command.format(x, y)) + self.execute(command.format(x, y)) - @asyn.asyncf - async def input_tap_pct(self, x, y): + def input_tap_pct(self, x, y): width, height = self.screen_resolution x = (x * width) // 100 y = (y * height) // 100 - await self.input_tap.asyn(x, y) + self.input_tap(x, y) - @asyn.asyncf - async def input_swipe(self, x1, y1, x2, y2): + def input_swipe(self, x1, y1, x2, y2): """ Issue a swipe on the screen from (x1, y1) to (x2, y2) Uses absolute screen positions """ command = 'input swipe {} {} {} {}' - await self.execute.asyn(command.format(x1, y1, x2, y2)) + self.execute(command.format(x1, y1, x2, y2)) - @asyn.asyncf - async def input_swipe_pct(self, x1, y1, x2, y2): + def input_swipe_pct(self, x1, y1, x2, y2): """ Issue a swipe on the screen from (x1, y1) to (x2, y2) Uses percent-based positions @@ -1970,43 +1744,38 @@ class AndroidTarget(Target): x2 = (x2 * width) // 100 y2 = (y2 * height) // 100 - await self.input_swipe.asyn(x1, y1, x2, y2) + self.input_swipe(x1, y1, x2, y2) - @asyn.asyncf - async def swipe_to_unlock(self, direction="diagonal"): + def swipe_to_unlock(self, direction="diagonal"): width, height = self.screen_resolution if direction == "diagonal": start = 100 stop = width - start swipe_height = height * 2 // 3 - await self.input_swipe.asyn(start, swipe_height, stop, 0) + self.input_swipe(start, swipe_height, stop, 0) elif direction == "horizontal": swipe_height = height * 2 // 3 start = 100 stop = width - start - await self.input_swipe.asyn(start, swipe_height, stop, swipe_height) + self.input_swipe(start, swipe_height, stop, swipe_height) elif direction == "vertical": swipe_middle = width / 2 swipe_height = height * 2 // 3 - await self.input_swipe.asyn(swipe_middle, swipe_height, swipe_middle, 0) + self.input_swipe(swipe_middle, swipe_height, swipe_middle, 0) else: raise TargetStableError("Invalid swipe direction: {}".format(direction)) - @asyn.asyncf - async def getprop(self, prop=None): - props = AndroidProperties(await self.execute.asyn('getprop')) + def getprop(self, prop=None): + props = AndroidProperties(self.execute('getprop')) if prop: return props[prop] return props - @asyn.asyncf - async def capture_ui_hierarchy(self, filepath): + def capture_ui_hierarchy(self, filepath): on_target_file = self.get_workpath('screen_capture.xml') - try: - await self.execute.asyn('uiautomator dump {}'.format(on_target_file)) - await self.pull.asyn(on_target_file, filepath) - finally: - await self.remove.asyn(on_target_file) + self.execute('uiautomator dump {}'.format(on_target_file)) + self.pull(on_target_file, filepath) + self.remove(on_target_file) parsed_xml = xml.dom.minidom.parse(filepath) with open(filepath, 'w') as f: @@ -2015,31 +1784,26 @@ class AndroidTarget(Target): else: f.write(parsed_xml.toprettyxml().encode('utf-8')) - @asyn.asyncf - async def is_installed(self, name): - return (await super(AndroidTarget, self).is_installed.asyn(name)) or (await self.package_is_installed.asyn(name)) + def is_installed(self, name): + return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name) - @asyn.asyncf - async def package_is_installed(self, package_name): - return package_name in (await self.list_packages.asyn()) + def package_is_installed(self, package_name): + return package_name in self.list_packages() - @asyn.asyncf - async def list_packages(self): - output = await self.execute.asyn('pm list packages') + def list_packages(self): + output = self.execute('pm list packages') output = output.replace('package:', '') return output.split() - @asyn.asyncf - async def get_package_version(self, package): - output = await self.execute.asyn('dumpsys package {}'.format(quote(package))) + def get_package_version(self, package): + output = self.execute('dumpsys package {}'.format(quote(package))) for line in convert_new_lines(output).split('\n'): if 'versionName' in line: return line.split('=', 1)[1] return None - @asyn.asyncf - async def get_package_info(self, package): - output = await self.execute.asyn('pm list packages -f {}'.format(quote(package))) + def get_package_info(self, package): + output = self.execute('pm list packages -f {}'.format(quote(package))) for entry in output.strip().split('\n'): rest, entry_package = entry.rsplit('=', 1) if entry_package != package: @@ -2047,15 +1811,13 @@ class AndroidTarget(Target): _, apk_path = rest.split(':') return installed_package_info(apk_path, entry_package) - @asyn.asyncf - async def get_sdk_version(self): + def get_sdk_version(self): try: - return int(await self.getprop.asyn('ro.build.version.sdk')) + return int(self.getprop('ro.build.version.sdk')) except (ValueError, TypeError): return None - @asyn.asyncf - async def install_apk(self, filepath, timeout=None, replace=False, allow_downgrade=False): # pylint: disable=W0221 + def install_apk(self, filepath, timeout=None, replace=False, allow_downgrade=False): # pylint: disable=W0221 ext = os.path.splitext(filepath)[1].lower() if ext == '.apk': flags = [] @@ -2071,17 +1833,16 @@ class AndroidTarget(Target): timeout=timeout, adb_server=self.adb_server) else: dev_path = self.get_workpath(filepath.rsplit(os.path.sep, 1)[-1]) - await self.push.asyn(quote(filepath), dev_path, timeout=timeout) - result = await self.execute.asyn("pm install {} {}".format(' '.join(flags), quote(dev_path)), timeout=timeout) - await self.remove.asyn(dev_path) + self.push(quote(filepath), dev_path, timeout=timeout) + result = self.execute("pm install {} {}".format(' '.join(flags), quote(dev_path)), timeout=timeout) + self.remove(dev_path) return result else: raise TargetStableError('Can\'t install {}: unsupported format.'.format(filepath)) - @asyn.asyncf - async def grant_package_permission(self, package, permission): + def grant_package_permission(self, package, permission): try: - return await self.execute.asyn('pm grant {} {}'.format(quote(package), quote(permission))) + return self.execute('pm grant {} {}'.format(quote(package), quote(permission))) except TargetStableError as e: if 'is not a changeable permission type' in e.message: pass # Ignore if unchangeable @@ -2094,68 +1855,61 @@ class AndroidTarget(Target): else: raise - @asyn.asyncf - async def refresh_files(self, file_list): + def refresh_files(self, file_list): """ Depending on the android version and root status, determine the appropriate method of forcing a re-index of the mediaserver cache for a given list of files. """ - if self.is_rooted or (await self.get_sdk_version.asyn()) < 24: # MM and below + if self.is_rooted or self.get_sdk_version() < 24: # MM and below common_path = commonprefix(file_list, sep=self.path.sep) - await self.broadcast_media_mounted.asyn(common_path, self.is_rooted) + self.broadcast_media_mounted(common_path, self.is_rooted) else: for f in file_list: - await self.broadcast_media_scan_file.asyn(f) + self.broadcast_media_scan_file(f) - @asyn.asyncf - async def broadcast_media_scan_file(self, filepath): + def broadcast_media_scan_file(self, filepath): """ Force a re-index of the mediaserver cache for the specified file. """ command = 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d {}' - await self.execute.asyn(command.format(quote('file://' + filepath))) + self.execute(command.format(quote('file://' + filepath))) - @asyn.asyncf - async def broadcast_media_mounted(self, dirpath, as_root=False): + def broadcast_media_mounted(self, dirpath, as_root=False): """ Force a re-index of the mediaserver cache for the specified directory. """ command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d {} '\ '-n com.android.providers.media/.MediaScannerReceiver' - await self.execute.asyn(command.format(quote('file://'+dirpath)), as_root=as_root) + self.execute(command.format(quote('file://'+dirpath)), as_root=as_root) - @asyn.asyncf - async def install_executable(self, filepath, with_name=None, timeout=None): + def install_executable(self, filepath, with_name=None, timeout=None): self._ensure_executables_directory_is_writable() executable_name = with_name or os.path.basename(filepath) on_device_file = self.path.join(self.working_directory, executable_name) on_device_executable = self.path.join(self.executables_directory, executable_name) - await self.push.asyn(filepath, on_device_file, timeout=timeout) + self.push(filepath, on_device_file, timeout=timeout) if on_device_file != on_device_executable: - await self.execute.asyn('cp {} {}'.format(quote(on_device_file), quote(on_device_executable)), + self.execute('cp {} {}'.format(quote(on_device_file), quote(on_device_executable)), as_root=self.needs_su, timeout=timeout) - await self.remove.asyn(on_device_file, as_root=self.needs_su) - await self.execute.asyn("chmod 0777 {}".format(quote(on_device_executable)), as_root=self.needs_su) + self.remove(on_device_file, as_root=self.needs_su) + self.execute("chmod 0777 {}".format(quote(on_device_executable)), as_root=self.needs_su) self._installed_binaries[executable_name] = on_device_executable return on_device_executable - @asyn.asyncf - async def uninstall_package(self, package): + def uninstall_package(self, package): if isinstance(self.conn, AdbConnection): adb_command(self.adb_name, "uninstall {}".format(quote(package)), timeout=30, adb_server=self.adb_server) else: - await self.execute.asyn("pm uninstall {}".format(quote(package)), timeout=30) + self.execute("pm uninstall {}".format(quote(package)), timeout=30) - @asyn.asyncf - async def uninstall_executable(self, executable_name): + def uninstall_executable(self, executable_name): on_device_executable = self.path.join(self.executables_directory, executable_name) self._ensure_executables_directory_is_writable() - await self.remove.asyn(on_device_executable, as_root=self.needs_su) + self.remove(on_device_executable, as_root=self.needs_su) - @asyn.asyncf - async def dump_logcat(self, filepath, filter=None, logcat_format=None, append=False, + def dump_logcat(self, filepath, filter=None, logcat_format=None, append=False, timeout=60): # pylint: disable=redefined-builtin op = '>>' if append else '>' filtstr = ' -s {}'.format(quote(filter)) if filter else '' @@ -2167,19 +1921,18 @@ class AndroidTarget(Target): else: dev_path = self.get_workpath('logcat') command = 'logcat {} {} {}'.format(logcat_opts, op, quote(dev_path)) - await self.execute.asyn(command, timeout=timeout) - await self.pull.asyn(dev_path, filepath) - await self.remove.asyn(dev_path) + self.execute(command, timeout=timeout) + self.pull(dev_path, filepath) + self.remove(dev_path) - @asyn.asyncf - async def clear_logcat(self): + def clear_logcat(self): locked = self.clear_logcat_lock.acquire(blocking=False) if locked: try: if isinstance(self.conn, AdbConnection): adb_command(self.adb_name, 'logcat -c', timeout=30, adb_server=self.adb_server) else: - await self.execute.asyn('logcat -c', timeout=30) + self.execute('logcat -c', timeout=30) finally: self.clear_logcat_lock.release() @@ -2194,9 +1947,8 @@ class AndroidTarget(Target): def reboot_bootloader(self, timeout=30): self.conn.reboot_bootloader() - @asyn.asyncf - async def is_screen_on(self): - output = await self.execute.asyn('dumpsys power') + def is_screen_on(self): + output = self.execute('dumpsys power') match = ANDROID_SCREEN_STATE_REGEX.search(output) if match: if 'DOZE' in match.group(1).upper(): @@ -2209,145 +1961,121 @@ class AndroidTarget(Target): else: raise TargetStableError('Could not establish screen state.') - @asyn.asyncf - async def ensure_screen_is_on(self, verify=True): - if not await self.is_screen_on.asyn(): + def ensure_screen_is_on(self, verify=True): + if not self.is_screen_on(): self.execute('input keyevent 26') - if verify and not await self.is_screen_on.asyn(): + if verify and not self.is_screen_on(): raise TargetStableError('Display cannot be turned on.') - @asyn.asyncf - async def ensure_screen_is_on_and_stays(self, verify=True, mode=7): - await self.ensure_screen_is_on.asyn(verify=verify) - await self.set_stay_on_mode.asyn(mode) + def ensure_screen_is_on_and_stays(self, verify=True, mode=7): + self.ensure_screen_is_on(verify=verify) + self.set_stay_on_mode(mode) - @asyn.asyncf - async def ensure_screen_is_off(self, verify=True): + def ensure_screen_is_off(self, verify=True): # Allow 2 attempts to help with cases of ambient display modes # where the first attempt will switch the display fully on. for _ in range(2): - if await self.is_screen_on.asyn(): - await self.execute.asyn('input keyevent 26') + if self.is_screen_on(): + self.execute('input keyevent 26') time.sleep(0.5) - if verify and await self.is_screen_on.asyn(): + if verify and self.is_screen_on(): msg = 'Display cannot be turned off. Is always on display enabled?' raise TargetStableError(msg) - @asyn.asyncf - async def set_auto_brightness(self, auto_brightness): + def set_auto_brightness(self, auto_brightness): cmd = 'settings put system screen_brightness_mode {}' - await self.execute.asyn(cmd.format(int(boolean(auto_brightness)))) + self.execute(cmd.format(int(boolean(auto_brightness)))) - @asyn.asyncf - async def get_auto_brightness(self): + def get_auto_brightness(self): cmd = 'settings get system screen_brightness_mode' - return boolean((await self.execute.asyn(cmd)).strip()) + return boolean(self.execute(cmd).strip()) - @asyn.asyncf - async def set_brightness(self, value): + def set_brightness(self, value): if not 0 <= value <= 255: msg = 'Invalid brightness "{}"; Must be between 0 and 255' raise ValueError(msg.format(value)) - await self.set_auto_brightness.asyn(False) + self.set_auto_brightness(False) cmd = 'settings put system screen_brightness {}' - await self.execute.asyn(cmd.format(int(value))) + self.execute(cmd.format(int(value))) - @asyn.asyncf - async def get_brightness(self): + def get_brightness(self): cmd = 'settings get system screen_brightness' - return integer((await self.execute.asyn(cmd)).strip()) + return integer(self.execute(cmd).strip()) - @asyn.asyncf - async def set_screen_timeout(self, timeout_ms): + def set_screen_timeout(self, timeout_ms): cmd = 'settings put system screen_off_timeout {}' - await self.execute.asyn(cmd.format(int(timeout_ms))) + self.execute(cmd.format(int(timeout_ms))) - @asyn.asyncf - async def get_screen_timeout(self): + def get_screen_timeout(self): cmd = 'settings get system screen_off_timeout' - return int((await self.execute.asyn(cmd)).strip()) + return int(self.execute(cmd).strip()) - @asyn.asyncf - async def get_airplane_mode(self): + def get_airplane_mode(self): cmd = 'settings get global airplane_mode_on' - return boolean((await self.execute.asyn(cmd)).strip()) + return boolean(self.execute(cmd).strip()) - @asyn.asyncf - async def get_stay_on_mode(self): + def get_stay_on_mode(self): cmd = 'settings get global stay_on_while_plugged_in' - return int((await self.execute.asyn(cmd)).strip()) + return int(self.execute(cmd).strip()) - @asyn.asyncf - async def set_airplane_mode(self, mode): - root_required = await self.get_sdk_version.asyn() > 23 + def set_airplane_mode(self, mode): + root_required = self.get_sdk_version() > 23 if root_required and not self.is_rooted: raise TargetStableError('Root is required to toggle airplane mode on Android 7+') mode = int(boolean(mode)) cmd = 'settings put global airplane_mode_on {}' - await self.execute.asyn(cmd.format(mode)) - await self.execute.asyn('am broadcast -a android.intent.action.AIRPLANE_MODE ' + self.execute(cmd.format(mode)) + self.execute('am broadcast -a android.intent.action.AIRPLANE_MODE ' '--ez state {}'.format(mode), as_root=root_required) - @asyn.asyncf - async def get_auto_rotation(self): + def get_auto_rotation(self): cmd = 'settings get system accelerometer_rotation' - return boolean((await self.execute.asyn(cmd)).strip()) + return boolean(self.execute(cmd).strip()) - @asyn.asyncf - async def set_auto_rotation(self, autorotate): + def set_auto_rotation(self, autorotate): cmd = 'settings put system accelerometer_rotation {}' - await self.execute.asyn(cmd.format(int(boolean(autorotate)))) + self.execute(cmd.format(int(boolean(autorotate)))) - @asyn.asyncf - async def set_natural_rotation(self): - await self.set_rotation.asyn(0) + def set_natural_rotation(self): + self.set_rotation(0) - @asyn.asyncf - async def set_left_rotation(self): - await self.set_rotation.asyn(1) + def set_left_rotation(self): + self.set_rotation(1) - @asyn.asyncf - async def set_inverted_rotation(self): - await self.set_rotation.asyn(2) + def set_inverted_rotation(self): + self.set_rotation(2) - @asyn.asyncf - async def set_right_rotation(self): - await self.set_rotation.asyn(3) + def set_right_rotation(self): + self.set_rotation(3) - @asyn.asyncf - async def get_rotation(self): - output = await self.execute.asyn('dumpsys input') + def get_rotation(self): + output = self.execute('dumpsys input') match = ANDROID_SCREEN_ROTATION_REGEX.search(output) if match: return int(match.group('rotation')) else: return None - @asyn.asyncf - async def set_rotation(self, rotation): + def set_rotation(self, rotation): if not 0 <= rotation <= 3: raise ValueError('Rotation value must be between 0 and 3') - await self.set_auto_rotation.asyn(False) + self.set_auto_rotation(False) cmd = 'settings put system user_rotation {}' - await self.execute.asyn(cmd.format(rotation)) + self.execute(cmd.format(rotation)) - @asyn.asyncf - async def set_stay_on_never(self): - await self.set_stay_on_mode.asyn(0) + def set_stay_on_never(self): + self.set_stay_on_mode(0) - @asyn.asyncf - async def set_stay_on_while_powered(self): - await self.set_stay_on_mode.asyn(7) + def set_stay_on_while_powered(self): + self.set_stay_on_mode(7) - @asyn.asyncf - async def set_stay_on_mode(self, mode): + def set_stay_on_mode(self, mode): if not 0 <= mode <= 7: raise ValueError('Screen stay on mode must be between 0 and 7') cmd = 'settings put global stay_on_while_plugged_in {}' - await self.execute.asyn(cmd.format(mode)) + self.execute(cmd.format(mode)) - @asyn.asyncf - async def open_url(self, url, force_new=False): + def open_url(self, url, force_new=False): """ Start a view activity by specifying an URL @@ -2364,11 +2092,10 @@ class AndroidTarget(Target): cmd = cmd + ' -f {}'.format(INTENT_FLAGS['ACTIVITY_NEW_TASK'] | INTENT_FLAGS['ACTIVITY_CLEAR_TASK']) - await self.execute.asyn(cmd.format(quote(url))) + self.execute(cmd.format(quote(url))) - @asyn.asyncf - async def homescreen(self): - await self.execute.asyn('am start -a android.intent.action.MAIN -c android.intent.category.HOME') + def homescreen(self): + self.execute('am start -a android.intent.action.MAIN -c android.intent.category.HOME') def _resolve_paths(self): if self.working_directory is None: @@ -2377,16 +2104,15 @@ class AndroidTarget(Target): if self.executables_directory is None: self.executables_directory = '/data/local/tmp/bin' - @asyn.asyncf - async def _ensure_executables_directory_is_writable(self): + def _ensure_executables_directory_is_writable(self): matched = [] - for entry in await self.list_file_systems.asyn(): + for entry in self.list_file_systems(): if self.executables_directory.rstrip('/').startswith(entry.mount_point): matched.append(entry) if matched: entry = sorted(matched, key=lambda x: len(x.mount_point))[-1] if 'rw' not in entry.options: - await self.execute.asyn('mount -o rw,remount {} {}'.format(quote(entry.device), + self.execute('mount -o rw,remount {} {}'.format(quote(entry.device), quote(entry.mount_point)), as_root=True) else: @@ -2839,7 +2565,6 @@ class LocalLinuxTarget(LinuxTarget): shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=LocalConnection, is_container=False, - max_async=50, ): super(LocalLinuxTarget, self).__init__(connection_settings=connection_settings, platform=platform, @@ -2850,8 +2575,7 @@ class LocalLinuxTarget(LinuxTarget): load_default_modules=load_default_modules, shell_prompt=shell_prompt, conn_cls=conn_cls, - is_container=is_container, - max_async=max_async) + is_container=is_container) def _resolve_paths(self): if self.working_directory is None: @@ -2924,8 +2648,7 @@ class ChromeOsTarget(LinuxTarget): load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, package_data_directory="/data/data", - is_container=False, - max_async=50, + is_container=False ): self.supports_android = None @@ -2951,8 +2674,7 @@ class ChromeOsTarget(LinuxTarget): load_default_modules=load_default_modules, shell_prompt=shell_prompt, conn_cls=SshConnection, - is_container=is_container, - max_async=max_async) + is_container=is_container) # We can't determine if the target supports android until connected to the linux host so # create unconditionally. diff --git a/external/devlib/devlib/utils/asyn.py b/external/devlib/devlib/utils/asyn.py deleted file mode 100644 index 25ce9e6aa8dacbb22ac376af3ba0fd41e426c389..0000000000000000000000000000000000000000 --- a/external/devlib/devlib/utils/asyn.py +++ /dev/null @@ -1,347 +0,0 @@ -# Copyright 2013-2018 ARM Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -""" -Async-related utilities -""" - -import abc -import asyncio -import functools -import itertools -import contextlib -import pathlib -import os.path - -# Allow nesting asyncio loops, which is necessary for: -# * Being able to call the blocking variant of a function from an async -# function for backward compat -# * Critically, run the blocking variant of a function in a Jupyter notebook -# environment, since it also uses asyncio. -# -# Maybe there is still hope for future versions of Python though: -# https://bugs.python.org/issue22239 -import nest_asyncio -nest_asyncio.apply() - - -def create_task(awaitable, name=None): - if isinstance(awaitable, asyncio.Task): - task = awaitable - else: - task = asyncio.create_task(awaitable) - if name is None: - name = getattr(awaitable, '__qualname__', None) - task.name = name - return task - - -class AsyncManager: - def __init__(self): - self.task_tree = dict() - self.resources = dict() - - def track_access(self, access): - """ - Register the given ``access`` to have been handled by the current - async task. - - :param access: Access that were done. - :type access: ConcurrentAccessBase - - This allows :func:`concurrently` to check that concurrent tasks did not - step on each other's toes. - """ - try: - task = asyncio.current_task() - except RuntimeError: - pass - else: - self.resources.setdefault(task, set()).add(access) - - async def concurrently(self, awaitables): - """ - Await concurrently for the given awaitables, and cancel them as soon as - one raises an exception. - """ - awaitables = list(awaitables) - - # Avoid creating asyncio.Tasks when it's not necessary, as it will - # disable a the blocking path optimization of Target._execute_async() - # that uses blocking calls as long as there is only one asyncio.Task - # running on the event loop. - if len(awaitables) == 1: - return [await awaitables[0]] - - tasks = list(map(create_task, awaitables)) - - current_task = asyncio.current_task() - task_tree = self.task_tree - - try: - node = task_tree[current_task] - except KeyError: - is_root_task = True - node = set() - else: - is_root_task = False - task_tree[current_task] = node - - task_tree.update({ - child: set() - for child in tasks - }) - node.update(tasks) - - try: - return await asyncio.gather(*tasks) - except BaseException: - for task in tasks: - task.cancel() - raise - finally: - - def get_children(task): - immediate_children = task_tree[task] - return frozenset( - itertools.chain( - [task], - immediate_children, - itertools.chain.from_iterable( - map(get_children, immediate_children) - ) - ) - ) - - # Get the resources created during the execution of each subtask - # (directly or indirectly) - resources = { - task: frozenset( - itertools.chain.from_iterable( - self.resources.get(child, []) - for child in get_children(task) - ) - ) - for task in tasks - } - for (task1, resources1), (task2, resources2) in itertools.combinations(resources.items(), 2): - for res1, res2 in itertools.product(resources1, resources2): - if issubclass(res2.__class__, res1.__class__) and res1.overlap_with(res2): - raise RuntimeError( - 'Overlapping resources manipulated in concurrent async tasks: {} (task {}) and {} (task {})'.format(res1, task1.name, res2, task2.name) - ) - - if is_root_task: - self.resources.clear() - task_tree.clear() - - async def map_concurrently(self, f, keys): - """ - Similar to :meth:`concurrently`, - but maps the given function ``f`` on the given ``keys``. - - :return: A dictionary with ``keys`` as keys, and function result as - values. - """ - keys = list(keys) - return dict(zip( - keys, - await self.concurrently(map(f, keys)) - )) - - -def compose(*coros): - """ - Compose coroutines, feeding the output of each as the input of the next - one. - - ``await compose(f, g)(x)`` is equivalent to ``await f(await g(x))`` - - .. note:: In Haskell, ``compose f g h`` would be equivalent to ``f <=< g <=< h`` - """ - async def f(*args, **kwargs): - empty_dict = {} - for coro in reversed(coros): - x = coro(*args, **kwargs) - # Allow mixing corountines and regular functions - if asyncio.isfuture(x): - x = await x - args = [x] - kwargs = empty_dict - - return x - return f - - -class _AsyncPolymorphicFunction: - """ - A callable that allows exposing both a synchronous and asynchronous API. - - When called, the blocking synchronous operation is called. The ```asyn`` - attribute gives access to the asynchronous version of the function, and all - the other attribute access will be redirected to the async function. - """ - def __init__(self, asyn, blocking): - self.asyn = asyn - self.blocking = blocking - - def __get__(self, *args, **kwargs): - return self.__class__( - asyn=self.asyn.__get__(*args, **kwargs), - blocking=self.blocking.__get__(*args, **kwargs), - ) - - def __call__(self, *args, **kwargs): - return self.blocking(*args, **kwargs) - - def __getattr__(self, attr): - return getattr(self.asyn, attr) - - -def asyncf(f): - """ - Decorator used to turn a coroutine into a blocking function, with an - optional asynchronous API. - - **Example**:: - - @asyncf - async def foo(x): - await do_some_async_things(x) - return x - - # Blocking call, just as if the function was synchronous, except it may - # use asynchronous code inside, e.g. to do concurrent operations. - foo(42) - - # Asynchronous API, foo.asyn being a corountine - await foo.asyn(42) - - This allows the same implementation to be both used as blocking for ease of - use and backward compatibility, or exposed as a corountine for callers that - can deal with awaitables. - """ - @functools.wraps(f) - def blocking(*args, **kwargs): - # Since run() needs a corountine, make sure we provide one - async def wrapper(): - return await f(*args, **kwargs) - return asyncio.run(wrapper()) - - @functools.wraps(f) - def concurrent(*args, **kwargs): - awaitable = f(*args, **kwargs) - task = create_task(awaitable) - return task - - return _AsyncPolymorphicFunction( - asyn=f, - blocking=blocking, - ) - - -class _AsyncPolymorphicCM: - """ - Wrap an async context manager such that it exposes a synchronous API as - well for backward compatibility. - """ - def __init__(self, async_cm): - self.cm = async_cm - - def __aenter__(self, *args, **kwargs): - return self.cm.__aenter__(*args, **kwargs) - - def __aexit__(self, *args, **kwargs): - return self.cm.__aexit__(*args, **kwargs) - - def __enter__(self, *args, **kwargs): - return asyncio.run(self.cm.__aenter__(*args, **kwargs)) - - def __exit__(self, *args, **kwargs): - return asyncio.run(self.cm.__aexit__(*args, **kwargs)) - - -def asynccontextmanager(f): - """ - Same as :func:`contextlib.asynccontextmanager` except that it can also be - used with a regular ``with`` statement for backward compatibility. - """ - f = contextlib.asynccontextmanager(f) - - @functools.wraps(f) - def wrapper(*args, **kwargs): - cm = f(*args, **kwargs) - return _AsyncPolymorphicCM(cm) - - return wrapper - - -class ConcurrentAccessBase(abc.ABC): - """ - Abstract Base Class for resources tracked by :func:`concurrently`. - """ - @abc.abstractmethod - def overlap_with(self, other): - """ - Return ``True`` if the resource overlaps with the given one. - - :param other: Resources that should not overlap with ``self``. - :type other: devlib.utils.asym.ConcurrentAccessBase - - .. note:: It is guaranteed that ``other`` will be a subclass of our - class. - """ - -class PathAccess(ConcurrentAccessBase): - """ - Concurrent resource representing a file access. - - :param namespace: Identifier of the namespace of the path. One of "target" or "host". - :type namespace: str - - :param path: Normalized path to the file. - :type path: str - - :param mode: Opening mode of the file. Can be ``"r"`` for read and ``"w"`` - for writing. - :type mode: str - """ - def __init__(self, namespace, path, mode): - assert namespace in ('host', 'target') - self.namespace = namespace - assert mode in ('r', 'w') - self.mode = mode - self.path = os.path.abspath(path) if namespace == 'host' else os.path.normpath(path) - - def overlap_with(self, other): - path1 = pathlib.Path(self.path).resolve() - path2 = pathlib.Path(other.path).resolve() - return ( - self.namespace == other.namespace and - 'w' in (self.mode, other.mode) and - ( - path1 == path2 or - path1 in path2.parents or - path2 in path1.parents - ) - ) - - def __str__(self): - mode = { - 'r': 'read', - 'w': 'write', - }[self.mode] - return '{} ({})'.format(self.path, mode) diff --git a/external/devlib/devlib/utils/misc.py b/external/devlib/devlib/utils/misc.py index eb9dfd375961ecf7f314a77951d96b70338242e1..de4944b3c614917eeb7a258be295014c189137ee 100644 --- a/external/devlib/devlib/utils/misc.py +++ b/external/devlib/devlib/utils/misc.py @@ -748,7 +748,8 @@ def batch_contextmanager(f, kwargs_list): yield -class nullcontext: +@contextmanager +def nullcontext(enter_result=None): """ Backport of Python 3.7 ``contextlib.nullcontext`` @@ -760,20 +761,7 @@ class nullcontext: statement, or `None` if nothing is specified. :type enter_result: object """ - def __init__(self, enter_result=None): - self.enter_result = enter_result - - def __enter__(self): - return self.enter_result - - async def __aenter__(self): - return self.enter_result - - def __exit__(*_): - return - - async def __aexit__(*_): - return + yield enter_result class tls_property: diff --git a/external/devlib/devlib/utils/version.py b/external/devlib/devlib/utils/version.py index 721a31bc3c6e2dc218bd33eff787905f27034200..a44498b6574b973990b479fd0d913941ae58a589 100644 --- a/external/devlib/devlib/utils/version.py +++ b/external/devlib/devlib/utils/version.py @@ -21,7 +21,7 @@ from subprocess import Popen, PIPE VersionTuple = namedtuple('Version', ['major', 'minor', 'revision', 'dev']) -version = VersionTuple(1, 4, 0, 'dev1') +version = VersionTuple(1, 4, 0, 'dev2') def get_devlib_version(): diff --git a/external/devlib/doc/target.rst b/external/devlib/doc/target.rst index 17b2bbd3a9f8bcc5ab09667700393bb7ea2f7191..f17cfe4e13498914667046cae8aa3a4c07dd2c03 100644 --- a/external/devlib/doc/target.rst +++ b/external/devlib/doc/target.rst @@ -3,7 +3,7 @@ Target ====== -.. class:: Target(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=None, max_async=50) +.. class:: Target(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=None) :class:`~devlib.target.Target` is the primary interface to the remote device. All interactions with the device are performed via a @@ -76,9 +76,6 @@ Target :param conn_cls: This is the type of connection that will be used to communicate with the device. - :param max_async: Maximum number of opened connections to the target used to - issue non-blocking commands when using the async API. - .. attribute:: Target.core_names This is a list containing names of CPU cores on the target, in the order in @@ -609,7 +606,7 @@ Target Linux Target ------------ -.. class:: LinuxTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=SshConnection, is_container=False, max_async=50) +.. class:: LinuxTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=SshConnection, is_container=False,) :class:`LinuxTarget` is a subclass of :class:`~devlib.target.Target` with customisations specific to a device running linux. @@ -618,7 +615,7 @@ Linux Target Local Linux Target ------------------ -.. class:: LocalLinuxTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=SshConnection, is_container=False, max_async=50) +.. class:: LocalLinuxTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=SshConnection, is_container=False,) :class:`LocalLinuxTarget` is a subclass of :class:`~devlib.target.LinuxTarget` with customisations specific to using @@ -628,7 +625,7 @@ Local Linux Target Android Target --------------- -.. class:: AndroidTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=AdbConnection, package_data_directory="/data/data", max_async=50) +.. class:: AndroidTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=AdbConnection, package_data_directory="/data/data") :class:`AndroidTarget` is a subclass of :class:`~devlib.target.Target` with additional features specific to a device running Android. @@ -776,7 +773,7 @@ Android Target ChromeOS Target --------------- -.. class:: ChromeOsTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, android_working_directory=None, android_executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, package_data_directory="/data/data", max_async=50) +.. class:: ChromeOsTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, android_working_directory=None, android_executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, package_data_directory="/data/data") :class:`ChromeOsTarget` is a subclass of :class:`LinuxTarget` with additional features specific to a device running ChromeOS for example, diff --git a/external/devlib/setup.py b/external/devlib/setup.py index 1bb7bd104048332248d38cbb1ba12fb6b690676d..0365683e69ecc97a0f45702e7a748bacaa147cbd 100644 --- a/external/devlib/setup.py +++ b/external/devlib/setup.py @@ -82,7 +82,6 @@ params = dict( url='https://github.com/ARM-software/devlib', license='Apache v2', maintainer='ARM Ltd.', - python_requires='>= 3.7', install_requires=[ 'python-dateutil', # converting between UTC and local time. 'pexpect>=3.3', # Send/recieve to/from device @@ -91,10 +90,13 @@ params = dict( 'scp', # SSH connection file transfers 'wrapt', # Basic for construction of decorator functions 'future', # Python 2-3 compatibility - 'numpy', - 'pandas', + 'enum34;python_version<"3.4"', # Enums for Python < 3.4 + 'contextlib2;python_version<"3.0"', # Python 3 contextlib backport for Python 2 + 'numpy<=1.16.4; python_version<"3"', + 'numpy; python_version>="3"', + 'pandas<=0.24.2; python_version<"3"', + 'pandas; python_version>"3"', 'lxml', # More robust xml parsing - 'nest_asyncio', # Allows running nested asyncio loops ], extras_require={ 'daq': ['daqpower>=2'], diff --git a/external/subtrees.conf b/external/subtrees.conf index f266ac209c29bb69553968bc83be4a1583c0b774..b734e4dcf23fc918be3f25b7ab241a384449ffb9 100644 --- a/external/subtrees.conf +++ b/external/subtrees.conf @@ -1,12 +1,12 @@ [devlib] path = external/devlib -# url = https://github.com/ARM-Software/devlib.git -# ref = master +url = https://github.com/ARM-Software/devlib.git +ref = master # # See external/devlib.manifest.yaml for instructions on how to build this # branch: -url = https://github.com/douglas-raillard-arm/devlib.git -ref = lisa +# url = https://github.com/douglas-raillard-arm/devlib.git +# ref = lisa [workload-automation] path = external/workload-automation