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 23import os 24import re 25import sys 26import shlex 27import subprocess 28import multiprocessing 29import string 30 31try: 32 import threading 33except ImportError: 34 import dummy_threading as threading 35 36class 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 49def getPlatform (): 50 if sys.platform.startswith('linux'): 51 return 'linux' 52 else: 53 return sys.platform 54 55def 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 64def isExecutable (path): 65 return os.path.isfile(path) and os.access(path, os.X_OK) 66 67def 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 76def isBinaryInPath (binName): 77 return which(binName) != None 78 79def selectFirstExistingBinary (filenames): 80 for filename in filenames: 81 if filename != None and isExecutable(filename): 82 return filename 83 84 return None 85 86def selectFirstExistingDir (paths): 87 for path in paths: 88 if path != None and os.path.isdir(path): 89 return path 90 91 return None 92 93def die (msg): 94 print msg 95 exit(-1) 96 97def shellquote(s): 98 return '"%s"' % s.replace('\\', '\\\\').replace('"', '\"').replace('$', '\$').replace('`', '\`') 99 100def 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 106def 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 113def 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 131def serialApply(f, argsList): 132 for args in argsList: 133 f(*args) 134 135def 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 159class 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 169def 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 191def getWin32Generator (): 192 if which("jom.exe") != None: 193 return "NMake Makefiles JOM" 194 else: 195 return "NMake Makefiles" 196 197def isNinjaSupported (): 198 return which("ninja") != None 199 200def getUnixGenerator (): 201 if isNinjaSupported(): 202 return "Ninja" 203 else: 204 return "Unix Makefiles" 205 206def getExtraBuildArgs (generator): 207 if generator == "Unix Makefiles": 208 return ["--", "-j%d" % multiprocessing.cpu_count()] 209 else: 210 return [] 211 212NDK_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 221def 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 229ANDROID_DIR = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) 230 231# Build configuration 232NATIVE_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 240ANDROID_JAVA_API = "android-22" 241NATIVE_LIB_NAME = "libdeqp.so" 242 243def makeNdkVersionString (version): 244 minorVersionString = (chr(ord('a') + version[1]) if version[1] > 0 else "") 245 return "r%d%s" % (version[0], minorVersionString) 246 247ANDROID_NDK_VERSION = (11,0) 248ANDROID_NDK_VERSION_STRING = makeNdkVersionString(ANDROID_NDK_VERSION) 249def 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 263def noneSafePathJoin (*components): 264 if None in components: 265 return None 266 return os.path.join(*components) 267 268 269# NDK paths 270ANDROID_NDK_PATH = selectNDKPath() 271ANDROID_NDK_HOST_OS = getNDKHostOsName(ANDROID_NDK_PATH) 272ANDROID_NDK_TOOLCHAIN_VERSION = "r11" # Toolchain file is selected based on this 273 274# Native code build settings 275CMAKE_GENERATOR = selectByOS({ 276 'win32': getWin32Generator(), 277 'other': getUnixGenerator() 278 }) 279EXTRA_BUILD_ARGS = getExtraBuildArgs(CMAKE_GENERATOR) 280 281# SDK paths 282ANDROID_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 ]) 288ANDROID_BIN = selectFirstExistingBinary([ 289 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android"), 290 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android.bat"), 291 which('android'), 292 ]) 293ADB_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 ]) 298ZIPALIGN_BIN = selectFirstExistingBinary([ 299 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign"), 300 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign.exe"), 301 which('zipalign'), 302 ]) 303JARSIGNER_BIN = which('jarsigner') 304 305# Apache ant 306ANT_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 314def makeNameValueTuple (name): 315 return (name, str(eval(name))) 316 317CONFIG_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 ] 334CONFIG_STRINGS = [makeNameValueTuple(x) for x in CONFIG_VAR_NAMES] 335