Merge changes I5f7e56e3,I7f88e1da into oreo-cts-dev
[platform/upstream/VK-GL-CTS.git] / android / scripts / common.py
1 # -*- coding: utf-8 -*-
2
3 #-------------------------------------------------------------------------
4 # drawElements Quality Program utilities
5 # --------------------------------------
6 #
7 # Copyright 2015 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 shlex
27 import subprocess
28 import multiprocessing
29 import string
30
31 try:
32         import threading
33 except ImportError:
34         import dummy_threading as threading
35
36 class NativeLib:
37         def __init__ (self, apiVersion, abiVersion, prebuiltDir):
38                 self.apiVersion         = apiVersion
39                 self.abiVersion         = abiVersion
40                 self.prebuiltDir        = prebuiltDir
41
42         def __str__ (self):
43                 return "(API: %s, ABI: %s)" % (self.apiVersion, self.abiVersion)
44
45         def __repr__ (self):
46                 return "(API: %s, ABI: %s)" % (self.apiVersion, self.abiVersion)
47
48
49 def getPlatform ():
50         if sys.platform.startswith('linux'):
51                 return 'linux'
52         else:
53                 return sys.platform
54
55 def selectByOS (variants):
56         platform = getPlatform()
57         if platform in variants:
58                 return variants[platform]
59         elif 'other' in variants:
60                 return variants['other']
61         else:
62                 raise Exception("No configuration for '%s'" % platform)
63
64 def isExecutable (path):
65         return os.path.isfile(path) and os.access(path, os.X_OK)
66
67 def which (binName):
68         for path in os.environ['PATH'].split(os.pathsep):
69                 path = path.strip('"')
70                 fullPath = os.path.join(path, binName)
71                 if isExecutable(fullPath):
72                         return fullPath
73
74         return None
75
76 def isBinaryInPath (binName):
77         return which(binName) != None
78
79 def selectFirstExistingBinary (filenames):
80         for filename in filenames:
81                 if filename != None and isExecutable(filename):
82                         return filename
83
84         return None
85
86 def selectFirstExistingDir (paths):
87         for path in paths:
88                 if path != None and os.path.isdir(path):
89                         return path
90
91         return None
92
93 def die (msg):
94         print msg
95         exit(-1)
96
97 def shellquote(s):
98         return '"%s"' % s.replace('\\', '\\\\').replace('"', '\"').replace('$', '\$').replace('`', '\`')
99
100 def execute (commandLine):
101         args    = shlex.split(commandLine)
102         retcode = subprocess.call(args)
103         if retcode != 0:
104                 raise Exception("Failed to execute '%s', got %d" % (commandLine, retcode))
105
106 def execArgs (args):
107         # Make sure previous stdout prints have been written out.
108         sys.stdout.flush()
109         retcode = subprocess.call(args)
110         if retcode != 0:
111                 raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
112
113 def execArgsInDirectory (args, cwd, linePrefix="", failOnNonZeroExit=True):
114
115         def readApplyPrefixAndPrint (source, prefix, sink):
116                 while True:
117                         line = source.readline()
118                         if len(line) == 0: # EOF
119                                 break;
120                         sink.write(prefix + line)
121
122         process = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
123         stdoutJob = threading.Thread(target=readApplyPrefixAndPrint, args=(process.stdout, linePrefix, sys.stdout))
124         stderrJob = threading.Thread(target=readApplyPrefixAndPrint, args=(process.stderr, linePrefix, sys.stderr))
125         stdoutJob.start()
126         stderrJob.start()
127         retcode = process.wait()
128         if failOnNonZeroExit and retcode != 0:
129                 raise Exception("Failed to execute '%s', got %d" % (str(args), retcode))
130
131 def serialApply(f, argsList):
132         for args in argsList:
133                 f(*args)
134
135 def parallelApply(f, argsList):
136         class ErrorCode:
137                 def __init__ (self):
138                         self.error = None;
139
140         def applyAndCaptureError (func, args, errorCode):
141                 try:
142                         func(*args)
143                 except:
144                         errorCode.error = sys.exc_info()
145
146         errorCode = ErrorCode()
147         jobs = []
148         for args in argsList:
149                 job = threading.Thread(target=applyAndCaptureError, args=(f, args, errorCode))
150                 job.start()
151                 jobs.append(job)
152
153         for job in jobs:
154                 job.join()
155
156         if errorCode.error:
157                 raise errorCode.error[0], errorCode.error[1], errorCode.error[2]
158
159 class Device:
160         def __init__(self, serial, product, model, device):
161                 self.serial             = serial
162                 self.product    = product
163                 self.model              = model
164                 self.device             = device
165
166         def __str__ (self):
167                 return "%s: {product: %s, model: %s, device: %s}" % (self.serial, self.product, self.model, self.device)
168
169 def getDevices (adb):
170         proc = subprocess.Popen([adb, 'devices', '-l'], stdout=subprocess.PIPE)
171         (stdout, stderr) = proc.communicate()
172
173         if proc.returncode != 0:
174                 raise Exception("adb devices -l failed, got %d" % proc.returncode)
175
176         ptrn = re.compile(r'^([a-zA-Z0-9\.:]+)\s+.*product:([^\s]+)\s+model:([^\s]+)\s+device:([^\s]+)')
177         devices = []
178         for line in stdout.splitlines()[1:]:
179                 if len(line.strip()) == 0:
180                         continue
181
182                 m = ptrn.match(line)
183                 if m == None:
184                         print "WARNING: Failed to parse device info '%s'" % line
185                         continue
186
187                 devices.append(Device(m.group(1), m.group(2), m.group(3), m.group(4)))
188
189         return devices
190
191 def getWin32Generator ():
192         if which("jom.exe") != None:
193                 return "NMake Makefiles JOM"
194         else:
195                 return "NMake Makefiles"
196
197 def isNinjaSupported ():
198         return which("ninja") != None
199
200 def getUnixGenerator ():
201         if isNinjaSupported():
202                 return "Ninja"
203         else:
204                 return "Unix Makefiles"
205
206 def getExtraBuildArgs (generator):
207         if generator == "Unix Makefiles":
208                 return ["--", "-j%d" % multiprocessing.cpu_count()]
209         else:
210                 return []
211
212 NDK_HOST_OS_NAMES = [
213         "windows",
214         "windows-x86_64",
215         "darwin-x86",
216         "darwin-x86_64",
217         "linux-x86",
218         "linux-x86_64"
219 ]
220
221 def getNDKHostOsName (ndkPath):
222         for name in NDK_HOST_OS_NAMES:
223                 if os.path.exists(os.path.join(ndkPath, "prebuilt", name)):
224                         return name
225
226         raise Exception("Couldn't determine NDK host OS")
227
228 # deqp/android path
229 ANDROID_DIR                             = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
230
231 # Build configuration
232 NATIVE_LIBS                             = [
233                 #                 API           ABI                             prebuiltsDir
234                 NativeLib(13,           "armeabi-v7a",  'android-arm'),         # ARM v7a ABI
235                 NativeLib(13,           "x86",                  'android-x86'),         # x86
236                 NativeLib(21,           "arm64-v8a",    'android-arm64'),       # ARM64 v8a ABI
237                 NativeLib(21,           "x86_64",               'android-x86_64'),      # x86_64
238         ]
239
240 ANDROID_JAVA_API                = "android-22"
241 NATIVE_LIB_NAME                 = "libdeqp.so"
242
243 def makeNdkVersionString (version):
244         minorVersionString = (chr(ord('a') + version[1]) if version[1] > 0 else "")
245         return "r%d%s" % (version[0], minorVersionString)
246
247 ANDROID_NDK_VERSION                     = (11,0)
248 ANDROID_NDK_VERSION_STRING      = makeNdkVersionString(ANDROID_NDK_VERSION)
249 def selectNDKPath ():
250         candidates =  [
251                 os.path.expanduser("~/android-ndk-" + ANDROID_NDK_VERSION_STRING),
252                 "C:/android/android-ndk-" + ANDROID_NDK_VERSION_STRING,
253                 os.environ.get("ANDROID_NDK_PATH", None), # If not defined, return None
254         ]
255
256         ndkPath = selectFirstExistingDir(candidates)
257
258         if ndkPath == None:
259                 raise Exception("None of NDK directory candidates exist: %s. Check ANDROID_NDK_PATH in common.py" % candidates)
260
261         return ndkPath
262
263 def noneSafePathJoin (*components):
264         if None in components:
265                 return None
266         return os.path.join(*components)
267
268
269 # NDK paths
270 ANDROID_NDK_PATH                                = selectNDKPath()
271 ANDROID_NDK_HOST_OS                             = getNDKHostOsName(ANDROID_NDK_PATH)
272 ANDROID_NDK_TOOLCHAIN_VERSION   = "r11" # Toolchain file is selected based on this
273
274 # Native code build settings
275 CMAKE_GENERATOR                 = selectByOS({
276                 'win32':        getWin32Generator(),
277                 'other':        getUnixGenerator()
278         })
279 EXTRA_BUILD_ARGS                = getExtraBuildArgs(CMAKE_GENERATOR)
280
281 # SDK paths
282 ANDROID_SDK_PATH                = selectFirstExistingDir([
283                 os.environ.get("ANDROID_SDK_PATH", None),
284                 os.path.expanduser("~/android-sdk-linux"),
285                 os.path.expanduser("~/android-sdk-mac_x86"),
286                 "C:/android/android-sdk-windows",
287         ])
288 ANDROID_BIN                             = selectFirstExistingBinary([
289                 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android"),
290                 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android.bat"),
291                 which('android'),
292         ])
293 ADB_BIN                                 = selectFirstExistingBinary([
294                 which('adb'), # \note Prefer adb in path to avoid version issues on dev machines
295                 noneSafePathJoin(ANDROID_SDK_PATH, "platform-tools", "adb"),
296                 noneSafePathJoin(ANDROID_SDK_PATH, "platform-tools", "adb.exe"),
297         ])
298 ZIPALIGN_BIN                    = selectFirstExistingBinary([
299                 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign"),
300                 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign.exe"),
301                 which('zipalign'),
302         ])
303 JARSIGNER_BIN                   = which('jarsigner')
304
305 # Apache ant
306 ANT_BIN                                 = selectFirstExistingBinary([
307                 which('ant'),
308                 "C:/android/apache-ant-1.8.4/bin/ant.bat",
309                 "C:/android/apache-ant-1.9.2/bin/ant.bat",
310                 "C:/android/apache-ant-1.9.3/bin/ant.bat",
311                 "C:/android/apache-ant-1.9.4/bin/ant.bat",
312         ])
313
314 def makeNameValueTuple (name):
315         return (name, str(eval(name)))
316
317 CONFIG_VAR_NAMES = [
318                 "ANDROID_DIR",
319                 "NATIVE_LIBS",
320                 "ANDROID_JAVA_API",
321                 "NATIVE_LIB_NAME",
322                 "ANDROID_NDK_PATH",
323                 "ANDROID_NDK_HOST_OS",
324                 "ANDROID_NDK_TOOLCHAIN_VERSION",
325                 "CMAKE_GENERATOR",
326                 "EXTRA_BUILD_ARGS",
327                 "ANDROID_SDK_PATH",
328                 "ANDROID_BIN",
329                 "ADB_BIN",
330                 "ZIPALIGN_BIN",
331                 "JARSIGNER_BIN",
332                 "ANT_BIN",
333         ]
334 CONFIG_STRINGS = [makeNameValueTuple(x) for x in CONFIG_VAR_NAMES]