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