blob: 630cd4136ed09a15e47daad899fff73560ba6666 [file] [log] [blame]
# -*- 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 <root>/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, nativeBuildType, gtfTarget, verbose, layers, angle):
self.env = env
self.sourcePath = DEQP_DIR
self.buildPath = buildPath
self.abis = abis
self.nativeApi = nativeApi
self.javaApi = 22
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 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" % toolPath)
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_HOST_OS=%s' % config.env.ndk.hostOsName,
'-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",
"-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)
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('--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, 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)))