mirror of
https://github.com/thead-yocto-mirror/skia
synced 2026-06-21 08:52:36 +02:00
322 lines
12 KiB
Python
322 lines
12 KiB
Python
#!/usr/bin/env python
|
|
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
""" This module contains tools used by Android-specific buildbot scripts. """
|
|
|
|
import os
|
|
import re
|
|
import shell_utils
|
|
import shlex
|
|
import sys
|
|
import time
|
|
|
|
|
|
CPU_SCALING_MODES = ['performance', 'interactive']
|
|
DEVICE_LOOKUP = {'nexus_s': 'crespo',
|
|
'xoom': 'stingray',
|
|
'galaxy_nexus': 'toro',
|
|
'nexus_4': 'mako',
|
|
'nexus_7': 'grouper',
|
|
'nexus_10': 'manta'}
|
|
PROCESS_MONITOR_INTERVAL = 5.0 # Seconds
|
|
SKIA_RUNNING = 'running'
|
|
SKIA_RETURN_CODE_REPEATS = 10
|
|
SUBPROCESS_TIMEOUT = 30.0
|
|
|
|
|
|
def GotADB(adb):
|
|
""" Returns True iff ADB exists at the given location.
|
|
|
|
adb: string; possible path to the ADB executable.
|
|
"""
|
|
try:
|
|
shell_utils.run([adb, 'version'], echo=False)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def FindADB(hint=None):
|
|
""" Attempt to find the ADB program using the following sequence of steps.
|
|
Returns the path to ADB if it can be found, or None otherwise.
|
|
1. If a hint was provided, is it a valid path to ADB?
|
|
2. Is ADB in PATH?
|
|
3. Is there an environment variable for ADB?
|
|
4. If the ANDROID_SDK_ROOT variable is set, try to find ADB in the SDK
|
|
directory.
|
|
5. Try to find ADB in a list of common locations.
|
|
|
|
hint: string indicating a possible path to ADB.
|
|
"""
|
|
# 1. If a hint was provided, does it point to ADB?
|
|
if hint:
|
|
if os.path.basename(hint) == 'adb':
|
|
adb = hint
|
|
else:
|
|
adb = os.path.join(hint, 'adb')
|
|
if GotADB(adb):
|
|
return adb
|
|
|
|
# 2. Is 'adb' in our PATH?
|
|
adb = 'adb'
|
|
if GotADB(adb):
|
|
return adb
|
|
|
|
# 3. Is there an environment variable for ADB?
|
|
adb = os.environ.get('ADB')
|
|
if GotADB(adb):
|
|
return adb
|
|
|
|
# 4. If ANDROID_SDK_ROOT is set, try to find ADB in the SDK directory.
|
|
sdk_dir = os.environ.get('ANDROID_SDK_ROOT', '')
|
|
adb = os.path.join(sdk_dir, 'platform-tools', 'adb')
|
|
if GotADB(adb):
|
|
return adb
|
|
|
|
# 4. Try to find ADB relative to this file.
|
|
common_locations = []
|
|
os_dir = None
|
|
if sys.platform.startswith('linux'):
|
|
os_dir = 'linux'
|
|
elif sys.platform.startswith('darwin'):
|
|
os_dir = 'mac'
|
|
else:
|
|
os_dir = 'win'
|
|
common_locations.append(os.path.join('platform_tools', 'android', 'bin',
|
|
os_dir, 'adb'))
|
|
common_locations.append(os.path.join(os.environ.get('HOME', ''),
|
|
'android-sdk-%s' % os_dir))
|
|
for location in common_locations:
|
|
if GotADB(location):
|
|
return location
|
|
|
|
raise Exception('android_utils: Unable to find ADB!')
|
|
|
|
|
|
PATH_TO_ADB = FindADB(hint=os.path.join('platform_tools', 'android', 'bin',
|
|
'linux', 'adb'))
|
|
|
|
|
|
def RunADB(serial, cmd, echo=True, attempts=5, secs_between_attempts=10,
|
|
timeout=None):
|
|
""" Run 'cmd' on an Android device, using ADB. No return value; throws an
|
|
exception if the command fails more than the allotted number of attempts.
|
|
|
|
serial: string indicating the serial number of the target device
|
|
cmd: string; the command to issue on the device
|
|
attempts: number of times to attempt the command
|
|
secs_between_attempts: number of seconds to wait between attempts
|
|
timeout: optional, integer indicating the maximum elapsed time in seconds
|
|
"""
|
|
adb_cmd = [PATH_TO_ADB, '-s', serial]
|
|
adb_cmd += cmd
|
|
shell_utils.run_retry(adb_cmd, echo=echo, attempts=attempts,
|
|
secs_between_attempts=secs_between_attempts)
|
|
|
|
|
|
def ADBShell(serial, cmd, echo=True):
|
|
""" Runs 'cmd' in the ADB shell on an Android device and returns the exit
|
|
code.
|
|
|
|
serial: string indicating the serial number of the target device
|
|
cmd: string; the command to issue on the device
|
|
"""
|
|
# ADB doesn't exit with the exit code of the command we ran. It only exits
|
|
# non-zero when ADB itself encountered a problem. Therefore, we have to use
|
|
# the shell to print the exit code for the command and parse that from stdout.
|
|
adb_cmd = '%s -s %s shell "%s; echo \$?"' % (PATH_TO_ADB, serial,
|
|
' '.join(cmd))
|
|
output = shell_utils.run(adb_cmd, shell=True, echo=echo)
|
|
output_lines = output.splitlines()
|
|
try:
|
|
real_exitcode = int(output_lines[-1].rstrip())
|
|
except ValueError:
|
|
real_exitcode = -1
|
|
if real_exitcode != 0:
|
|
raise Exception('Command failed with code %s' % real_exitcode)
|
|
return '\n'.join(output_lines[:-1])
|
|
|
|
|
|
def ADBKill(serial, process, kill_app=False):
|
|
""" Kill a process running on an Android device.
|
|
|
|
serial: string indicating the serial number of the target device
|
|
process: string indicating the name of the process to kill
|
|
kill_app: bool indicating whether the process is an Android app, as opposed
|
|
to a normal executable process.
|
|
"""
|
|
if kill_app:
|
|
ADBShell(serial, ['am', 'kill', process])
|
|
else:
|
|
try:
|
|
stdout = shell_utils.run('%s -s %s shell ps | grep %s' % (PATH_TO_ADB,
|
|
serial,
|
|
process),
|
|
shell=True)
|
|
except Exception:
|
|
return
|
|
for line in stdout.split('\n'):
|
|
if line != '':
|
|
split = shlex.split(line)
|
|
if len(split) < 2:
|
|
continue
|
|
pid = split[1]
|
|
ADBShell(serial, ['kill', pid])
|
|
# Raise an exception if any Skia processes are still running.
|
|
try:
|
|
stdout = shell_utils.run('%s -s %s shell ps | grep %s' % (PATH_TO_ADB,
|
|
serial,
|
|
process),
|
|
shell=True)
|
|
except Exception:
|
|
return
|
|
if stdout:
|
|
raise Exception('There are still some skia processes running:\n%s\n'
|
|
'Maybe the device should be rebooted?' % stdout)
|
|
|
|
|
|
def GetSerial(device_type):
|
|
""" Determine the serial number of the *first* connected device with the
|
|
specified type. The ordering of 'adb devices' is not documented, and the
|
|
connected devices do not appear to be ordered by serial number. Therefore,
|
|
we have to assume that, in the case of multiple devices of the same type being
|
|
connected to one host, we cannot predict which device will be chosen.
|
|
|
|
device_type: string indicating the 'common name' for the target device
|
|
"""
|
|
if not device_type in DEVICE_LOOKUP:
|
|
raise ValueError('Unknown device: %s!' % device_type)
|
|
device_name = DEVICE_LOOKUP[device_type]
|
|
output = shell_utils.run_retry('%s devices' % PATH_TO_ADB, shell=True,
|
|
attempts=5)
|
|
print output
|
|
lines = output.split('\n')
|
|
device_ids = []
|
|
for line in lines:
|
|
# Filter garbage lines
|
|
if line != '' and not ('List of devices attached' in line) and \
|
|
line[0] != '*':
|
|
device_ids.append(line.split('\t')[0])
|
|
for device_id in device_ids:
|
|
print 'Finding type for id %s' % device_id
|
|
# Get device name
|
|
name_line = shell_utils.run_retry(
|
|
'%s -s %s shell cat /system/build.prop | grep "ro.product.device="' % (
|
|
PATH_TO_ADB, device_id), shell=True, attempts=5)
|
|
print name_line
|
|
name = name_line.split('=')[-1].rstrip()
|
|
# Just return the first attached device of the requested model.
|
|
if device_name in name:
|
|
return device_id
|
|
raise Exception('No %s device attached!' % device_name)
|
|
|
|
|
|
def SetCPUScalingMode(serial, mode):
|
|
""" Set the CPU scaling governor for the device with the given serial number
|
|
to the given mode.
|
|
|
|
serial: string indicating the serial number of the device whose scaling mode
|
|
is to be modified
|
|
mode: string indicating the desired CPU scaling mode. Acceptable values
|
|
are listed in CPU_SCALING_MODES.
|
|
"""
|
|
if mode not in CPU_SCALING_MODES:
|
|
raise ValueError('mode must be one of: %s' % CPU_SCALING_MODES)
|
|
cpu_dirs = shell_utils.run('%s -s %s shell ls /sys/devices/system/cpu' % (
|
|
PATH_TO_ADB, serial), echo=False, shell=True)
|
|
cpu_dirs_list = cpu_dirs.split('\n')
|
|
regex = re.compile('cpu\d')
|
|
for cpu_dir_from_list in cpu_dirs_list:
|
|
cpu_dir = cpu_dir_from_list.rstrip()
|
|
if regex.match(cpu_dir):
|
|
path = '/sys/devices/system/cpu/%s/cpufreq/scaling_governor' % cpu_dir
|
|
path_found = shell_utils.run('%s -s %s shell ls %s' % (
|
|
PATH_TO_ADB, serial, path),
|
|
echo=False, shell=True).rstrip()
|
|
if path_found == path:
|
|
# Unfortunately, we can't directly change the scaling_governor file over
|
|
# ADB. Instead, we write a script to do so, push it to the device, and
|
|
# run it.
|
|
old_mode = shell_utils.run('%s -s %s shell cat %s' % (
|
|
PATH_TO_ADB, serial, path),
|
|
echo=False, shell=True).rstrip()
|
|
print 'Current scaling mode for %s is: %s' % (cpu_dir, old_mode)
|
|
filename = 'skia_cpuscale.sh'
|
|
with open(filename, 'w') as script_file:
|
|
script_file.write('echo %s > %s\n' % (mode, path))
|
|
os.chmod(filename, 0777)
|
|
RunADB(serial, ['push', filename, '/system/bin'], echo=False)
|
|
RunADB(serial, ['shell', filename], echo=True)
|
|
RunADB(serial, ['shell', 'rm', '/system/bin/%s' % filename], echo=False)
|
|
os.remove(filename)
|
|
new_mode = shell_utils.run('%s -s %s shell cat %s' % (
|
|
PATH_TO_ADB, serial, path),
|
|
echo=False, shell=True).rstrip()
|
|
print 'New scaling mode for %s is: %s' % (cpu_dir, new_mode)
|
|
|
|
|
|
def IsAndroidShellRunning(serial):
|
|
""" Find the status of the Android shell for the device with the given serial
|
|
number. Returns True if the shell is running and False otherwise.
|
|
|
|
serial: string indicating the serial number of the target device.
|
|
"""
|
|
if 'Error:' in ADBShell(serial, ['pm', 'path', 'android'], echo=False):
|
|
return False
|
|
return True
|
|
|
|
|
|
def StopShell(serial, timeout=60):
|
|
""" Halt the Android runtime on the device with the given serial number.
|
|
Blocks until the shell reports that it has stopped.
|
|
|
|
serial: string indicating the serial number of the target device.
|
|
timeout: maximum allotted time, in seconds.
|
|
"""
|
|
ADBShell(serial, ['stop'])
|
|
start_time = time.time()
|
|
while IsAndroidShellRunning(serial):
|
|
time.sleep(1)
|
|
if time.time() - start_time > timeout:
|
|
raise Exception('Timeout while attempting to stop the Android runtime.')
|
|
|
|
|
|
def StartShell(serial, timeout=60):
|
|
""" Start the Android runtime on the device with the given serial number.
|
|
Blocks until the shell reports that it has started.
|
|
|
|
serial: string indicating the serial number of the target device.
|
|
timeout: maximum allotted time, in seconds.
|
|
"""
|
|
ADBShell(serial, ['start'])
|
|
start_time = time.time()
|
|
while not IsAndroidShellRunning(serial):
|
|
time.sleep(1)
|
|
if time.time() - start_time > timeout:
|
|
raise Exception('Timeout while attempting to start the Android runtime.')
|
|
|
|
|
|
def RunSkia(serial, cmd, release, device):
|
|
""" Run the given command through skia_launcher on a given device.
|
|
|
|
serial: string indicating the serial number of the target device.
|
|
cmd: list of strings; the command line to run.
|
|
release: bool; whether or not to run the app in Release mode.
|
|
device: string indicating the target device.
|
|
"""
|
|
RunADB(serial, ['logcat', '-c'])
|
|
try:
|
|
os.environ['SKIA_ANDROID_VERBOSE_SETUP'] = '1'
|
|
cmd_to_run = [os.path.join('platform_tools', 'android', 'bin',
|
|
'android_run_skia')]
|
|
if release:
|
|
cmd_to_run.extend(['--release'])
|
|
cmd_to_run.extend(['-d', device])
|
|
cmd_to_run.extend(['-s', serial])
|
|
cmd_to_run.extend(cmd)
|
|
shell_utils.run(cmd_to_run)
|
|
finally:
|
|
RunADB(serial, ['logcat', '-d', '-v', 'time'])
|