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=""): 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.stdout, linePrefix, sys.stderr)) 125 stdoutJob.start() 126 stderrJob.start() 127 retcode = process.wait() 128 if 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 ] 238 239 ANDROID_JAVA_API = "android-13" 240 NATIVE_LIB_NAME = "libdeqp.so" 241 242 def selectNDKPath (): 243 candidates = [ 244 os.path.expanduser("~/android-ndk-r10c"), 245 "C:/android/android-ndk-r10c", 246 os.environ.get("ANDROID_NDK_PATH", None), # If not defined, return None 247 ] 248 249 ndkPath = selectFirstExistingDir(candidates) 250 251 if ndkPath == None: 252 raise Exception("None of NDK directory candidates exist: %s. Check ANDROID_NDK_PATH in common.py" % candidates) 253 254 return ndkPath 255 256 def noneSafePathJoin (*components): 257 if None in components: 258 return None 259 return os.path.join(*components) 260 261 262 # NDK paths 263 ANDROID_NDK_PATH = selectNDKPath() 264 ANDROID_NDK_HOST_OS = getNDKHostOsName(ANDROID_NDK_PATH) 265 ANDROID_NDK_TOOLCHAIN_VERSION = "r10c" # Toolchain file is selected based on this 266 267 # Native code build settings 268 CMAKE_GENERATOR = selectByOS({ 269 'win32': getWin32Generator(), 270 'other': getUnixGenerator() 271 }) 272 EXTRA_BUILD_ARGS = getExtraBuildArgs(CMAKE_GENERATOR) 273 274 # SDK paths 275 ANDROID_SDK_PATH = selectFirstExistingDir([ 276 os.path.expanduser("~/android-sdk-linux"), 277 os.path.expanduser("~/android-sdk-mac_x86"), 278 "C:/android/android-sdk-windows", 279 ]) 280 ANDROID_BIN = selectFirstExistingBinary([ 281 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android"), 282 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "android.bat"), 283 which('android'), 284 ]) 285 ADB_BIN = selectFirstExistingBinary([ 286 which('adb'), # \note Prefer adb in path to avoid version issues on dev machines 287 noneSafePathJoin(ANDROID_SDK_PATH, "platform-tools", "adb"), 288 noneSafePathJoin(ANDROID_SDK_PATH, "platform-tools", "adb.exe"), 289 ]) 290 ZIPALIGN_BIN = selectFirstExistingBinary([ 291 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign"), 292 noneSafePathJoin(ANDROID_SDK_PATH, "tools", "zipalign.exe"), 293 which('zipalign'), 294 ]) 295 JARSIGNER_BIN = which('jarsigner') 296 297 # Apache ant 298 ANT_BIN = selectFirstExistingBinary([ 299 which('ant'), 300 "C:/android/apache-ant-1.8.4/bin/ant.bat", 301 "C:/android/apache-ant-1.9.2/bin/ant.bat", 302 "C:/android/apache-ant-1.9.3/bin/ant.bat", 303 "C:/android/apache-ant-1.9.4/bin/ant.bat", 304 ]) 305 306 def makeNameValueTuple (name): 307 return (name, str(eval(name))) 308 309 CONFIG_VAR_NAMES = [ 310 "ANDROID_DIR", 311 "NATIVE_LIBS", 312 "ANDROID_JAVA_API", 313 "NATIVE_LIB_NAME", 314 "ANDROID_NDK_PATH", 315 "ANDROID_NDK_HOST_OS", 316 "ANDROID_NDK_TOOLCHAIN_VERSION", 317 "CMAKE_GENERATOR", 318 "EXTRA_BUILD_ARGS", 319 "ANDROID_SDK_PATH", 320 "ANDROID_BIN", 321 "ADB_BIN", 322 "ZIPALIGN_BIN", 323 "JARSIGNER_BIN", 324 "ANT_BIN", 325 ] 326 CONFIG_STRINGS = [makeNameValueTuple(x) for x in CONFIG_VAR_NAMES] 327