blob: 9d274a2e09703e03aa815b544d0615c2a0cd1ad0 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001import os
2import re
3import time
4import logging
5import posixpath
6import subprocess
7import tempfile
8import threading
9from collections import namedtuple
10
11from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
12from devlib.module import get_module
13from devlib.platform import Platform
14from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError
15from devlib.utils.ssh import SshConnection
16from devlib.utils.android import AdbConnection, AndroidProperties, adb_command, adb_disconnect
17from devlib.utils.misc import memoized, isiterable, convert_new_lines, merge_lists
18from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list, escape_double_quotes
19from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string
20
21
Sebastian Goscik040daab2016-02-23 10:27:45 +000022FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)')
Sebastian Goscik1890db72016-02-15 14:43:30 +000023ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)',
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010024 re.IGNORECASE)
25ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)'
26 r'\s+(?P<width>\d+)x(?P<height>\d+)')
27DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root)@.*:/\S* [#$] ',
28 re.MULTILINE)
29
30
31class Target(object):
32
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010033 path = None
34 os = None
35
36 default_modules = [
37 'hotplug',
38 'cpufreq',
39 'cpuidle',
40 'cgroups',
41 'hwmon',
42 ]
43
44 @property
45 def core_names(self):
46 return self.platform.core_names
47
48 @property
49 def core_clusters(self):
50 return self.platform.core_clusters
51
52 @property
53 def big_core(self):
54 return self.platform.big_core
55
56 @property
57 def little_core(self):
58 return self.platform.little_core
59
60 @property
61 def is_connected(self):
62 return self.conn is not None
63
64 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010065 def connected_as_root(self):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +000066 if self._connected_as_root is None:
67 result = self.execute('id')
68 self._connected_as_root = 'uid=0(' in result
69 return self._connected_as_root
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010070
71 @property
72 @memoized
73 def is_rooted(self):
74 if self.connected_as_root:
75 return True
76 try:
77 self.execute('ls /', timeout=2, as_root=True)
78 return True
79 except (TargetError, TimeoutError):
80 return False
81
82 @property
83 @memoized
Javi Merino16d87c62016-06-23 14:55:19 +010084 def needs_su(self):
85 return not self.connected_as_root and self.is_rooted
86
87 @property
88 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010089 def kernel_version(self):
Sebastian Goscikbdbf4742016-02-24 14:26:18 +000090 return KernelVersion(self.execute('{} uname -r -v'.format(self.busybox)).strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010091
92 @property
93 def os_version(self): # pylint: disable=no-self-use
94 return {}
95
96 @property
97 def abi(self): # pylint: disable=no-self-use
98 return None
99
100 @property
101 @memoized
102 def cpuinfo(self):
103 return Cpuinfo(self.execute('cat /proc/cpuinfo'))
104
105 @property
106 @memoized
107 def number_of_cpus(self):
108 num_cpus = 0
109 corere = re.compile(r'^\s*cpu\d+\s*$')
110 output = self.execute('ls /sys/devices/system/cpu')
111 for entry in output.split():
112 if corere.match(entry):
113 num_cpus += 1
114 return num_cpus
115
116 @property
117 @memoized
118 def config(self):
119 try:
120 return KernelConfig(self.execute('zcat /proc/config.gz'))
121 except TargetError:
122 for path in ['/boot/config', '/boot/config-$(uname -r)']:
123 try:
124 return KernelConfig(self.execute('cat {}'.format(path)))
125 except TargetError:
126 pass
127 return KernelConfig('')
128
129 @property
130 @memoized
131 def user(self):
132 return self.getenv('USER')
133
134 @property
135 def conn(self):
136 if self._connections:
137 tid = id(threading.current_thread())
138 if tid not in self._connections:
139 self._connections[tid] = self.get_connection()
140 return self._connections[tid]
141 else:
142 return None
143
144 def __init__(self,
145 connection_settings=None,
146 platform=None,
147 working_directory=None,
148 executables_directory=None,
149 connect=True,
150 modules=None,
151 load_default_modules=True,
152 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000153 conn_cls=None,
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100154 ):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +0000155 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100156 self.connection_settings = connection_settings or {}
Anouk Van Laer21f40032017-01-31 13:11:03 +0000157 # Set self.platform: either it's given directly (by platform argument)
158 # or it's given in the connection_settings argument
159 # If neither, create default Platform()
160 if platform is None:
161 self.platform = self.connection_settings.get('platform', Platform())
162 else:
163 self.platform = platform
164 # Check if the user hasn't given two different platforms
165 if 'platform' in self.connection_settings:
166 if connection_settings['platform'] is not platform:
167 raise TargetError('Platform specified in connection_settings '
168 '({}) differs from that directly passed '
169 '({})!)'
170 .format(connection_settings['platform'],
171 self.platform))
172 self.connection_settings['platform'] = self.platform
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100173 self.working_directory = working_directory
174 self.executables_directory = executables_directory
175 self.modules = modules or []
176 self.load_default_modules = load_default_modules
177 self.shell_prompt = shell_prompt
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000178 self.conn_cls = conn_cls
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100179 self.logger = logging.getLogger(self.__class__.__name__)
180 self._installed_binaries = {}
181 self._installed_modules = {}
182 self._cache = {}
183 self._connections = {}
184 self.busybox = None
185
186 if load_default_modules:
187 module_lists = [self.default_modules]
188 else:
189 module_lists = []
190 module_lists += [self.modules, self.platform.modules]
191 self.modules = merge_lists(*module_lists, duplicates='first')
192 self._update_modules('early')
193 if connect:
194 self.connect()
195
196 # connection and initialization
197
198 def connect(self, timeout=None):
199 self.platform.init_target_connection(self)
200 tid = id(threading.current_thread())
201 self._connections[tid] = self.get_connection(timeout=timeout)
Sergei Trofimov961f9572015-11-18 17:32:26 +0000202 self._resolve_paths()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100203 self.busybox = self.get_installed('busybox')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100204 self.platform.update_from_target(self)
Patrick Bellasib83e5182015-10-12 12:37:11 +0100205 self._update_modules('connected')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100206 if self.platform.big_core and self.load_default_modules:
207 self._install_module(get_module('bl'))
208
209 def disconnect(self):
210 for conn in self._connections.itervalues():
211 conn.close()
212 self._connections = {}
213
214 def get_connection(self, timeout=None):
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000215 if self.conn_cls == None:
216 raise ValueError('Connection class not specified on Target creation.')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100217 return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
218
219 def setup(self, executables=None):
220 self.execute('mkdir -p {}'.format(self.working_directory))
221 self.execute('mkdir -p {}'.format(self.executables_directory))
222 self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox'))
Patrick Bellasif2eac512015-11-27 16:35:57 +0000223
224 # Setup shutils script for the target
225 shutils_ifile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils.in')
226 shutils_ofile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils')
227 shell_path = '/bin/sh'
228 if self.os == 'android':
229 shell_path = '/system/bin/sh'
230 with open(shutils_ifile) as fh:
231 lines = fh.readlines()
232 with open(shutils_ofile, 'w') as ofile:
233 for line in lines:
234 line = line.replace("__DEVLIB_SHELL__", shell_path)
235 line = line.replace("__DEVLIB_BUSYBOX__", self.busybox)
236 ofile.write(line)
237 self.shutils = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils'))
238
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100239 for host_exe in (executables or []): # pylint: disable=superfluous-parens
240 self.install(host_exe)
241
Anouk Van Laer21f40032017-01-31 13:11:03 +0000242 # Check for platform dependent setup procedures
243 self.platform.setup(self)
244
Patrick Bellasic4e46b72016-05-13 18:15:51 +0100245 # Initialize modules which requires Buxybox (e.g. shutil dependent tasks)
246 self._update_modules('setup')
247
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100248 def reboot(self, hard=False, connect=True, timeout=180):
249 if hard:
250 if not self.has('hard_reset'):
251 raise TargetError('Hard reset not supported for this target.')
252 self.hard_reset() # pylint: disable=no-member
253 else:
254 if not self.is_connected:
255 message = 'Cannot reboot target becuase it is disconnected. ' +\
256 'Either connect() first, or specify hard=True ' +\
257 '(in which case, a hard_reset module must be installed)'
258 raise TargetError(message)
259 self.reset()
Sergei Trofimovebe3a8a2016-02-25 10:28:45 +0000260 # Wait a fixed delay before starting polling to give the target time to
261 # shut down, otherwise, might create the connection while it's still shutting
262 # down resulting in subsequenct connection failing.
263 self.logger.debug('Waiting for target to power down...')
264 reset_delay = 20
265 time.sleep(reset_delay)
266 timeout = max(timeout - reset_delay, 10)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100267 if self.has('boot'):
268 self.boot() # pylint: disable=no-member
269 if connect:
270 self.connect(timeout=timeout)
271
272 # file transfer
273
274 def push(self, source, dest, timeout=None):
275 return self.conn.push(source, dest, timeout=timeout)
276
277 def pull(self, source, dest, timeout=None):
278 return self.conn.pull(source, dest, timeout=timeout)
279
280 # execution
281
Patrick Bellasif2eac512015-11-27 16:35:57 +0000282 def _execute_util(self, command, timeout=None, check_exit_code=True, as_root=False):
283 command = '{} {}'.format(self.shutils, command)
284 return self.conn.execute(command, timeout, check_exit_code, as_root)
285
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100286 def execute(self, command, timeout=None, check_exit_code=True, as_root=False):
287 return self.conn.execute(command, timeout, check_exit_code, as_root)
288
289 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
290 return self.conn.background(command, stdout, stderr, as_root)
291
292 def invoke(self, binary, args=None, in_directory=None, on_cpus=None,
293 as_root=False, timeout=30):
294 """
295 Executes the specified binary under the specified conditions.
296
297 :binary: binary to execute. Must be present and executable on the device.
298 :args: arguments to be passed to the binary. The can be either a list or
299 a string.
300 :in_directory: execute the binary in the specified directory. This must
301 be an absolute path.
302 :on_cpus: taskset the binary to these CPUs. This may be a single ``int`` (in which
303 case, it will be interpreted as the mask), a list of ``ints``, in which
304 case this will be interpreted as the list of cpus, or string, which
305 will be interpreted as a comma-separated list of cpu ranges, e.g.
306 ``"0,4-7"``.
307 :as_root: Specify whether the command should be run as root
308 :timeout: If the invocation does not terminate within this number of seconds,
309 a ``TimeoutError`` exception will be raised. Set to ``None`` if the
310 invocation should not timeout.
311
Brendan Jackman27f545f2016-11-15 16:58:57 +0000312 :returns: output of command.
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100313 """
314 command = binary
315 if args:
316 if isiterable(args):
317 args = ' '.join(args)
318 command = '{} {}'.format(command, args)
319 if on_cpus:
320 on_cpus = bitmask(on_cpus)
321 command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
322 if in_directory:
323 command = 'cd {} && {}'.format(in_directory, command)
324 return self.execute(command, as_root=as_root, timeout=timeout)
325
326 def kick_off(self, command, as_root=False):
327 raise NotImplementedError()
328
329 # sysfs interaction
330
331 def read_value(self, path, kind=None):
Javi Merino16d87c62016-06-23 14:55:19 +0100332 output = self.execute('cat \'{}\''.format(path), as_root=self.needs_su).strip() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100333 if kind:
334 return kind(output)
335 else:
336 return output
337
338 def read_int(self, path):
339 return self.read_value(path, kind=integer)
340
341 def read_bool(self, path):
342 return self.read_value(path, kind=boolean)
343
344 def write_value(self, path, value, verify=True):
345 value = str(value)
346 self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True)
347 if verify:
348 output = self.read_value(path)
349 if not output == value:
350 message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output)
351 raise TargetError(message)
352
353 def reset(self):
354 try:
Javi Merino16d87c62016-06-23 14:55:19 +0100355 self.execute('reboot', as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100356 except (TargetError, TimeoutError, subprocess.CalledProcessError):
357 # on some targets "reboot" doesn't return gracefully
358 pass
359
360 def check_responsive(self):
361 try:
362 self.conn.execute('ls /', timeout=5)
363 except (TimeoutError, subprocess.CalledProcessError):
364 raise TargetNotRespondingError(self.conn.name)
365
366 # process management
367
368 def kill(self, pid, signal=None, as_root=False):
369 signal_string = '-s {}'.format(signal) if signal else ''
370 self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
371
372 def killall(self, process_name, signal=None, as_root=False):
373 for pid in self.get_pids_of(process_name):
Sergei Trofimov6351a3b2017-01-30 11:14:36 +0000374 try:
375 self.kill(pid, signal=signal, as_root=as_root)
376 except TargetError:
377 pass
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100378
379 def get_pids_of(self, process_name):
380 raise NotImplementedError()
381
382 def ps(self, **kwargs):
383 raise NotImplementedError()
384
385 # files
386
387 def file_exists(self, filepath):
388 command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'
Brendan Jackmanc1b51522016-11-23 13:44:00 +0000389 output = self.execute(command.format(filepath), as_root=self.is_rooted)
390 return boolean(output.strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100391
Sebastian Goscik33603c62016-02-15 15:07:19 +0000392 def directory_exists(self, filepath):
393 output = self.execute('if [ -d \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
394 # output from ssh my contain part of the expression in the buffer,
395 # split out everything except the last word.
396 return boolean(output.split()[-1]) # pylint: disable=maybe-no-member
397
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100398 def list_file_systems(self):
399 output = self.execute('mount')
400 fstab = []
401 for line in output.split('\n'):
402 line = line.strip()
403 if not line:
404 continue
405 match = FSTAB_ENTRY_REGEX.search(line)
406 if match:
407 fstab.append(FstabEntry(match.group(1), match.group(2),
408 match.group(3), match.group(4),
409 None, None))
410 else: # assume pre-M Android
411 fstab.append(FstabEntry(*line.split()))
412 return fstab
413
414 def list_directory(self, path, as_root=False):
415 raise NotImplementedError()
416
417 def get_workpath(self, name):
418 return self.path.join(self.working_directory, name)
419
420 def tempfile(self, prefix='', suffix=''):
421 names = tempfile._get_candidate_names() # pylint: disable=W0212
422 for _ in xrange(tempfile.TMP_MAX):
423 name = names.next()
424 path = self.get_workpath(prefix + name + suffix)
425 if not self.file_exists(path):
426 return path
427 raise IOError('No usable temporary filename found')
428
429 def remove(self, path, as_root=False):
430 self.execute('rm -rf {}'.format(path), as_root=as_root)
431
432 # misc
433 def core_cpus(self, core):
434 return [i for i, c in enumerate(self.core_names) if c == core]
435
436 def list_online_cpus(self, core=None):
437 path = self.path.join('/sys/devices/system/cpu/online')
438 output = self.read_value(path)
439 all_online = ranges_to_list(output)
440 if core:
441 cpus = self.core_cpus(core)
442 if not cpus:
443 raise ValueError(core)
444 return [o for o in all_online if o in cpus]
445 else:
446 return all_online
447
448 def list_offline_cpus(self):
449 online = self.list_online_cpus()
450 return [c for c in xrange(self.number_of_cpus)
451 if c not in online]
452
453 def getenv(self, variable):
454 return self.execute('echo ${}'.format(variable)).rstrip('\r\n')
455
456 def capture_screen(self, filepath):
457 raise NotImplementedError()
458
459 def install(self, filepath, timeout=None, with_name=None):
460 raise NotImplementedError()
461
462 def uninstall(self, name):
463 raise NotImplementedError()
464
Sebastian Goscik84151f92016-02-15 15:09:27 +0000465 def get_installed(self, name, search_system_binaries=True):
466 # Check user installed binaries first
Sergei Trofimovb5324532015-11-18 18:07:47 +0000467 if self.file_exists(self.executables_directory):
468 if name in self.list_directory(self.executables_directory):
469 return self.path.join(self.executables_directory, name)
Sebastian Goscik84151f92016-02-15 15:09:27 +0000470 # Fall back to binaries in PATH
471 if search_system_binaries:
472 for path in self.getenv('PATH').split(self.path.pathsep):
473 try:
474 if name in self.list_directory(path):
475 return self.path.join(path, name)
476 except TargetError:
477 pass # directory does not exist or no executable premssions
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100478
479 which = get_installed
480
Sebastian Goscikbe8f9722016-02-15 15:11:38 +0000481 def install_if_needed(self, host_path, search_system_binaries=True):
482
483 binary_path = self.get_installed(os.path.split(host_path)[1],
484 search_system_binaries=search_system_binaries)
485 if not binary_path:
486 binary_path = self.install(host_path)
487 return binary_path
488
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100489 def is_installed(self, name):
490 return bool(self.get_installed(name))
491
492 def bin(self, name):
493 return self._installed_binaries.get(name, name)
494
495 def has(self, modname):
496 return hasattr(self, identifier(modname))
497
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000498 def lsmod(self):
499 lines = self.execute('lsmod').splitlines()
500 entries = []
501 for line in lines[1:]: # first line is the header
502 if not line.strip():
503 continue
504 parts = line.split()
505 name = parts[0]
506 size = int(parts[1])
507 use_count = int(parts[2])
508 if len(parts) > 3:
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000509 used_by = ''.join(parts[3:]).split(',')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000510 else:
511 used_by = []
512 entries.append(LsmodEntry(name, size, use_count, used_by))
513 return entries
514
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000515 def insmod(self, path):
516 target_path = self.get_workpath(os.path.basename(path))
517 self.push(path, target_path)
518 self.execute('insmod {}'.format(target_path), as_root=True)
519
Sergei Trofimovbaaa67b2016-07-14 11:00:24 +0100520
521 def extract(self, path, dest=None):
522 """
523 Extact the specified on-target file. The extraction method to be used
524 (unzip, gunzip, bunzip2, or tar) will be based on the file's extension.
525 If ``dest`` is specified, it must be an existing directory on target;
526 the extracted contents will be placed there.
527
528 Note that, depending on the archive file format (and therfore the
529 extraction method used), the original archive file may or may not exist
530 after the extraction.
531
532 The return value is the path to the extracted contents. In case of
533 gunzip and bunzip2, this will be path to the extracted file; for tar
534 and uzip, this will be the directory with the extracted file(s)
535 (``dest`` if it was specified otherwise, the directory that cotained
536 the archive).
537
538 """
539 for ending in ['.tar.gz', '.tar.bz', '.tar.bz2',
540 '.tgz', '.tbz', '.tbz2']:
541 if path.endswith(ending):
542 return self._extract_archive(path, 'tar xf {} -C {}', dest)
543
544 ext = self.path.splitext(path)[1]
545 if ext in ['.bz', '.bz2']:
546 return self._extract_file(path, 'bunzip2 -f {}', dest)
547 elif ext == '.gz':
548 return self._extract_file(path, 'gunzip -f {}', dest)
549 elif ext == '.zip':
550 return self._extract_archive(path, 'unzip {} -d {}', dest)
551 else:
552 raise ValueError('Unknown compression format: {}'.format(ext))
553
554 # internal methods
555
556 def _extract_archive(self, path, cmd, dest=None):
557 cmd = '{} ' + cmd # busybox
558 if dest:
559 extracted = dest
560 else:
561 extracted = self.path.dirname(path)
562 cmdtext = cmd.format(self.busybox, path, extracted)
563 self.execute(cmdtext)
564 return extracted
565
566 def _extract_file(self, path, cmd, dest=None):
567 cmd = '{} ' + cmd # busybox
568 cmdtext = cmd.format(self.busybox, path)
569 self.execute(cmdtext)
570 extracted = self.path.splitext(path)[0]
571 if dest:
572 self.execute('mv -f {} {}'.format(extracted, dest))
573 if dest.endswith('/'):
574 extracted = self.path.join(dest, self.path.basename(extracted))
575 else:
576 extracted = dest
577 return extracted
578
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100579 def _update_modules(self, stage):
580 for mod in self.modules:
581 if isinstance(mod, dict):
582 mod, params = mod.items()[0]
583 else:
584 params = {}
585 mod = get_module(mod)
586 if not mod.stage == stage:
587 continue
588 if mod.probe(self):
589 self._install_module(mod, **params)
590 else:
Javi Merinod0c71fb2016-01-18 12:27:48 +0000591 msg = 'Module {} is not supported by the target'.format(mod.name)
592 if self.load_default_modules:
593 self.logger.debug(msg)
594 else:
595 self.logger.warning(msg)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100596
597 def _install_module(self, mod, **params):
598 if mod.name not in self._installed_modules:
599 self.logger.debug('Installing module {}'.format(mod.name))
600 mod.install(self, **params)
601 self._installed_modules[mod.name] = mod
602 else:
603 self.logger.debug('Module {} is already installed.'.format(mod.name))
604
Sergei Trofimov961f9572015-11-18 17:32:26 +0000605 def _resolve_paths(self):
606 raise NotImplementedError()
607
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100608
609class LinuxTarget(Target):
610
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100611 path = posixpath
612 os = 'linux'
613
614 @property
615 @memoized
616 def abi(self):
Sebastian Goscik5880f6e2016-06-16 13:31:53 +0100617 value = self.execute('uname -m').strip()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100618 for abi, architectures in ABI_MAP.iteritems():
619 if value in architectures:
620 result = abi
621 break
622 else:
623 result = value
624 return result
625
626 @property
627 @memoized
628 def os_version(self):
629 os_version = {}
630 try:
631 command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
632 version_files = self.execute(command, check_exit_code=False).strip().split()
633 for vf in version_files:
634 name = self.path.basename(vf)
635 output = self.read_value(vf)
636 os_version[name] = output.strip().replace('\n', ' ')
637 except TargetError:
638 raise
639 return os_version
640
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000641 @property
642 @memoized
643 # There is currently no better way to do this cross platform.
644 # ARM does not have dmidecode
645 def model(self):
646 if self.file_exists("/proc/device-tree/model"):
647 raw_model = self.execute("cat /proc/device-tree/model")
648 return '_'.join(raw_model.split()[:2])
649 return None
650
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000651 def __init__(self,
652 connection_settings=None,
653 platform=None,
654 working_directory=None,
655 executables_directory=None,
656 connect=True,
657 modules=None,
658 load_default_modules=True,
659 shell_prompt=DEFAULT_SHELL_PROMPT,
660 conn_cls=SshConnection,
661 ):
662 super(LinuxTarget, self).__init__(connection_settings=connection_settings,
663 platform=platform,
664 working_directory=working_directory,
665 executables_directory=executables_directory,
666 connect=connect,
667 modules=modules,
668 load_default_modules=load_default_modules,
669 shell_prompt=shell_prompt,
670 conn_cls=conn_cls)
671
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100672 def connect(self, timeout=None):
673 super(LinuxTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100674
675 def kick_off(self, command, as_root=False):
676 command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
677 return self.conn.execute(command, as_root=as_root)
678
679 def get_pids_of(self, process_name):
680 """Returns a list of PIDs of all processes with the specified name."""
681 # result should be a column of PIDs with the first row as "PID" header
682 result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA
683 check_exit_code=False).strip().split()
684 if len(result) >= 2: # at least one row besides the header
685 return map(int, result[1:])
686 else:
687 return []
688
689 def ps(self, **kwargs):
690 command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
691 lines = iter(convert_new_lines(self.execute(command)).split('\n'))
692 lines.next() # header
693
694 result = []
695 for line in lines:
696 parts = re.split(r'\s+', line, maxsplit=8)
697 if parts and parts != ['']:
698 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
699
700 if not kwargs:
701 return result
702 else:
703 filtered_result = []
704 for entry in result:
705 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
706 filtered_result.append(entry)
707 return filtered_result
708
709 def list_directory(self, path, as_root=False):
710 contents = self.execute('ls -1 {}'.format(path), as_root=as_root)
711 return [x.strip() for x in contents.split('\n') if x.strip()]
712
713 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
714 destpath = self.path.join(self.executables_directory,
715 with_name and with_name or self.path.basename(filepath))
716 self.push(filepath, destpath)
717 self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
718 self._installed_binaries[self.path.basename(destpath)] = destpath
719 return destpath
720
721 def uninstall(self, name):
722 path = self.path.join(self.executables_directory, name)
723 self.remove(path)
724
725 def capture_screen(self, filepath):
726 if not self.is_installed('scrot'):
727 self.logger.debug('Could not take screenshot as scrot is not installed.')
728 return
729 try:
730
731 tmpfile = self.tempfile()
732 self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile))
733 self.pull(tmpfile, filepath)
734 self.remove(tmpfile)
735 except TargetError as e:
736 if "Can't open X dispay." not in e.message:
737 raise e
738 message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
739 self.logger.debug('Could not take screenshot: {}'.format(message))
740
Sergei Trofimov961f9572015-11-18 17:32:26 +0000741 def _resolve_paths(self):
742 if self.working_directory is None:
743 if self.connected_as_root:
744 self.working_directory = '/root/devlib-target'
745 else:
746 self.working_directory = '/home/{}/devlib-target'.format(self.user)
747 if self.executables_directory is None:
748 self.executables_directory = self.path.join(self.working_directory, 'bin')
749
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100750
751class AndroidTarget(Target):
752
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100753 path = posixpath
754 os = 'android'
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100755 ls_command = ''
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100756
757 @property
758 @memoized
759 def abi(self):
760 return self.getprop()['ro.product.cpu.abi'].split('-')[0]
761
762 @property
763 @memoized
764 def os_version(self):
765 os_version = {}
766 for k, v in self.getprop().iteritems():
767 if k.startswith('ro.build.version'):
768 part = k.split('.')[-1]
769 os_version[part] = v
770 return os_version
771
772 @property
773 def adb_name(self):
774 return self.conn.device
775
776 @property
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000777 @memoized
Sebastian Goscikcafeb812016-02-15 15:17:32 +0000778 def android_id(self):
779 """
780 Get the device's ANDROID_ID. Which is
781
782 "A 64-bit number (as a hex string) that is randomly generated when the user
783 first sets up the device and should remain constant for the lifetime of the
784 user's device."
785
786 .. note:: This will get reset on userdata erasure.
787
788 """
789 output = self.execute('content query --uri content://settings/secure --projection value --where "name=\'android_id\'"').strip()
790 return output.split('value=')[-1]
791
792 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100793 @memoized
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000794 def model(self):
795 try:
796 return self.getprop(prop='ro.product.device')
797 except KeyError:
798 return None
799
800 @property
801 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100802 def screen_resolution(self):
803 output = self.execute('dumpsys window')
804 match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output)
805 if match:
806 return (int(match.group('width')),
807 int(match.group('height')))
808 else:
809 return (0, 0)
810
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000811 def __init__(self,
812 connection_settings=None,
813 platform=None,
814 working_directory=None,
815 executables_directory=None,
816 connect=True,
817 modules=None,
818 load_default_modules=True,
819 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000820 conn_cls=AdbConnection,
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000821 package_data_directory="/data/data",
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000822 ):
823 super(AndroidTarget, self).__init__(connection_settings=connection_settings,
824 platform=platform,
825 working_directory=working_directory,
826 executables_directory=executables_directory,
827 connect=connect,
828 modules=modules,
829 load_default_modules=load_default_modules,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000830 shell_prompt=shell_prompt,
831 conn_cls=conn_cls)
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000832 self.package_data_directory = package_data_directory
833
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100834 def reset(self, fastboot=False): # pylint: disable=arguments-differ
835 try:
836 self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
Javi Merino16d87c62016-06-23 14:55:19 +0100837 as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100838 except (TargetError, TimeoutError, subprocess.CalledProcessError):
839 # on some targets "reboot" doesn't return gracefully
840 pass
841
842 def connect(self, timeout=10, check_boot_completed=True): # pylint: disable=arguments-differ
843 start = time.time()
844 device = self.connection_settings.get('device')
845 if device and ':' in device:
846 # ADB does not automatically remove a network device from it's
847 # devices list when the connection is broken by the remote, so the
848 # adb connection may have gone "stale", resulting in adb blocking
849 # indefinitely when making calls to the device. To avoid this,
850 # always disconnect first.
851 adb_disconnect(device)
852 super(AndroidTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100853
854 if check_boot_completed:
855 boot_completed = boolean(self.getprop('sys.boot_completed'))
856 while not boot_completed and timeout >= time.time() - start:
857 time.sleep(5)
858 boot_completed = boolean(self.getprop('sys.boot_completed'))
859 if not boot_completed:
860 raise TargetError('Connected but Android did not fully boot.')
861
862 def setup(self, executables=None):
863 super(AndroidTarget, self).setup(executables)
864 self.execute('mkdir -p {}'.format(self._file_transfer_cache))
865
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100866 def kick_off(self, command, as_root=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100867 """
868 Like execute but closes adb session and returns immediately, leaving the command running on the
869 device (this is different from execute(background=True) which keeps adb connection open and returns
870 a subprocess object).
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100871 """
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100872 if as_root is None:
Javi Merino16d87c62016-06-23 14:55:19 +0100873 as_root = self.needs_su
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100874 try:
Sergei Trofimovca0b6e82016-08-30 14:25:11 +0100875 command = 'cd {} && {} nohup {} &'.format(self.working_directory, self.busybox, command)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100876 output = self.execute(command, timeout=1, as_root=as_root)
877 except TimeoutError:
878 pass
879 else:
880 raise ValueError('Background command exited before timeout; got "{}"'.format(output))
881
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100882 def __setup_list_directory(self):
883 # In at least Linaro Android 16.09 (which was their first Android 7 release) and maybe
884 # AOSP 7.0 as well, the ls command was changed.
885 # Previous versions default to a single column listing, which is nice and easy to parse.
886 # Newer versions default to a multi-column listing, which is not, but it does support
887 # a '-1' option to get into single column mode. Older versions do not support this option
888 # so we try the new version, and if it fails we use the old version.
889 self.ls_command = 'ls -1'
890 try:
891 self.execute('ls -1 /', as_root=False)
892 except TargetError:
893 self.ls_command = 'ls'
894
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100895 def list_directory(self, path, as_root=False):
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100896 if self.ls_command == '':
897 self.__setup_list_directory()
898 contents = self.execute('{} {}'.format(self.ls_command, path), as_root=as_root)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100899 return [x.strip() for x in contents.split('\n') if x.strip()]
900
901 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
902 ext = os.path.splitext(filepath)[1].lower()
903 if ext == '.apk':
904 return self.install_apk(filepath, timeout)
905 else:
906 return self.install_executable(filepath, with_name)
907
908 def uninstall(self, name):
909 if self.package_is_installed(name):
910 self.uninstall_package(name)
911 else:
912 self.uninstall_executable(name)
913
914 def get_pids_of(self, process_name):
915 result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
916 if result and 'not found' not in result:
917 return [int(x.split()[1]) for x in result.split('\n')[1:]]
918 else:
919 return []
920
921 def ps(self, **kwargs):
922 lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
923 lines.next() # header
924 result = []
925 for line in lines:
926 parts = line.split()
927 if parts:
928 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
929 if not kwargs:
930 return result
931 else:
932 filtered_result = []
933 for entry in result:
934 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
935 filtered_result.append(entry)
936 return filtered_result
937
938 def capture_screen(self, filepath):
939 on_device_file = self.path.join(self.working_directory, 'screen_capture.png')
940 self.execute('screencap -p {}'.format(on_device_file))
941 self.pull(on_device_file, filepath)
942 self.remove(on_device_file)
943
944 def push(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
945 if not as_root:
946 self.conn.push(source, dest, timeout=timeout)
947 else:
948 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000949 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100950 self.conn.push(source, device_tempfile, timeout=timeout)
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000951 self.execute("cp '{}' '{}'".format(device_tempfile, dest), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100952
953 def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
954 if not as_root:
955 self.conn.pull(source, dest, timeout=timeout)
956 else:
957 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +0000958 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
959 self.execute("cp '{}' '{}'".format(source, device_tempfile), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100960 self.conn.pull(device_tempfile, dest, timeout=timeout)
961
962 # Android-specific
963
Sebastian Goscik880a0bc2016-02-15 15:19:47 +0000964 def swipe_to_unlock(self, direction="horizontal"):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100965 width, height = self.screen_resolution
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100966 command = 'input swipe {} {} {} {}'
Sebastian Goscik880a0bc2016-02-15 15:19:47 +0000967 if direction == "horizontal":
968 swipe_heigh = height * 2 // 3
969 start = 100
970 stop = width - start
971 self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
972 if direction == "vertical":
973 swipe_middle = height / 2
974 swipe_heigh = height * 2 // 3
975 self.execute(command.format(swipe_middle, swipe_heigh, swipe_middle, 0))
976 else:
977 raise DeviceError("Invalid swipe direction: {}".format(self.swipe_to_unlock))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100978
979 def getprop(self, prop=None):
980 props = AndroidProperties(self.execute('getprop'))
981 if prop:
982 return props[prop]
983 return props
984
985 def is_installed(self, name):
986 return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name)
987
988 def package_is_installed(self, package_name):
989 return package_name in self.list_packages()
990
991 def list_packages(self):
992 output = self.execute('pm list packages')
993 output = output.replace('package:', '')
994 return output.split()
995
996 def get_package_version(self, package):
997 output = self.execute('dumpsys package {}'.format(package))
998 for line in convert_new_lines(output).split('\n'):
999 if 'versionName' in line:
1000 return line.split('=', 1)[1]
1001 return None
1002
1003 def install_apk(self, filepath, timeout=None): # pylint: disable=W0221
1004 ext = os.path.splitext(filepath)[1].lower()
1005 if ext == '.apk':
Sebastian Goscik1424ceb2016-02-15 15:27:19 +00001006 return adb_command(self.adb_name, "install '{}'".format(filepath), timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001007 else:
1008 raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
1009
1010 def install_executable(self, filepath, with_name=None):
1011 self._ensure_executables_directory_is_writable()
1012 executable_name = with_name or os.path.basename(filepath)
1013 on_device_file = self.path.join(self.working_directory, executable_name)
1014 on_device_executable = self.path.join(self.executables_directory, executable_name)
1015 self.push(filepath, on_device_file)
1016 if on_device_file != on_device_executable:
Javi Merino16d87c62016-06-23 14:55:19 +01001017 self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.needs_su)
1018 self.remove(on_device_file, as_root=self.needs_su)
1019 self.execute("chmod 0777 '{}'".format(on_device_executable), as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001020 self._installed_binaries[executable_name] = on_device_executable
1021 return on_device_executable
1022
1023 def uninstall_package(self, package):
1024 adb_command(self.adb_name, "uninstall {}".format(package), timeout=30)
1025
1026 def uninstall_executable(self, executable_name):
1027 on_device_executable = self.path.join(self.executables_directory, executable_name)
1028 self._ensure_executables_directory_is_writable()
Javi Merino16d87c62016-06-23 14:55:19 +01001029 self.remove(on_device_executable, as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001030
1031 def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin
Sebastian Goscikaab487c2016-02-15 15:21:40 +00001032 op = '>>' if append else '>'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001033 filtstr = ' -s {}'.format(filter) if filter else ''
1034 command = 'logcat -d{} {} {}'.format(filtstr, op, filepath)
1035 adb_command(self.adb_name, command, timeout=timeout)
1036
1037 def clear_logcat(self):
1038 adb_command(self.adb_name, 'logcat -c', timeout=30)
1039
Patrick Bellasi9ce57c02017-02-16 13:11:02 +00001040 def adb_reboot_bootloader(self, timeout=30):
1041 adb_command(self.adb_name, 'reboot-bootloader', timeout)
1042
1043 def adb_root(self, enable=True):
1044 if enable:
1045 if self._connected_as_root:
1046 return
1047 adb_command(self.adb_name, 'root', timeout=30)
1048 self._connected_as_root = True
1049 return
1050 adb_command(self.adb_name, 'unroot', timeout=30)
1051 self._connected_as_root = False
1052
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001053 def is_screen_on(self):
1054 output = self.execute('dumpsys power')
1055 match = ANDROID_SCREEN_STATE_REGEX.search(output)
1056 if match:
1057 return boolean(match.group(1))
1058 else:
1059 raise TargetError('Could not establish screen state.')
1060
1061 def ensure_screen_is_on(self):
1062 if not self.is_screen_on():
1063 self.execute('input keyevent 26')
1064
Sergei Trofimov961f9572015-11-18 17:32:26 +00001065 def _resolve_paths(self):
1066 if self.working_directory is None:
1067 self.working_directory = '/data/local/tmp/devlib-target'
1068 self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache')
1069 if self.executables_directory is None:
Sebastian Goscikff8261e2016-02-15 15:28:20 +00001070 self.executables_directory = '/data/local/tmp/bin'
Sergei Trofimov961f9572015-11-18 17:32:26 +00001071
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001072 def _ensure_executables_directory_is_writable(self):
1073 matched = []
1074 for entry in self.list_file_systems():
1075 if self.executables_directory.rstrip('/').startswith(entry.mount_point):
1076 matched.append(entry)
1077 if matched:
1078 entry = sorted(matched, key=lambda x: len(x.mount_point))[-1]
1079 if 'rw' not in entry.options:
1080 self.execute('mount -o rw,remount {} {}'.format(entry.device,
1081 entry.mount_point),
1082 as_root=True)
1083 else:
1084 message = 'Could not find mount point for executables directory {}'
1085 raise TargetError(message.format(self.executables_directory))
1086
1087
1088FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
1089PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +00001090LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001091
1092
1093class Cpuinfo(object):
1094
1095 @property
1096 @memoized
1097 def architecture(self):
1098 for section in self.sections:
1099 if 'CPU architecture' in section:
1100 return section['CPU architecture']
1101 if 'architecture' in section:
1102 return section['architecture']
1103
1104 @property
1105 @memoized
1106 def cpu_names(self):
1107 cpu_names = []
1108 global_name = None
1109 for section in self.sections:
1110 if 'processor' in section:
1111 if 'CPU part' in section:
1112 cpu_names.append(_get_part_name(section))
1113 elif 'model name' in section:
1114 cpu_names.append(_get_model_name(section))
1115 else:
1116 cpu_names.append(None)
1117 elif 'CPU part' in section:
1118 global_name = _get_part_name(section)
1119 return [caseless_string(c or global_name) for c in cpu_names]
1120
1121 def __init__(self, text):
1122 self.sections = None
1123 self.text = None
1124 self.parse(text)
1125
1126 @memoized
1127 def get_cpu_features(self, cpuid=0):
1128 global_features = []
1129 for section in self.sections:
1130 if 'processor' in section:
1131 if int(section.get('processor')) != cpuid:
1132 continue
1133 if 'Features' in section:
1134 return section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001135 elif 'flags' in section:
1136 return section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001137 elif 'Features' in section:
1138 global_features = section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001139 elif 'flags' in section:
1140 global_features = section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001141 return global_features
1142
1143 def parse(self, text):
1144 self.sections = []
1145 current_section = {}
1146 self.text = text.strip()
1147 for line in self.text.split('\n'):
1148 line = line.strip()
1149 if line:
1150 key, value = line.split(':', 1)
1151 current_section[key.strip()] = value.strip()
1152 else: # not line
1153 self.sections.append(current_section)
1154 current_section = {}
1155 self.sections.append(current_section)
1156
1157 def __str__(self):
1158 return 'CpuInfo({})'.format(self.cpu_names)
1159
1160 __repr__ = __str__
1161
1162
1163class KernelVersion(object):
1164
1165 def __init__(self, version_string):
1166 if ' #' in version_string:
1167 release, version = version_string.split(' #')
1168 self.release = release
1169 self.version = version
1170 elif version_string.startswith('#'):
1171 self.release = ''
1172 self.version = version_string
1173 else:
1174 self.release = version_string
1175 self.version = ''
1176
1177 def __str__(self):
1178 return '{} {}'.format(self.release, self.version)
1179
1180 __repr__ = __str__
1181
1182
1183class KernelConfig(object):
1184
1185 not_set_regex = re.compile(r'# (\S+) is not set')
1186
1187 @staticmethod
1188 def get_config_name(name):
1189 name = name.upper()
1190 if not name.startswith('CONFIG_'):
1191 name = 'CONFIG_' + name
1192 return name
1193
1194 def iteritems(self):
1195 return self._config.iteritems()
1196
1197 def __init__(self, text):
1198 self.text = text
1199 self._config = {}
1200 for line in text.split('\n'):
1201 line = line.strip()
1202 if line.startswith('#'):
1203 match = self.not_set_regex.search(line)
1204 if match:
1205 self._config[match.group(1)] = 'n'
1206 elif '=' in line:
1207 name, value = line.split('=', 1)
1208 self._config[name.strip()] = value.strip()
1209
1210 def get(self, name):
1211 return self._config.get(self.get_config_name(name))
1212
1213 def like(self, name):
1214 regex = re.compile(name, re.I)
1215 result = {}
1216 for k, v in self._config.iteritems():
1217 if regex.search(k):
1218 result[k] = v
1219 return result
1220
1221 def is_enabled(self, name):
1222 return self.get(name) == 'y'
1223
1224 def is_module(self, name):
1225 return self.get(name) == 'm'
1226
1227 def is_not_set(self, name):
1228 return self.get(name) == 'n'
1229
1230 def has(self, name):
1231 return self.get(name) in ['m', 'y']
1232
1233
1234class LocalLinuxTarget(LinuxTarget):
1235
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +00001236 def __init__(self,
1237 connection_settings=None,
1238 platform=None,
1239 working_directory=None,
1240 executables_directory=None,
1241 connect=True,
1242 modules=None,
1243 load_default_modules=True,
1244 shell_prompt=DEFAULT_SHELL_PROMPT,
1245 conn_cls=LocalConnection,
1246 ):
1247 super(LocalLinuxTarget, self).__init__(connection_settings=connection_settings,
1248 platform=platform,
1249 working_directory=working_directory,
1250 executables_directory=executables_directory,
1251 connect=connect,
1252 modules=modules,
1253 load_default_modules=load_default_modules,
1254 shell_prompt=shell_prompt,
1255 conn_cls=conn_cls)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001256
Sergei Trofimov961f9572015-11-18 17:32:26 +00001257 def _resolve_paths(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001258 if self.working_directory is None:
1259 self.working_directory = '/tmp'
1260 if self.executables_directory is None:
1261 self.executables_directory = '/tmp'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001262
1263
1264def _get_model_name(section):
1265 name_string = section['model name']
1266 parts = name_string.split('@')[0].strip().split()
1267 return ' '.join([p for p in parts
1268 if '(' not in p and p != 'CPU'])
1269
1270
1271def _get_part_name(section):
1272 implementer = section.get('CPU implementer', '0x0')
1273 part = section['CPU part']
1274 variant = section.get('CPU variant', '0x0')
1275 name = get_cpu_name(*map(integer, [implementer, part, variant]))
1276 if name is None:
1277 name = '{}/{}/{}'.format(implementer, part, variant)
1278 return name