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