# -*- coding: utf-8 -*- #------------------------------------------------------------------------- # drawElements Quality Program utilities # -------------------------------------- # # Copyright 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #------------------------------------------------------------------------- # \todo [2017-04-10 pyry] # * Use smarter asset copy in main build # * cmake -E copy_directory doesn't copy timestamps which will cause # assets to be always re-packaged # * Consider adding an option for downloading SDK & NDK import os import re import sys import glob import string import shutil import argparse import tempfile import xml.etree.ElementTree # Import from /scripts sys.path.append(os.path.join(os.path.dirname(__file__), "..")) from build.common import * from build.config import * from build.build import * class SDKEnv: def __init__(self, path): self.path = path self.buildToolsVersion = SDKEnv.selectBuildToolsVersion(self.path) @staticmethod def getBuildToolsVersions (path): buildToolsPath = os.path.join(path, "build-tools") versions = [] if os.path.exists(buildToolsPath): for item in os.listdir(buildToolsPath): m = re.match(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$', item) if m != None: versions.append((int(m.group(1)), int(m.group(2)), int(m.group(3)))) return versions @staticmethod def selectBuildToolsVersion (path): preferred = [(25, 0, 2)] versions = SDKEnv.getBuildToolsVersions(path) if len(versions) == 0: return (0,0,0) for candidate in preferred: if candidate in versions: return candidate # Pick newest versions.sort() return versions[-1] def getPlatformLibrary (self, apiVersion): return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar") def getBuildToolsPath (self): return os.path.join(self.path, "build-tools", "%d.%d.%d" % self.buildToolsVersion) class NDKEnv: def __init__(self, path): self.path = path self.version = NDKEnv.detectVersion(self.path) self.hostOsName = NDKEnv.detectHostOsName(self.path) @staticmethod def getKnownAbis (): return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"] @staticmethod def getAbiPrebuiltsName (abiName): prebuilts = { "armeabi-v7a": 'android-arm', "arm64-v8a": 'android-arm64', "x86": 'android-x86', "x86_64": 'android-x86_64', } if not abiName in prebuilts: raise Exception("Unknown ABI: " + abiName) return prebuilts[abiName] @staticmethod def detectVersion (path): propFilePath = os.path.join(path, "source.properties") try: with open(propFilePath) as propFile: for line in propFile: keyValue = list(map(lambda x: x.strip(), line.split("="))) if keyValue[0] == "Pkg.Revision": versionParts = keyValue[1].split(".") return tuple(map(int, versionParts[0:2])) except Exception as e: raise Exception("Failed to read source prop file '%s': %s" % (propFilePath, str(e))) except: raise Exception("Failed to read source prop file '%s': unkown error") raise Exception("Failed to detect NDK version (does %s/source.properties have Pkg.Revision?)" % path) @staticmethod def isHostOsSupported (hostOsName): os = HostInfo.getOs() bits = HostInfo.getArchBits() hostOsParts = hostOsName.split('-') if len(hostOsParts) > 1: assert(len(hostOsParts) == 2) assert(hostOsParts[1] == "x86_64") if bits != 64: return False if os == HostInfo.OS_WINDOWS: return hostOsParts[0] == 'windows' elif os == HostInfo.OS_LINUX: return hostOsParts[0] == 'linux' elif os == HostInfo.OS_OSX: return hostOsParts[0] == 'darwin' else: raise Exception("Unhandled HostInfo.getOs() '%d'" % os) @staticmethod def detectHostOsName (path): hostOsNames = [ "windows", "windows-x86_64", "darwin-x86", "darwin-x86_64", "linux-x86", "linux-x86_64" ] for name in hostOsNames: if os.path.exists(os.path.join(path, "prebuilt", name)): return name raise Exception("Failed to determine NDK host OS") class Environment: def __init__(self, sdk, ndk): self.sdk = sdk self.ndk = ndk class Configuration: def __init__(self, env, buildPath, abis, nativeApi, javaApi, minApi, nativeBuildType, gtfTarget, verbose, layers, angle): self.env = env self.sourcePath = DEQP_DIR self.buildPath = buildPath self.abis = abis self.nativeApi = nativeApi self.javaApi = javaApi self.minApi = minApi self.nativeBuildType = nativeBuildType self.gtfTarget = gtfTarget self.verbose = verbose self.layers = layers self.angle = angle self.cmakeGenerator = selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR]) def check (self): if self.cmakeGenerator == None: raise Exception("Failed to find build tools for CMake") if not os.path.exists(self.env.ndk.path): raise Exception("Android NDK not found at %s" % self.env.ndk.path) if not NDKEnv.isHostOsSupported(self.env.ndk.hostOsName): raise Exception("NDK '%s' is not supported on this machine" % self.env.ndk.hostOsName) if self.env.ndk.version[0] < 15: raise Exception("Android NDK version %d is not supported; build requires NDK version >= 15" % (self.env.ndk.version[0])) if not (self.minApi <= self.javaApi <= self.nativeApi): raise Exception("Requires: min-api (%d) <= java-api (%d) <= native-api (%d)" % (self.minApi, self.javaApi, self.nativeApi)) if self.env.sdk.buildToolsVersion == (0,0,0): raise Exception("No build tools directory found at %s" % os.path.join(self.env.sdk.path, "build-tools")) androidBuildTools = ["aapt", "zipalign", "dx"] for tool in androidBuildTools: if which(tool, [self.env.sdk.getBuildToolsPath()]) == None: raise Exception("Missing Android build tool: %s" % tool) requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"] for tool in requiredToolsInPath: if which(tool) == None: raise Exception("%s not in PATH" % tool) def log (config, msg): if config.verbose: print(msg) def executeAndLog (config, args): if config.verbose: print(" ".join(args)) execute(args) # Path components class ResolvablePathComponent: def __init__ (self): pass class SourceRoot (ResolvablePathComponent): def resolve (self, config): return config.sourcePath class BuildRoot (ResolvablePathComponent): def resolve (self, config): return config.buildPath class NativeBuildPath (ResolvablePathComponent): def __init__ (self, abiName): self.abiName = abiName def resolve (self, config): return getNativeBuildPath(config, self.abiName) class GeneratedResSourcePath (ResolvablePathComponent): def __init__ (self, package): self.package = package def resolve (self, config): packageComps = self.package.getPackageName(config).split('.') packageDir = os.path.join(*packageComps) return os.path.join(config.buildPath, self.package.getAppDirName(), "src", packageDir, "R.java") def resolvePath (config, path): resolvedComps = [] for component in path: if isinstance(component, ResolvablePathComponent): resolvedComps.append(component.resolve(config)) else: resolvedComps.append(str(component)) return os.path.join(*resolvedComps) def resolvePaths (config, paths): return list(map(lambda p: resolvePath(config, p), paths)) class BuildStep: def __init__ (self): pass def getInputs (self): return [] def getOutputs (self): return [] @staticmethod def expandPathsToFiles (paths): """ Expand mixed list of file and directory paths into a flattened list of files. Any non-existent input paths are preserved as is. """ def getFiles (dirPath): for root, dirs, files in os.walk(dirPath): for file in files: yield os.path.join(root, file) files = [] for path in paths: if os.path.isdir(path): files += list(getFiles(path)) else: files.append(path) return files def isUpToDate (self, config): inputs = resolvePaths(config, self.getInputs()) outputs = resolvePaths(config, self.getOutputs()) assert len(inputs) > 0 and len(outputs) > 0 expandedInputs = BuildStep.expandPathsToFiles(inputs) expandedOutputs = BuildStep.expandPathsToFiles(outputs) existingInputs = list(filter(os.path.exists, expandedInputs)) existingOutputs = list(filter(os.path.exists, expandedOutputs)) if len(existingInputs) != len(expandedInputs): for file in expandedInputs: if file not in existingInputs: print("ERROR: Missing input file: %s" % file) die("Missing input files") if len(existingOutputs) != len(expandedOutputs): return False # One or more output files are missing lastInputChange = max(map(os.path.getmtime, existingInputs)) firstOutputChange = min(map(os.path.getmtime, existingOutputs)) return lastInputChange <= firstOutputChange def update (config): die("BuildStep.update() not implemented") def getNativeBuildPath (config, abiName): return os.path.join(config.buildPath, "%s-%s-%d" % (abiName, config.nativeBuildType, config.nativeApi)) def clearCMakeCacheVariables(args): # New value, so clear the necessary cmake variables args.append('-UANGLE_LIBS') args.append('-UGLES1_LIBRARY') args.append('-UGLES2_LIBRARY') args.append('-UEGL_LIBRARY') def buildNativeLibrary (config, abiName): def makeNDKVersionString (version): minorVersionString = (chr(ord('a') + version[1]) if version[1] > 0 else "") return "r%d%s" % (version[0], minorVersionString) def getBuildArgs (config, abiName): args = ['-DDEQP_TARGET=android', '-DDEQP_TARGET_TOOLCHAIN=ndk-modern', '-DCMAKE_C_FLAGS=-Werror', '-DCMAKE_CXX_FLAGS=-Werror', '-DANDROID_NDK_PATH=%s' % config.env.ndk.path, '-DANDROID_ABI=%s' % abiName, '-DDE_ANDROID_API=%s' % config.nativeApi, '-DGLCTS_GTF_TARGET=%s' % config.gtfTarget] if config.angle is None: # Find any previous builds that may have embedded ANGLE libs and clear the CMake cache for abi in NDKEnv.getKnownAbis(): cMakeCachePath = os.path.join(getNativeBuildPath(config, abi), "CMakeCache.txt") try: if 'ANGLE_LIBS' in open(cMakeCachePath).read(): clearCMakeCacheVariables(args) except IOError: pass else: cMakeCachePath = os.path.join(getNativeBuildPath(config, abiName), "CMakeCache.txt") angleLibsDir = os.path.join(config.angle, abiName) # Check if the user changed where the ANGLE libs are being loaded from try: if angleLibsDir not in open(cMakeCachePath).read(): clearCMakeCacheVariables(args) except IOError: pass args.append('-DANGLE_LIBS=%s' % angleLibsDir) return args nativeBuildPath = getNativeBuildPath(config, abiName) buildConfig = BuildConfig(nativeBuildPath, config.nativeBuildType, getBuildArgs(config, abiName)) build(buildConfig, config.cmakeGenerator, ["deqp"]) def executeSteps (config, steps): for step in steps: if not step.isUpToDate(config): step.update(config) def parsePackageName (manifestPath): tree = xml.etree.ElementTree.parse(manifestPath) if not 'package' in tree.getroot().attrib: raise Exception("'package' attribute missing from root element in %s" % manifestPath) return tree.getroot().attrib['package'] class PackageDescription: def __init__ (self, appDirName, appName, hasResources = True): self.appDirName = appDirName self.appName = appName self.hasResources = hasResources def getAppName (self): return self.appName def getAppDirName (self): return self.appDirName def getPackageName (self, config): manifestPath = resolvePath(config, self.getManifestPath()) return parsePackageName(manifestPath) def getManifestPath (self): return [SourceRoot(), "android", self.appDirName, "AndroidManifest.xml"] def getResPath (self): return [SourceRoot(), "android", self.appDirName, "res"] def getSourcePaths (self): return [ [SourceRoot(), "android", self.appDirName, "src"] ] def getAssetsPath (self): return [BuildRoot(), self.appDirName, "assets"] def getClassesJarPath (self): return [BuildRoot(), self.appDirName, "bin", "classes.jar"] def getClassesDexPath (self): return [BuildRoot(), self.appDirName, "bin", "classes.dex"] def getAPKPath (self): return [BuildRoot(), self.appDirName, "bin", self.appName + ".apk"] # Build step implementations class BuildNativeLibrary (BuildStep): def __init__ (self, abi): self.abi = abi def isUpToDate (self, config): return False def update (self, config): log(config, "BuildNativeLibrary: %s" % self.abi) buildNativeLibrary(config, self.abi) class GenResourcesSrc (BuildStep): def __init__ (self, package): self.package = package def getInputs (self): return [self.package.getResPath(), self.package.getManifestPath()] def getOutputs (self): return [[GeneratedResSourcePath(self.package)]] def update (self, config): aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) dstDir = os.path.dirname(resolvePath(config, [GeneratedResSourcePath(self.package)])) if not os.path.exists(dstDir): os.makedirs(dstDir) executeAndLog(config, [ aaptPath, "package", "-f", "-m", "-S", resolvePath(config, self.package.getResPath()), "-M", resolvePath(config, self.package.getManifestPath()), "-J", resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "src"]), "-I", config.env.sdk.getPlatformLibrary(config.javaApi) ]) # Builds classes.jar from *.java files class BuildJavaSource (BuildStep): def __init__ (self, package, libraries = []): self.package = package self.libraries = libraries def getSourcePaths (self): srcPaths = self.package.getSourcePaths() if self.package.hasResources: srcPaths.append([BuildRoot(), self.package.getAppDirName(), "src"]) # Generated sources return srcPaths def getInputs (self): inputs = self.getSourcePaths() for lib in self.libraries: inputs.append(lib.getClassesJarPath()) return inputs def getOutputs (self): return [self.package.getClassesJarPath()] def update (self, config): srcPaths = resolvePaths(config, self.getSourcePaths()) srcFiles = BuildStep.expandPathsToFiles(srcPaths) jarPath = resolvePath(config, self.package.getClassesJarPath()) objPath = resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "obj"]) classPaths = [objPath] + [resolvePath(config, lib.getClassesJarPath()) for lib in self.libraries] pathSep = ";" if HostInfo.getOs() == HostInfo.OS_WINDOWS else ":" if os.path.exists(objPath): shutil.rmtree(objPath) os.makedirs(objPath) for srcFile in srcFiles: executeAndLog(config, [ "javac", "-source", "1.7", "-target", "1.7", "-d", objPath, "-bootclasspath", config.env.sdk.getPlatformLibrary(config.javaApi), "-classpath", pathSep.join(classPaths), "-sourcepath", pathSep.join(srcPaths), srcFile ]) if not os.path.exists(os.path.dirname(jarPath)): os.makedirs(os.path.dirname(jarPath)) try: pushWorkingDir(objPath) executeAndLog(config, [ "jar", "cf", jarPath, "." ]) finally: popWorkingDir() class BuildDex (BuildStep): def __init__ (self, package, libraries): self.package = package self.libraries = libraries def getInputs (self): return [self.package.getClassesJarPath()] + [lib.getClassesJarPath() for lib in self.libraries] def getOutputs (self): return [self.package.getClassesDexPath()] def update (self, config): dxPath = which("dx", [config.env.sdk.getBuildToolsPath()]) srcPaths = resolvePaths(config, self.getInputs()) dexPath = resolvePath(config, self.package.getClassesDexPath()) jarPaths = [resolvePath(config, self.package.getClassesJarPath())] for lib in self.libraries: jarPaths.append(resolvePath(config, lib.getClassesJarPath())) executeAndLog(config, [ dxPath, "--dex", "--output", dexPath ] + jarPaths) class CreateKeystore (BuildStep): def __init__ (self): self.keystorePath = [BuildRoot(), "debug.keystore"] def getOutputs (self): return [self.keystorePath] def isUpToDate (self, config): return os.path.exists(resolvePath(config, self.keystorePath)) def update (self, config): executeAndLog(config, [ "keytool", "-genkey", "-keystore", resolvePath(config, self.keystorePath), "-storepass", "android", "-alias", "androiddebugkey", "-keypass", "android", "-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-dname", "CN=, OU=, O=, L=, S=, C=", ]) # Builds APK without code class BuildBaseAPK (BuildStep): def __init__ (self, package, libraries = []): self.package = package self.libraries = libraries self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "base.apk"] def getResPaths (self): paths = [] for pkg in [self.package] + self.libraries: if pkg.hasResources: paths.append(pkg.getResPath()) return paths def getInputs (self): return [self.package.getManifestPath()] + self.getResPaths() def getOutputs (self): return [self.dstPath] def update (self, config): aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) dstPath = resolvePath(config, self.dstPath) if not os.path.exists(os.path.dirname(dstPath)): os.makedirs(os.path.dirname(dstPath)) args = [ aaptPath, "package", "-f", "--min-sdk-version", str(config.minApi), "--target-sdk-version", str(config.javaApi), "-M", resolvePath(config, self.package.getManifestPath()), "-I", config.env.sdk.getPlatformLibrary(config.javaApi), "-F", dstPath, ] for resPath in self.getResPaths(): args += ["-S", resolvePath(config, resPath)] if config.verbose: args.append("-v") executeAndLog(config, args) def addFilesToAPK (config, apkPath, baseDir, relFilePaths): aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()]) maxBatchSize = 25 pushWorkingDir(baseDir) try: workQueue = list(relFilePaths) # Workaround for Windows. if os.path.sep == "\\": workQueue = [i.replace("\\", "/") for i in workQueue] while len(workQueue) > 0: batchSize = min(len(workQueue), maxBatchSize) items = workQueue[0:batchSize] executeAndLog(config, [ aaptPath, "add", "-f", apkPath, ] + items) del workQueue[0:batchSize] finally: popWorkingDir() def addFileToAPK (config, apkPath, baseDir, relFilePath): addFilesToAPK(config, apkPath, baseDir, [relFilePath]) class AddJavaToAPK (BuildStep): def __init__ (self, package): self.package = package self.srcPath = BuildBaseAPK(self.package).getOutputs()[0] self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-java.apk"] def getInputs (self): return [ self.srcPath, self.package.getClassesDexPath(), ] def getOutputs (self): return [self.dstPath] def update (self, config): srcPath = resolvePath(config, self.srcPath) dstPath = resolvePath(config, self.getOutputs()[0]) dexPath = resolvePath(config, self.package.getClassesDexPath()) shutil.copyfile(srcPath, dstPath) addFileToAPK(config, dstPath, os.path.dirname(dexPath), os.path.basename(dexPath)) class AddAssetsToAPK (BuildStep): def __init__ (self, package, abi): self.package = package self.buildPath = [NativeBuildPath(abi)] self.srcPath = AddJavaToAPK(self.package).getOutputs()[0] self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-assets.apk"] def getInputs (self): return [ self.srcPath, self.buildPath + ["assets"] ] def getOutputs (self): return [self.dstPath] @staticmethod def getAssetFiles (buildPath): allFiles = BuildStep.expandPathsToFiles([os.path.join(buildPath, "assets")]) return [os.path.relpath(p, buildPath) for p in allFiles] def update (self, config): srcPath = resolvePath(config, self.srcPath) dstPath = resolvePath(config, self.getOutputs()[0]) buildPath = resolvePath(config, self.buildPath) assetFiles = AddAssetsToAPK.getAssetFiles(buildPath) shutil.copyfile(srcPath, dstPath) addFilesToAPK(config, dstPath, buildPath, assetFiles) class AddNativeLibsToAPK (BuildStep): def __init__ (self, package, abis): self.package = package self.abis = abis self.srcPath = AddAssetsToAPK(self.package, "").getOutputs()[0] self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-native-libs.apk"] def getInputs (self): paths = [self.srcPath] for abi in self.abis: paths.append([NativeBuildPath(abi), "libdeqp.so"]) return paths def getOutputs (self): return [self.dstPath] def update (self, config): srcPath = resolvePath(config, self.srcPath) dstPath = resolvePath(config, self.getOutputs()[0]) pkgPath = resolvePath(config, [BuildRoot(), self.package.getAppDirName()]) libFiles = [] # Create right directory structure first for abi in self.abis: libSrcPath = resolvePath(config, [NativeBuildPath(abi), "libdeqp.so"]) libRelPath = os.path.join("lib", abi, "libdeqp.so") libAbsPath = os.path.join(pkgPath, libRelPath) if not os.path.exists(os.path.dirname(libAbsPath)): os.makedirs(os.path.dirname(libAbsPath)) shutil.copyfile(libSrcPath, libAbsPath) libFiles.append(libRelPath) if config.layers: layersGlob = os.path.join(config.layers, abi, "libVkLayer_*.so") libVkLayers = glob.glob(layersGlob) for layer in libVkLayers: layerFilename = os.path.basename(layer) layerRelPath = os.path.join("lib", abi, layerFilename) layerAbsPath = os.path.join(pkgPath, layerRelPath) shutil.copyfile(layer, layerAbsPath) libFiles.append(layerRelPath) print("Adding layer binary: %s" % (layer,)) if config.angle: angleGlob = os.path.join(config.angle, abi, "lib*_angle.so") libAngle = glob.glob(angleGlob) for lib in libAngle: libFilename = os.path.basename(lib) libRelPath = os.path.join("lib", abi, libFilename) libAbsPath = os.path.join(pkgPath, libRelPath) shutil.copyfile(lib, libAbsPath) libFiles.append(libRelPath) print("Adding ANGLE binary: %s" % (lib,)) shutil.copyfile(srcPath, dstPath) addFilesToAPK(config, dstPath, pkgPath, libFiles) class SignAPK (BuildStep): def __init__ (self, package): self.package = package self.srcPath = AddNativeLibsToAPK(self.package, []).getOutputs()[0] self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "signed.apk"] self.keystorePath = CreateKeystore().getOutputs()[0] def getInputs (self): return [self.srcPath, self.keystorePath] def getOutputs (self): return [self.dstPath] def update (self, config): srcPath = resolvePath(config, self.srcPath) dstPath = resolvePath(config, self.dstPath) executeAndLog(config, [ "jarsigner", "-keystore", resolvePath(config, self.keystorePath), "-storepass", "android", "-keypass", "android", "-signedjar", dstPath, srcPath, "androiddebugkey" ]) def getBuildRootRelativeAPKPath (package): return os.path.join(package.getAppDirName(), package.getAppName() + ".apk") class FinalizeAPK (BuildStep): def __init__ (self, package): self.package = package self.srcPath = SignAPK(self.package).getOutputs()[0] self.dstPath = [BuildRoot(), getBuildRootRelativeAPKPath(self.package)] self.keystorePath = CreateKeystore().getOutputs()[0] def getInputs (self): return [self.srcPath] def getOutputs (self): return [self.dstPath] def update (self, config): srcPath = resolvePath(config, self.srcPath) dstPath = resolvePath(config, self.dstPath) zipalignPath = os.path.join(config.env.sdk.getBuildToolsPath(), "zipalign") executeAndLog(config, [ zipalignPath, "-f", "4", srcPath, dstPath ]) def getBuildStepsForPackage (abis, package, libraries = []): steps = [] assert len(abis) > 0 # Build native code first for abi in abis: steps += [BuildNativeLibrary(abi)] # Build library packages for library in libraries: if library.hasResources: steps.append(GenResourcesSrc(library)) steps.append(BuildJavaSource(library)) # Build main package .java sources if package.hasResources: steps.append(GenResourcesSrc(package)) steps.append(BuildJavaSource(package, libraries)) steps.append(BuildDex(package, libraries)) # Build base APK steps.append(BuildBaseAPK(package, libraries)) steps.append(AddJavaToAPK(package)) # Add assets from first ABI steps.append(AddAssetsToAPK(package, abis[0])) # Add native libs to APK steps.append(AddNativeLibsToAPK(package, abis)) # Finalize APK steps.append(CreateKeystore()) steps.append(SignAPK(package)) steps.append(FinalizeAPK(package)) return steps def getPackageAndLibrariesForTarget (target): deqpPackage = PackageDescription("package", "dEQP") ctsPackage = PackageDescription("openglcts", "Khronos-CTS", hasResources = False) if target == 'deqp': return (deqpPackage, []) elif target == 'openglcts': return (ctsPackage, [deqpPackage]) else: raise Exception("Uknown target '%s'" % target) def findNDK (): ndkBuildPath = which('ndk-build') if ndkBuildPath != None: return os.path.dirname(ndkBuildPath) else: return None def findSDK (): sdkBuildPath = which('android') if sdkBuildPath != None: return os.path.dirname(os.path.dirname(sdkBuildPath)) else: return None def getDefaultBuildRoot (): return os.path.join(tempfile.gettempdir(), "deqp-android-build") def parseArgs (): nativeBuildTypes = ['Release', 'Debug', 'MinSizeRel', 'RelWithAsserts', 'RelWithDebInfo'] defaultNDKPath = findNDK() defaultSDKPath = findSDK() defaultBuildRoot = getDefaultBuildRoot() parser = argparse.ArgumentParser(os.path.basename(__file__), formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--native-build-type', dest='nativeBuildType', default="RelWithAsserts", choices=nativeBuildTypes, help="Native code build type") parser.add_argument('--build-root', dest='buildRoot', default=defaultBuildRoot, help="Root build directory") parser.add_argument('--abis', dest='abis', default=",".join(NDKEnv.getKnownAbis()), help="ABIs to build") parser.add_argument('--native-api', type=int, dest='nativeApi', default=28, help="Android API level to target in native code") parser.add_argument('--java-api', type=int, dest='javaApi', default=28, help="Android API level to target in Java code") parser.add_argument('--min-api', type=int, dest='minApi', default=22, help="Minimum Android API level for which the APK can be installed") parser.add_argument('--sdk', dest='sdkPath', default=defaultSDKPath, help="Android SDK path", required=(True if defaultSDKPath == None else False)) parser.add_argument('--ndk', dest='ndkPath', default=defaultNDKPath, help="Android NDK path", required=(True if defaultNDKPath == None else False)) parser.add_argument('-v', '--verbose', dest='verbose', help="Verbose output", default=False, action='store_true') parser.add_argument('--target', dest='target', help='Build target', choices=['deqp', 'openglcts'], default='deqp') parser.add_argument('--kc-cts-target', dest='gtfTarget', default='gles32', choices=['gles32', 'gles31', 'gles3', 'gles2', 'gl'], help="KC-CTS (GTF) target API (only used in openglcts target)") parser.add_argument('--layers-path', dest='layers', default=None, required=False) parser.add_argument('--angle-path', dest='angle', default=None, required=False) args = parser.parse_args() def parseAbis (abisStr): knownAbis = set(NDKEnv.getKnownAbis()) abis = [] for abi in abisStr.split(','): abi = abi.strip() if not abi in knownAbis: raise Exception("Unknown ABI: %s" % abi) abis.append(abi) return abis # Custom parsing & checks try: args.abis = parseAbis(args.abis) if len(args.abis) == 0: raise Exception("--abis can't be empty") except Exception as e: print("ERROR: %s" % str(e)) parser.print_help() sys.exit(-1) return args if __name__ == "__main__": args = parseArgs() ndk = NDKEnv(os.path.realpath(args.ndkPath)) sdk = SDKEnv(os.path.realpath(args.sdkPath)) buildPath = os.path.realpath(args.buildRoot) env = Environment(sdk, ndk) config = Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, javaApi=args.javaApi, minApi=args.minApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget, verbose=args.verbose, layers=args.layers, angle=args.angle) try: config.check() except Exception as e: print("ERROR: %s" % str(e)) print("") print("Please check your configuration:") print(" --sdk=%s" % args.sdkPath) print(" --ndk=%s" % args.ndkPath) sys.exit(-1) pkg, libs = getPackageAndLibrariesForTarget(args.target) steps = getBuildStepsForPackage(config.abis, pkg, libs) executeSteps(config, steps) print("") print("Built %s" % os.path.join(buildPath, getBuildRootRelativeAPKPath(pkg)))