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
23import os
24import re
25import sys
26import argparse
27import threading
28import subprocess
29
30from build_apk import findSDK
31from build_apk import getDefaultBuildRoot
32from build_apk import getPackageAndLibrariesForTarget
33from build_apk import getBuildRootRelativeAPKPath
34from build_apk import parsePackageName
35
36# Import from <root>/scripts
37sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
38
39from build.common import *
40
41class 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
51def 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
73def 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
91def serialApply (f, argsList):
92	for args in argsList:
93		f(*args)
94
95def 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
119def 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
127def 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
135def 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
144def 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
151def installToAllDevices (doParallel, adbPath, packageName, apkPath):
152	devices = getDevices(adbPath)
153	installToDevices(devices, doParallel, adbPath, packageName, apkPath)
154
155def getAPKPath (buildRootPath, target):
156	package = getPackageAndLibrariesForTarget(target)[0]
157	return os.path.join(buildRootPath, getBuildRootRelativeAPKPath(package))
158
159def 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
165def 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
178def 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
214if __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