Rename CTS "build" Python module to "ctsbuild"
[platform/upstream/VK-GL-CTS.git] / scripts / android / install_apk.py
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 ctsbuild.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.decode('utf-8'))
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.decode('utf-8'))
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]).with_traceback(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                         '-g',
132                         apkPath
133                 ], printPrefix)
134         print(printPrefix + "Install complete\n",)
135
136 def installToDevice (device, adbPath, packageName, apkPath, printPrefix=""):
137         if len(printPrefix) == 0:
138                 print("Installing to %s (%s)...\n" % (device.serial, device.model), end='')
139         else:
140                 print(printPrefix + "Installing to %s\n" % device.serial, end='')
141
142         uninstall(adbPath, packageName, ['-s', device.serial], printPrefix)
143         install(adbPath, apkPath, ['-s', device.serial], printPrefix)
144
145 def installToDevices (devices, doParallel, adbPath, packageName, apkPath):
146         padLen = max([len(device.model) for device in devices])+1
147         if doParallel:
148                 parallelApply(installToDevice, [(device, adbPath, packageName, apkPath, ("(%s):%s" % (device.model, ' ' * (padLen - len(device.model))))) for device in devices]);
149         else:
150                 serialApply(installToDevice, [(device, adbPath, packageName, apkPath) for device in devices]);
151
152 def installToAllDevices (doParallel, adbPath, packageName, apkPath):
153         devices = getDevices(adbPath)
154         installToDevices(devices, doParallel, adbPath, packageName, apkPath)
155
156 def getAPKPath (buildRootPath, target):
157         package = getPackageAndLibrariesForTarget(target)[0]
158         return os.path.join(buildRootPath, getBuildRootRelativeAPKPath(package))
159
160 def getPackageName (target):
161         package                 = getPackageAndLibrariesForTarget(target)[0]
162         manifestPath    = os.path.join(DEQP_DIR, "android", package.appDirName, "AndroidManifest.xml")
163
164         return parsePackageName(manifestPath)
165
166 def findADB ():
167         adbInPath = which("adb")
168         if adbInPath != None:
169                 return adbInPath
170
171         sdkPath = findSDK()
172         if sdkPath != None:
173                 adbInSDK = os.path.join(sdkPath, "platform-tools", "adb")
174                 if os.path.isfile(adbInSDK):
175                         return adbInSDK
176
177         return None
178
179 def parseArgs ():
180         defaultADBPath          = findADB()
181         defaultBuildRoot        = getDefaultBuildRoot()
182
183         parser = argparse.ArgumentParser(os.path.basename(__file__),
184                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
185         parser.add_argument('--build-root',
186                 dest='buildRoot',
187                 default=defaultBuildRoot,
188                 help="Root build directory")
189         parser.add_argument('--adb',
190                 dest='adbPath',
191                 default=defaultADBPath,
192                 help="ADB binary path",
193                 required=(True if defaultADBPath == None else False))
194         parser.add_argument('--target',
195                 dest='target',
196                 help='Build target',
197                 choices=['deqp', 'openglcts'],
198                 default='deqp')
199         parser.add_argument('-p', '--parallel',
200                 dest='doParallel',
201                 action="store_true",
202                 help="Install package in parallel")
203         parser.add_argument('-s', '--serial',
204                 dest='serial',
205                 type=str,
206                 nargs='+',
207                 help="Install package to device with serial number")
208         parser.add_argument('-a', '--all',
209                 dest='all',
210                 action="store_true",
211                 help="Install to all devices")
212
213         return parser.parse_args()
214
215 if __name__ == "__main__":
216         args            = parseArgs()
217         packageName     = getPackageName(args.target)
218         apkPath         = getAPKPath(args.buildRoot, args.target)
219
220         if not os.path.isfile(apkPath):
221                 die("%s does not exist" % apkPath)
222
223         if args.all:
224                 installToAllDevices(args.doParallel, args.adbPath, packageName, apkPath)
225         else:
226                 if args.serial == None:
227                         devices = getDevices(args.adbPath)
228                         if len(devices) == 0:
229                                 die('No devices connected')
230                         elif len(devices) == 1:
231                                 installToDevice(devices[0], args.adbPath, packageName, apkPath)
232                         else:
233                                 print("More than one device connected:")
234                                 for i in range(0, len(devices)):
235                                         print("%3d: %16s %s" % ((i+1), devices[i].serial, devices[i].model))
236
237                                 deviceNdx = int(input("Choose device (1-%d): " % len(devices)))
238                                 installToDevice(devices[deviceNdx-1], args.adbPath, packageName, apkPath)
239                 else:
240                         devices = getDevices(args.adbPath)
241
242                         devices = [dev for dev in devices if dev.serial in args.serial]
243                         devSerials = [dev.serial for dev in devices]
244                         notFounds = [serial for serial in args.serial if not serial in devSerials]
245
246                         for notFound in notFounds:
247                                 print("Couldn't find device matching serial '%s'" % notFound)
248
249                         installToDevices(devices, args.doParallel, args.adbPath, packageName, apkPath)