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# \todo [2017-04-10 pyry] 24# * Use smarter asset copy in main build 25# * cmake -E copy_directory doesn't copy timestamps which will cause 26# assets to be always re-packaged 27# * Consider adding an option for downloading SDK & NDK 28 29import os 30import re 31import sys 32import string 33import shutil 34import argparse 35import tempfile 36import xml.etree.ElementTree 37 38# Import from <root>/scripts 39sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 40 41from build.common import * 42from build.config import * 43from build.build import * 44 45class SDKEnv: 46 def __init__(self, path): 47 self.path = path 48 self.buildToolsVersion = SDKEnv.selectBuildToolsVersion(self.path) 49 50 @staticmethod 51 def getBuildToolsVersions (path): 52 buildToolsPath = os.path.join(path, "build-tools") 53 versions = [] 54 55 if os.path.exists(buildToolsPath): 56 for item in os.listdir(buildToolsPath): 57 m = re.match(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$', item) 58 if m != None: 59 versions.append((int(m.group(1)), int(m.group(2)), int(m.group(3)))) 60 61 return versions 62 63 @staticmethod 64 def selectBuildToolsVersion (path): 65 preferred = [(25, 0, 2)] 66 versions = SDKEnv.getBuildToolsVersions(path) 67 68 if len(versions) == 0: 69 return (0,0,0) 70 71 for candidate in preferred: 72 if candidate in versions: 73 return candidate 74 75 # Pick newest 76 versions.sort() 77 return versions[-1] 78 79 def getPlatformLibrary (self, apiVersion): 80 return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar") 81 82 def getBuildToolsPath (self): 83 return os.path.join(self.path, "build-tools", "%d.%d.%d" % self.buildToolsVersion) 84 85class NDKEnv: 86 def __init__(self, path): 87 self.path = path 88 self.version = NDKEnv.detectVersion(self.path) 89 self.hostOsName = NDKEnv.detectHostOsName(self.path) 90 91 @staticmethod 92 def getKnownAbis (): 93 return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"] 94 95 @staticmethod 96 def getAbiPrebuiltsName (abiName): 97 prebuilts = { 98 "armeabi-v7a": 'android-arm', 99 "arm64-v8a": 'android-arm64', 100 "x86": 'android-x86', 101 "x86_64": 'android-x86_64', 102 } 103 104 if not abiName in prebuilts: 105 raise Exception("Unknown ABI: " + abiName) 106 107 return prebuilts[abiName] 108 109 @staticmethod 110 def detectVersion (path): 111 propFilePath = os.path.join(path, "source.properties") 112 try: 113 with open(propFilePath) as propFile: 114 for line in propFile: 115 keyValue = map(lambda x: string.strip(x), line.split("=")) 116 if keyValue[0] == "Pkg.Revision": 117 versionParts = keyValue[1].split(".") 118 return tuple(map(int, versionParts[0:2])) 119 except Exception as e: 120 raise Exception("Failed to read source prop file '%s': %s" % (propFilePath, str(e))) 121 except: 122 raise Exception("Failed to read source prop file '%s': unkown error") 123 124 raise Exception("Failed to detect NDK version (does %s/source.properties have Pkg.Revision?)" % path) 125 126 @staticmethod 127 def isHostOsSupported (hostOsName): 128 os = HostInfo.getOs() 129 bits = HostInfo.getArchBits() 130 hostOsParts = hostOsName.split('-') 131 132 if len(hostOsParts) > 1: 133 assert(len(hostOsParts) == 2) 134 assert(hostOsParts[1] == "x86_64") 135 136 if bits != 64: 137 return False 138 139 if os == HostInfo.OS_WINDOWS: 140 return hostOsParts[0] == 'windows' 141 elif os == HostInfo.OS_LINUX: 142 return hostOsParts[0] == 'linux' 143 elif os == HostInfo.OS_OSX: 144 return hostOsParts[0] == 'darwin' 145 else: 146 raise Exception("Unhandled HostInfo.getOs() '%d'" % os) 147 148 @staticmethod 149 def detectHostOsName (path): 150 hostOsNames = [ 151 "windows", 152 "windows-x86_64", 153 "darwin-x86", 154 "darwin-x86_64", 155 "linux-x86", 156 "linux-x86_64" 157 ] 158 159 for name in hostOsNames: 160 if os.path.exists(os.path.join(path, "prebuilt", name)): 161 return name 162 163 raise Exception("Failed to determine NDK host OS") 164 165class Environment: 166 def __init__(self, sdk, ndk): 167 self.sdk = sdk 168 self.ndk = ndk 169 170class Configuration: 171 def __init__(self, env, buildPath, abis, nativeApi, nativeBuildType, gtfTarget, verbose): 172 self.env = env 173 self.sourcePath = DEQP_DIR 174 self.buildPath = buildPath 175 self.abis = abis 176 self.nativeApi = nativeApi 177 self.javaApi = 22 178 self.nativeBuildType = nativeBuildType 179 self.gtfTarget = gtfTarget 180 self.verbose = verbose 181 self.cmakeGenerator = selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR]) 182 183 def check (self): 184 if self.cmakeGenerator == None: 185 raise Exception("Failed to find build tools for CMake") 186 187 if not os.path.exists(self.env.ndk.path): 188 raise Exception("Android NDK not found at %s" % self.env.ndk.path) 189 190 if not NDKEnv.isHostOsSupported(self.env.ndk.hostOsName): 191 raise Exception("NDK '%s' is not supported on this machine" % self.env.ndk.hostOsName) 192 193 supportedNDKVersion = [11, 15] 194 if self.env.ndk.version[0] not in supportedNDKVersion: 195 raise Exception("Android NDK version %d is not supported; build requires NDK version %s" % (self.env.ndk.version[0], supportedNDKVersion)) 196 197 # https://gitlab.khronos.org/Tracker/vk-gl-cts/issues/723 198 if self.env.ndk.version[0] == 15: 199 if "armeabi-v7a" in self.abis: 200 raise Exception("dEQP is incompatible with NDK r15 for armeabi-v7a") 201 else: 202 print >> sys.stderr, "WARNING: Support for NDK r15 is experimental; NDK r11c is recommended for official submissions" 203 204 if self.env.sdk.buildToolsVersion == (0,0,0): 205 raise Exception("No build tools directory found at %s" % os.path.join(self.env.sdk.path, "build-tools")) 206 207 androidBuildTools = ["aapt", "zipalign", "dx"] 208 for tool in androidBuildTools: 209 if which(tool, [self.env.sdk.getBuildToolsPath()]) == None: 210 raise Exception("Missing Android build tool: %s" % toolPath) 211 212 requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"] 213 for tool in requiredToolsInPath: 214 if which(tool) == None: 215 raise Exception("%s not in PATH" % tool) 216 217def log (config, msg): 218 if config.verbose: 219 print msg 220 221def executeAndLog (config, args): 222 if config.verbose: 223 print " ".join(args) 224 execute(args) 225 226# Path components 227 228class ResolvablePathComponent: 229 def __init__ (self): 230 pass 231 232class SourceRoot (ResolvablePathComponent): 233 def resolve (self, config): 234 return config.sourcePath 235 236class BuildRoot (ResolvablePathComponent): 237 def resolve (self, config): 238 return config.buildPath 239 240class NativeBuildPath (ResolvablePathComponent): 241 def __init__ (self, abiName): 242 self.abiName = abiName 243 244 def resolve (self, config): 245 return getNativeBuildPath(config, self.abiName) 246 247class GeneratedResSourcePath (ResolvablePathComponent): 248 def __init__ (self, package): 249 self.package = package 250 251 def resolve (self, config): 252 packageComps = self.package.getPackageName(config).split('.') 253 packageDir = os.path.join(*packageComps) 254 255 return os.path.join(config.buildPath, self.package.getAppDirName(), "src", packageDir, "R.java") 256 257def resolvePath (config, path): 258 resolvedComps = [] 259 260 for component in path: 261 if isinstance(component, ResolvablePathComponent): 262 resolvedComps.append(component.resolve(config)) 263 else: 264 resolvedComps.append(str(component)) 265 266 return os.path.join(*resolvedComps) 267 268def resolvePaths (config, paths): 269 return list(map(lambda p: resolvePath(config, p), paths)) 270 271class BuildStep: 272 def __init__ (self): 273 pass 274 275 def getInputs (self): 276 return [] 277 278 def getOutputs (self): 279 return [] 280 281 @staticmethod 282 def expandPathsToFiles (paths): 283 """ 284 Expand mixed list of file and directory paths into a flattened list 285 of files. Any non-existent input paths are preserved as is. 286 """ 287 288 def getFiles (dirPath): 289 for root, dirs, files in os.walk(dirPath): 290 for file in files: 291 yield os.path.join(root, file) 292 293 files = [] 294 for path in paths: 295 if os.path.isdir(path): 296 files += list(getFiles(path)) 297 else: 298 files.append(path) 299 300 return files 301 302 def isUpToDate (self, config): 303 inputs = resolvePaths(config, self.getInputs()) 304 outputs = resolvePaths(config, self.getOutputs()) 305 306 assert len(inputs) > 0 and len(outputs) > 0 307 308 expandedInputs = BuildStep.expandPathsToFiles(inputs) 309 expandedOutputs = BuildStep.expandPathsToFiles(outputs) 310 311 existingInputs = filter(os.path.exists, expandedInputs) 312 existingOutputs = filter(os.path.exists, expandedOutputs) 313 314 if len(existingInputs) != len(expandedInputs): 315 for file in expandedInputs: 316 if file not in existingInputs: 317 print "ERROR: Missing input file: %s" % file 318 die("Missing input files") 319 320 if len(existingOutputs) != len(expandedOutputs): 321 return False # One or more output files are missing 322 323 lastInputChange = max(map(os.path.getmtime, existingInputs)) 324 firstOutputChange = min(map(os.path.getmtime, existingOutputs)) 325 326 return lastInputChange <= firstOutputChange 327 328 def update (config): 329 die("BuildStep.update() not implemented") 330 331def getNativeBuildPath (config, abiName): 332 return os.path.join(config.buildPath, "%s-%s-%d" % (abiName, config.nativeBuildType, config.nativeApi)) 333 334def buildNativeLibrary (config, abiName): 335 def makeNDKVersionString (version): 336 minorVersionString = (chr(ord('a') + version[1]) if version[1] > 0 else "") 337 return "r%d%s" % (version[0], minorVersionString) 338 339 def getBuildArgs (config, abiName): 340 toolchain = 'ndk-%s' % makeNDKVersionString((config.env.ndk.version[0], 0)) 341 return ['-DDEQP_TARGET=android', 342 '-DDEQP_TARGET_TOOLCHAIN=%s' % toolchain, 343 '-DCMAKE_C_FLAGS=-Werror', 344 '-DCMAKE_CXX_FLAGS=-Werror', 345 '-DANDROID_NDK_HOST_OS=%s' % config.env.ndk.hostOsName, 346 '-DANDROID_NDK_PATH=%s' % config.env.ndk.path, 347 '-DANDROID_ABI=%s' % abiName, 348 '-DDE_ANDROID_API=%s' % config.nativeApi, 349 '-DGLCTS_GTF_TARGET=%s' % config.gtfTarget] 350 351 nativeBuildPath = getNativeBuildPath(config, abiName) 352 buildConfig = BuildConfig(nativeBuildPath, config.nativeBuildType, getBuildArgs(config, abiName)) 353 354 build(buildConfig, config.cmakeGenerator, ["deqp"]) 355 356def executeSteps (config, steps): 357 for step in steps: 358 if not step.isUpToDate(config): 359 step.update(config) 360 361def parsePackageName (manifestPath): 362 tree = xml.etree.ElementTree.parse(manifestPath) 363 364 if not 'package' in tree.getroot().attrib: 365 raise Exception("'package' attribute missing from root element in %s" % manifestPath) 366 367 return tree.getroot().attrib['package'] 368 369class PackageDescription: 370 def __init__ (self, appDirName, appName, hasResources = True): 371 self.appDirName = appDirName 372 self.appName = appName 373 self.hasResources = hasResources 374 375 def getAppName (self): 376 return self.appName 377 378 def getAppDirName (self): 379 return self.appDirName 380 381 def getPackageName (self, config): 382 manifestPath = resolvePath(config, self.getManifestPath()) 383 384 return parsePackageName(manifestPath) 385 386 def getManifestPath (self): 387 return [SourceRoot(), "android", self.appDirName, "AndroidManifest.xml"] 388 389 def getResPath (self): 390 return [SourceRoot(), "android", self.appDirName, "res"] 391 392 def getSourcePaths (self): 393 return [ 394 [SourceRoot(), "android", self.appDirName, "src"] 395 ] 396 397 def getAssetsPath (self): 398 return [BuildRoot(), self.appDirName, "assets"] 399 400 def getClassesJarPath (self): 401 return [BuildRoot(), self.appDirName, "bin", "classes.jar"] 402 403 def getClassesDexPath (self): 404 return [BuildRoot(), self.appDirName, "bin", "classes.dex"] 405 406 def getAPKPath (self): 407 return [BuildRoot(), self.appDirName, "bin", self.appName + ".apk"] 408 409# Build step implementations 410 411class BuildNativeLibrary (BuildStep): 412 def __init__ (self, abi): 413 self.abi = abi 414 415 def isUpToDate (self, config): 416 return False 417 418 def update (self, config): 419 log(config, "BuildNativeLibrary: %s" % self.abi) 420 buildNativeLibrary(config, self.abi) 421 422class GenResourcesSrc (BuildStep): 423 def __init__ (self, package): 424 self.package = package 425 426 def getInputs (self): 427 return [self.package.getResPath(), self.package.getManifestPath()] 428 429 def getOutputs (self): 430 return [[GeneratedResSourcePath(self.package)]] 431 432 def update (self, config): 433 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) 434 dstDir = os.path.dirname(resolvePath(config, [GeneratedResSourcePath(self.package)])) 435 436 if not os.path.exists(dstDir): 437 os.makedirs(dstDir) 438 439 executeAndLog(config, [ 440 aaptPath, 441 "package", 442 "-f", 443 "-m", 444 "-S", resolvePath(config, self.package.getResPath()), 445 "-M", resolvePath(config, self.package.getManifestPath()), 446 "-J", resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "src"]), 447 "-I", config.env.sdk.getPlatformLibrary(config.javaApi) 448 ]) 449 450# Builds classes.jar from *.java files 451class BuildJavaSource (BuildStep): 452 def __init__ (self, package, libraries = []): 453 self.package = package 454 self.libraries = libraries 455 456 def getSourcePaths (self): 457 srcPaths = self.package.getSourcePaths() 458 459 if self.package.hasResources: 460 srcPaths.append([BuildRoot(), self.package.getAppDirName(), "src"]) # Generated sources 461 462 return srcPaths 463 464 def getInputs (self): 465 inputs = self.getSourcePaths() 466 467 for lib in self.libraries: 468 inputs.append(lib.getClassesJarPath()) 469 470 return inputs 471 472 def getOutputs (self): 473 return [self.package.getClassesJarPath()] 474 475 def update (self, config): 476 srcPaths = resolvePaths(config, self.getSourcePaths()) 477 srcFiles = BuildStep.expandPathsToFiles(srcPaths) 478 jarPath = resolvePath(config, self.package.getClassesJarPath()) 479 objPath = resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "obj"]) 480 classPaths = [objPath] + [resolvePath(config, lib.getClassesJarPath()) for lib in self.libraries] 481 pathSep = ";" if HostInfo.getOs() == HostInfo.OS_WINDOWS else ":" 482 483 if os.path.exists(objPath): 484 shutil.rmtree(objPath) 485 486 os.makedirs(objPath) 487 488 for srcFile in srcFiles: 489 executeAndLog(config, [ 490 "javac", 491 "-source", "1.7", 492 "-target", "1.7", 493 "-d", objPath, 494 "-bootclasspath", config.env.sdk.getPlatformLibrary(config.javaApi), 495 "-classpath", pathSep.join(classPaths), 496 "-sourcepath", pathSep.join(srcPaths), 497 srcFile 498 ]) 499 500 if not os.path.exists(os.path.dirname(jarPath)): 501 os.makedirs(os.path.dirname(jarPath)) 502 503 try: 504 pushWorkingDir(objPath) 505 executeAndLog(config, [ 506 "jar", 507 "cf", 508 jarPath, 509 "." 510 ]) 511 finally: 512 popWorkingDir() 513 514class BuildDex (BuildStep): 515 def __init__ (self, package, libraries): 516 self.package = package 517 self.libraries = libraries 518 519 def getInputs (self): 520 return [self.package.getClassesJarPath()] + [lib.getClassesJarPath() for lib in self.libraries] 521 522 def getOutputs (self): 523 return [self.package.getClassesDexPath()] 524 525 def update (self, config): 526 dxPath = which("dx", [config.env.sdk.getBuildToolsPath()]) 527 srcPaths = resolvePaths(config, self.getInputs()) 528 dexPath = resolvePath(config, self.package.getClassesDexPath()) 529 jarPaths = [resolvePath(config, self.package.getClassesJarPath())] 530 531 for lib in self.libraries: 532 jarPaths.append(resolvePath(config, lib.getClassesJarPath())) 533 534 executeAndLog(config, [ 535 dxPath, 536 "--dex", 537 "--output", dexPath 538 ] + jarPaths) 539 540class CreateKeystore (BuildStep): 541 def __init__ (self): 542 self.keystorePath = [BuildRoot(), "debug.keystore"] 543 544 def getOutputs (self): 545 return [self.keystorePath] 546 547 def isUpToDate (self, config): 548 return os.path.exists(resolvePath(config, self.keystorePath)) 549 550 def update (self, config): 551 executeAndLog(config, [ 552 "keytool", 553 "-genkey", 554 "-keystore", resolvePath(config, self.keystorePath), 555 "-storepass", "android", 556 "-alias", "androiddebugkey", 557 "-keypass", "android", 558 "-keyalg", "RSA", 559 "-keysize", "2048", 560 "-validity", "10000", 561 "-dname", "CN=, OU=, O=, L=, S=, C=", 562 ]) 563 564# Builds APK without code 565class BuildBaseAPK (BuildStep): 566 def __init__ (self, package, libraries = []): 567 self.package = package 568 self.libraries = libraries 569 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "base.apk"] 570 571 def getResPaths (self): 572 paths = [] 573 for pkg in [self.package] + self.libraries: 574 if pkg.hasResources: 575 paths.append(pkg.getResPath()) 576 return paths 577 578 def getInputs (self): 579 return [self.package.getManifestPath()] + self.getResPaths() 580 581 def getOutputs (self): 582 return [self.dstPath] 583 584 def update (self, config): 585 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) 586 dstPath = resolvePath(config, self.dstPath) 587 588 if not os.path.exists(os.path.dirname(dstPath)): 589 os.makedirs(os.path.dirname(dstPath)) 590 591 args = [ 592 aaptPath, 593 "package", 594 "-f", 595 "-M", resolvePath(config, self.package.getManifestPath()), 596 "-I", config.env.sdk.getPlatformLibrary(config.javaApi), 597 "-F", dstPath, 598 ] 599 600 for resPath in self.getResPaths(): 601 args += ["-S", resolvePath(config, resPath)] 602 603 if config.verbose: 604 args.append("-v") 605 606 executeAndLog(config, args) 607 608def addFilesToAPK (config, apkPath, baseDir, relFilePaths): 609 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) 610 maxBatchSize = 25 611 612 pushWorkingDir(baseDir) 613 try: 614 workQueue = list(relFilePaths) 615 616 while len(workQueue) > 0: 617 batchSize = min(len(workQueue), maxBatchSize) 618 items = workQueue[0:batchSize] 619 620 executeAndLog(config, [ 621 aaptPath, 622 "add", 623 "-f", apkPath, 624 ] + items) 625 626 del workQueue[0:batchSize] 627 finally: 628 popWorkingDir() 629 630def addFileToAPK (config, apkPath, baseDir, relFilePath): 631 addFilesToAPK(config, apkPath, baseDir, [relFilePath]) 632 633class AddJavaToAPK (BuildStep): 634 def __init__ (self, package): 635 self.package = package 636 self.srcPath = BuildBaseAPK(self.package).getOutputs()[0] 637 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-java.apk"] 638 639 def getInputs (self): 640 return [ 641 self.srcPath, 642 self.package.getClassesDexPath(), 643 ] 644 645 def getOutputs (self): 646 return [self.dstPath] 647 648 def update (self, config): 649 srcPath = resolvePath(config, self.srcPath) 650 dstPath = resolvePath(config, self.getOutputs()[0]) 651 dexPath = resolvePath(config, self.package.getClassesDexPath()) 652 653 shutil.copyfile(srcPath, dstPath) 654 addFileToAPK(config, dstPath, os.path.dirname(dexPath), os.path.basename(dexPath)) 655 656class AddAssetsToAPK (BuildStep): 657 def __init__ (self, package, abi): 658 self.package = package 659 self.buildPath = [NativeBuildPath(abi)] 660 self.srcPath = AddJavaToAPK(self.package).getOutputs()[0] 661 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-assets.apk"] 662 663 def getInputs (self): 664 return [ 665 self.srcPath, 666 self.buildPath + ["assets"] 667 ] 668 669 def getOutputs (self): 670 return [self.dstPath] 671 672 @staticmethod 673 def getAssetFiles (buildPath): 674 allFiles = BuildStep.expandPathsToFiles([os.path.join(buildPath, "assets")]) 675 return [os.path.relpath(p, buildPath) for p in allFiles] 676 677 def update (self, config): 678 srcPath = resolvePath(config, self.srcPath) 679 dstPath = resolvePath(config, self.getOutputs()[0]) 680 buildPath = resolvePath(config, self.buildPath) 681 assetFiles = AddAssetsToAPK.getAssetFiles(buildPath) 682 683 shutil.copyfile(srcPath, dstPath) 684 685 addFilesToAPK(config, dstPath, buildPath, assetFiles) 686 687class AddNativeLibsToAPK (BuildStep): 688 def __init__ (self, package, abis): 689 self.package = package 690 self.abis = abis 691 self.srcPath = AddAssetsToAPK(self.package, "").getOutputs()[0] 692 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-native-libs.apk"] 693 694 def getInputs (self): 695 paths = [self.srcPath] 696 for abi in self.abis: 697 paths.append([NativeBuildPath(abi), "libdeqp.so"]) 698 return paths 699 700 def getOutputs (self): 701 return [self.dstPath] 702 703 def update (self, config): 704 srcPath = resolvePath(config, self.srcPath) 705 dstPath = resolvePath(config, self.getOutputs()[0]) 706 pkgPath = resolvePath(config, [BuildRoot(), self.package.getAppDirName()]) 707 libFiles = [] 708 709 # Create right directory structure first 710 for abi in self.abis: 711 libSrcPath = resolvePath(config, [NativeBuildPath(abi), "libdeqp.so"]) 712 libRelPath = os.path.join("lib", abi, "libdeqp.so") 713 libAbsPath = os.path.join(pkgPath, libRelPath) 714 715 if not os.path.exists(os.path.dirname(libAbsPath)): 716 os.makedirs(os.path.dirname(libAbsPath)) 717 718 shutil.copyfile(libSrcPath, libAbsPath) 719 libFiles.append(libRelPath) 720 721 shutil.copyfile(srcPath, dstPath) 722 addFilesToAPK(config, dstPath, pkgPath, libFiles) 723 724class SignAPK (BuildStep): 725 def __init__ (self, package): 726 self.package = package 727 self.srcPath = AddNativeLibsToAPK(self.package, []).getOutputs()[0] 728 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "signed.apk"] 729 self.keystorePath = CreateKeystore().getOutputs()[0] 730 731 def getInputs (self): 732 return [self.srcPath, self.keystorePath] 733 734 def getOutputs (self): 735 return [self.dstPath] 736 737 def update (self, config): 738 srcPath = resolvePath(config, self.srcPath) 739 dstPath = resolvePath(config, self.dstPath) 740 741 executeAndLog(config, [ 742 "jarsigner", 743 "-keystore", resolvePath(config, self.keystorePath), 744 "-storepass", "android", 745 "-keypass", "android", 746 "-signedjar", dstPath, 747 srcPath, 748 "androiddebugkey" 749 ]) 750 751def getBuildRootRelativeAPKPath (package): 752 return os.path.join(package.getAppDirName(), package.getAppName() + ".apk") 753 754class FinalizeAPK (BuildStep): 755 def __init__ (self, package): 756 self.package = package 757 self.srcPath = SignAPK(self.package).getOutputs()[0] 758 self.dstPath = [BuildRoot(), getBuildRootRelativeAPKPath(self.package)] 759 self.keystorePath = CreateKeystore().getOutputs()[0] 760 761 def getInputs (self): 762 return [self.srcPath] 763 764 def getOutputs (self): 765 return [self.dstPath] 766 767 def update (self, config): 768 srcPath = resolvePath(config, self.srcPath) 769 dstPath = resolvePath(config, self.dstPath) 770 zipalignPath = os.path.join(config.env.sdk.getBuildToolsPath(), "zipalign") 771 772 executeAndLog(config, [ 773 zipalignPath, 774 "-f", "4", 775 srcPath, 776 dstPath 777 ]) 778 779def getBuildStepsForPackage (abis, package, libraries = []): 780 steps = [] 781 782 assert len(abis) > 0 783 784 # Build native code first 785 for abi in abis: 786 steps += [BuildNativeLibrary(abi)] 787 788 # Build library packages 789 for library in libraries: 790 if library.hasResources: 791 steps.append(GenResourcesSrc(library)) 792 steps.append(BuildJavaSource(library)) 793 794 # Build main package .java sources 795 if package.hasResources: 796 steps.append(GenResourcesSrc(package)) 797 steps.append(BuildJavaSource(package, libraries)) 798 steps.append(BuildDex(package, libraries)) 799 800 # Build base APK 801 steps.append(BuildBaseAPK(package, libraries)) 802 steps.append(AddJavaToAPK(package)) 803 804 # Add assets from first ABI 805 steps.append(AddAssetsToAPK(package, abis[0])) 806 807 # Add native libs to APK 808 steps.append(AddNativeLibsToAPK(package, abis)) 809 810 # Finalize APK 811 steps.append(CreateKeystore()) 812 steps.append(SignAPK(package)) 813 steps.append(FinalizeAPK(package)) 814 815 return steps 816 817def getPackageAndLibrariesForTarget (target): 818 deqpPackage = PackageDescription("package", "dEQP") 819 ctsPackage = PackageDescription("openglcts", "Khronos-CTS", hasResources = False) 820 821 if target == 'deqp': 822 return (deqpPackage, []) 823 elif target == 'openglcts': 824 return (ctsPackage, [deqpPackage]) 825 else: 826 raise Exception("Uknown target '%s'" % target) 827 828def findNDK (): 829 ndkBuildPath = which('ndk-build') 830 if ndkBuildPath != None: 831 return os.path.dirname(ndkBuildPath) 832 else: 833 return None 834 835def findSDK (): 836 sdkBuildPath = which('android') 837 if sdkBuildPath != None: 838 return os.path.dirname(os.path.dirname(sdkBuildPath)) 839 else: 840 return None 841 842def getDefaultBuildRoot (): 843 return os.path.join(tempfile.gettempdir(), "deqp-android-build") 844 845def parseArgs (): 846 nativeBuildTypes = ['Release', 'Debug', 'MinSizeRel', 'RelWithAsserts', 'RelWithDebInfo'] 847 defaultNDKPath = findNDK() 848 defaultSDKPath = findSDK() 849 defaultBuildRoot = getDefaultBuildRoot() 850 851 parser = argparse.ArgumentParser(os.path.basename(__file__), 852 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 853 parser.add_argument('--native-build-type', 854 dest='nativeBuildType', 855 default="RelWithAsserts", 856 choices=nativeBuildTypes, 857 help="Native code build type") 858 parser.add_argument('--build-root', 859 dest='buildRoot', 860 default=defaultBuildRoot, 861 help="Root build directory") 862 parser.add_argument('--abis', 863 dest='abis', 864 default=",".join(NDKEnv.getKnownAbis()), 865 help="ABIs to build") 866 parser.add_argument('--native-api', 867 type=int, 868 dest='nativeApi', 869 default=21, 870 help="Android API level to target in native code") 871 parser.add_argument('--sdk', 872 dest='sdkPath', 873 default=defaultSDKPath, 874 help="Android SDK path", 875 required=(True if defaultSDKPath == None else False)) 876 parser.add_argument('--ndk', 877 dest='ndkPath', 878 default=defaultNDKPath, 879 help="Android NDK path", 880 required=(True if defaultNDKPath == None else False)) 881 parser.add_argument('-v', '--verbose', 882 dest='verbose', 883 help="Verbose output", 884 default=False, 885 action='store_true') 886 parser.add_argument('--target', 887 dest='target', 888 help='Build target', 889 choices=['deqp', 'openglcts'], 890 default='deqp') 891 parser.add_argument('--kc-cts-target', 892 dest='gtfTarget', 893 default='gles32', 894 choices=['gles32', 'gles31', 'gles3', 'gles2', 'gl'], 895 help="KC-CTS (GTF) target API (only used in openglcts target)") 896 897 args = parser.parse_args() 898 899 def parseAbis (abisStr): 900 knownAbis = set(NDKEnv.getKnownAbis()) 901 abis = [] 902 903 for abi in abisStr.split(','): 904 abi = abi.strip() 905 if not abi in knownAbis: 906 raise Exception("Unknown ABI: %s" % abi) 907 abis.append(abi) 908 909 return abis 910 911 # Custom parsing & checks 912 try: 913 args.abis = parseAbis(args.abis) 914 if len(args.abis) == 0: 915 raise Exception("--abis can't be empty") 916 except Exception as e: 917 print "ERROR: %s" % str(e) 918 parser.print_help() 919 sys.exit(-1) 920 921 return args 922 923if __name__ == "__main__": 924 args = parseArgs() 925 926 ndk = NDKEnv(os.path.realpath(args.ndkPath)) 927 sdk = SDKEnv(os.path.realpath(args.sdkPath)) 928 buildPath = os.path.realpath(args.buildRoot) 929 env = Environment(sdk, ndk) 930 config = Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget, verbose=args.verbose) 931 932 try: 933 config.check() 934 except Exception as e: 935 print "ERROR: %s" % str(e) 936 print "" 937 print "Please check your configuration:" 938 print " --sdk=%s" % args.sdkPath 939 print " --ndk=%s" % args.ndkPath 940 sys.exit(-1) 941 942 pkg, libs = getPackageAndLibrariesForTarget(args.target) 943 steps = getBuildStepsForPackage(config.abis, pkg, libs) 944 945 executeSteps(config, steps) 946 947 print "" 948 print "Built %s" % os.path.join(buildPath, getBuildRootRelativeAPKPath(pkg)) 949