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