Home | History | Annotate | Download | only in android
      1 # -*- coding: utf-8 -*-
      2 
      3 #-------------------------------------------------------------------------
      4 # drawElements Quality Program utilities
      5 # --------------------------------------
      6 #
      7 # Copyright 2017 The Android Open Source Project
      8 #
      9 # Licensed under the Apache License, Version 2.0 (the "License");
     10 # you may not use this file except in compliance with the License.
     11 # You may obtain a copy of the License at
     12 #
     13 #      http://www.apache.org/licenses/LICENSE-2.0
     14 #
     15 # Unless required by applicable law or agreed to in writing, software
     16 # distributed under the License is distributed on an "AS IS" BASIS,
     17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     18 # See the License for the specific language governing permissions and
     19 # limitations under the License.
     20 #
     21 #-------------------------------------------------------------------------
     22 
     23 import os
     24 import re
     25 import sys
     26 import argparse
     27 import threading
     28 import subprocess
     29 
     30 from build_apk import findSDK
     31 from build_apk import getDefaultBuildRoot
     32 from build_apk import getPackageAndLibrariesForTarget
     33 from build_apk import getBuildRootRelativeAPKPath
     34 from build_apk import parsePackageName
     35 
     36 # Import from <root>/scripts
     37 sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
     38 
     39 from build.common import *
     40 
     41 class Device:
     42 	def __init__(self, serial, product, model, device):
     43 		self.serial		= serial
     44 		self.product	= product
     45 		self.model		= model
     46 		self.device		= device
     47 
     48 	def __str__ (self):
     49 		return "%s: {product: %s, model: %s, device: %s}" % (self.serial, self.product, self.model, self.device)
     50 
     51 def getDevices (adbPath):
     52 	proc = subprocess.Popen([adbPath, 'devices', '-l'], stdout=subprocess.PIPE)
     53 	(stdout, stderr) = proc.communicate()
     54 
     55 	if proc.returncode != 0:
     56 		raise Exception("adb devices -l failed, got %d" % proc.returncode)
     57 
     58 	ptrn = re.compile(r'^([a-zA-Z0-9\.\-:]+)\s+.*product:([^\s]+)\s+model:([^\s]+)\s+device:([^\s]+)')
     59 	devices = []
     60 	for line in stdout.splitlines()[1:]:
     61 		if len(line.strip()) == 0:
     62 			continue
     63 
     64 		m = ptrn.match(line)
     65 		if m == None:
     66 			print "WARNING: Failed to parse device info '%s'" % line
     67 			continue
     68 
     69 		devices.append(Device(m.group(1), m.group(2), m.group(3), m.group(4)))
     70 
     71 	return devices
     72 
     73 def execWithPrintPrefix (args, linePrefix="", failOnNonZeroExit=True):
     74 
     75 	def readApplyPrefixAndPrint (source, prefix, sink):
     76 		while True:
     77 			line = source.readline()
     78 			if len(line) == 0: # EOF
     79 				break;
     80 			sink.write(prefix + line)
     81 
     82 	process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     83 	stdoutJob = threading.Thread(target=readApplyPrefixAndPrint, args=(process.stdout, linePrefix, sys.stdout))
     84 	stderrJob = threading.Thread(target=readApplyPrefixAndPrint, args=(process.stderr, linePrefix, sys.stderr))
     85 	stdoutJob.start()
     86 	stderrJob.start()
     87 	retcode = process.wait()
     88 	if failOnNonZeroExit and retcode != 0:
     89 		raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
     90 
     91 def serialApply (f, argsList):
     92 	for args in argsList:
     93 		f(*args)
     94 
     95 def parallelApply (f, argsList):
     96 	class ErrorCode:
     97 		def __init__ (self):
     98 			self.error = None;
     99 
    100 	def applyAndCaptureError (func, args, errorCode):
    101 		try:
    102 			func(*args)
    103 		except:
    104 			errorCode.error = sys.exc_info()
    105 
    106 	errorCode = ErrorCode()
    107 	jobs = []
    108 	for args in argsList:
    109 		job = threading.Thread(target=applyAndCaptureError, args=(f, args, errorCode))
    110 		job.start()
    111 		jobs.append(job)
    112 
    113 	for job in jobs:
    114 		job.join()
    115 
    116 	if errorCode.error:
    117 		raise errorCode.error[0], errorCode.error[1], errorCode.error[2]
    118 
    119 def uninstall (adbPath, packageName, extraArgs = [], printPrefix=""):
    120 	print printPrefix + "Removing existing %s...\n" % packageName,
    121 	execWithPrintPrefix([adbPath] + extraArgs + [
    122 			'uninstall',
    123 			packageName
    124 		], printPrefix, failOnNonZeroExit=False)
    125 	print printPrefix + "Remove complete\n",
    126 
    127 def install (adbPath, apkPath, extraArgs = [], printPrefix=""):
    128 	print printPrefix + "Installing %s...\n" % apkPath,
    129 	execWithPrintPrefix([adbPath] + extraArgs + [
    130 			'install',
    131 			apkPath
    132 		], printPrefix)
    133 	print printPrefix + "Install complete\n",
    134 
    135 def installToDevice (device, adbPath, packageName, apkPath, printPrefix=""):
    136 	if len(printPrefix) == 0:
    137 		print "Installing to %s (%s)...\n" % (device.serial, device.model),
    138 	else:
    139 		print printPrefix + "Installing to %s\n" % device.serial,
    140 
    141 	uninstall(adbPath, packageName, ['-s', device.serial], printPrefix)
    142 	install(adbPath, apkPath, ['-s', device.serial], printPrefix)
    143 
    144 def installToDevices (devices, doParallel, adbPath, packageName, apkPath):
    145 	padLen = max([len(device.model) for device in devices])+1
    146 	if doParallel:
    147 		parallelApply(installToDevice, [(device, adbPath, packageName, apkPath, ("(%s):%s" % (device.model, ' ' * (padLen - len(device.model))))) for device in devices]);
    148 	else:
    149 		serialApply(installToDevice, [(device, adbPath, packageName, apkPath) for device in devices]);
    150 
    151 def installToAllDevices (doParallel, adbPath, packageName, apkPath):
    152 	devices = getDevices(adbPath)
    153 	installToDevices(devices, doParallel, adbPath, packageName, apkPath)
    154 
    155 def getAPKPath (buildRootPath, target):
    156 	package = getPackageAndLibrariesForTarget(target)[0]
    157 	return os.path.join(buildRootPath, getBuildRootRelativeAPKPath(package))
    158 
    159 def getPackageName (target):
    160 	package			= getPackageAndLibrariesForTarget(target)[0]
    161 	manifestPath	= os.path.join(DEQP_DIR, "android", package.appDirName, "AndroidManifest.xml")
    162 
    163 	return parsePackageName(manifestPath)
    164 
    165 def findADB ():
    166 	adbInPath = which("adb")
    167 	if adbInPath != None:
    168 		return adbInPath
    169 
    170 	sdkPath = findSDK()
    171 	if sdkPath != None:
    172 		adbInSDK = os.path.join(sdkPath, "platform-tools", "adb")
    173 		if os.path.isfile(adbInSDK):
    174 			return adbInSDK
    175 
    176 	return None
    177 
    178 def parseArgs ():
    179 	defaultADBPath		= findADB()
    180 	defaultBuildRoot	= getDefaultBuildRoot()
    181 
    182 	parser = argparse.ArgumentParser(os.path.basename(__file__),
    183 		formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    184 	parser.add_argument('--build-root',
    185 		dest='buildRoot',
    186 		default=defaultBuildRoot,
    187 		help="Root build directory")
    188 	parser.add_argument('--adb',
    189 		dest='adbPath',
    190 		default=defaultADBPath,
    191 		help="ADB binary path",
    192 		required=(True if defaultADBPath == None else False))
    193 	parser.add_argument('--target',
    194 		dest='target',
    195 		help='Build target',
    196 		choices=['deqp', 'openglcts'],
    197 		default='deqp')
    198 	parser.add_argument('-p', '--parallel',
    199 		dest='doParallel',
    200 		action="store_true",
    201 		help="Install package in parallel")
    202 	parser.add_argument('-s', '--serial',
    203 		dest='serial',
    204 		type=str,
    205 		nargs='+',
    206 		help="Install package to device with serial number")
    207 	parser.add_argument('-a', '--all',
    208 		dest='all',
    209 		action="store_true",
    210 		help="Install to all devices")
    211 
    212 	return parser.parse_args()
    213 
    214 if __name__ == "__main__":
    215 	args		= parseArgs()
    216 	packageName	= getPackageName(args.target)
    217 	apkPath		= getAPKPath(args.buildRoot, args.target)
    218 
    219 	if not os.path.isfile(apkPath):
    220 		die("%s does not exist" % apkPath)
    221 
    222 	if args.all:
    223 		installToAllDevices(args.doParallel, args.adbPath, packageName, apkPath)
    224 	else:
    225 		if args.serial == None:
    226 			devices = getDevices(args.adbPath)
    227 			if len(devices) == 0:
    228 				die('No devices connected')
    229 			elif len(devices) == 1:
    230 				installToDevice(devices[0], args.adbPath, packageName, apkPath)
    231 			else:
    232 				print "More than one device connected:"
    233 				for i in range(0, len(devices)):
    234 					print "%3d: %16s %s" % ((i+1), devices[i].serial, devices[i].model)
    235 
    236 				deviceNdx = int(raw_input("Choose device (1-%d): " % len(devices)))
    237 				installToDevice(devices[deviceNdx-1], args.adbPath, packageName, apkPath)
    238 		else:
    239 			devices = getDevices(args.adbPath)
    240 
    241 			devices = [dev for dev in devices if dev.serial in args.serial]
    242 			devSerials = [dev.serial for dev in devices]
    243 			notFounds = [serial for serial in args.serial if not serial in devSerials]
    244 
    245 			for notFound in notFounds:
    246 				print("Couldn't find device matching serial '%s'" % notFound)
    247 
    248 			installToDevices(devices, args.doParallel, args.adbPath, packageName, apkPath)
    249