xref: /aosp_15_r20/external/deqp/scripts/android/install_apk.py (revision 35238bce31c2a825756842865a792f8cf7f89930)
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 ctsbuild.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.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
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.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
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]).with_traceback(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            '-g',
132            apkPath
133        ], printPrefix)
134    print(printPrefix + "Install complete\n",)
135
136def 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
145def 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
152def installToAllDevices (doParallel, adbPath, packageName, apkPath):
153    devices = getDevices(adbPath)
154    installToDevices(devices, doParallel, adbPath, packageName, apkPath)
155
156def getAPKPath (buildRootPath, target):
157    package = getPackageAndLibrariesForTarget(target)[0]
158    return os.path.join(buildRootPath, getBuildRootRelativeAPKPath(package))
159
160def 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
166def 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
179def 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
215if __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)
250