1 # -*- coding: utf-8 -*-
3 #-------------------------------------------------------------------------
4 # drawElements Quality Program utilities
5 # --------------------------------------
7 # Copyright 2017 The Android Open Source Project
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 #-------------------------------------------------------------------------
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
37 import xml.etree.ElementTree
39 # Import from <root>/scripts
40 sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
42 from ctsbuild.common import *
43 from ctsbuild.config import *
44 from ctsbuild.build import *
47 def __init__(self, path):
49 self.buildToolsVersion = SDKEnv.selectBuildToolsVersion(self.path)
52 def getBuildToolsVersions (path):
53 buildToolsPath = os.path.join(path, "build-tools")
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)
60 versions.append((int(m.group(1)), int(m.group(2)), int(m.group(3))))
65 def selectBuildToolsVersion (path):
66 preferred = [(25, 0, 2)]
67 versions = SDKEnv.getBuildToolsVersions(path)
69 if len(versions) == 0:
72 for candidate in preferred:
73 if candidate in versions:
80 def getPlatformLibrary (self, apiVersion):
81 return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar")
83 def getBuildToolsPath (self):
84 return os.path.join(self.path, "build-tools", "%d.%d.%d" % self.buildToolsVersion)
87 def __init__(self, path):
89 self.version = NDKEnv.detectVersion(self.path)
90 self.hostOsName = NDKEnv.detectHostOsName(self.path)
94 return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
97 def getAbiPrebuiltsName (abiName):
99 "armeabi-v7a": 'android-arm',
100 "arm64-v8a": 'android-arm64',
101 "x86": 'android-x86',
102 "x86_64": 'android-x86_64',
105 if not abiName in prebuilts:
106 raise Exception("Unknown ABI: " + abiName)
108 return prebuilts[abiName]
111 def detectVersion (path):
112 propFilePath = os.path.join(path, "source.properties")
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)))
123 raise Exception("Failed to read source prop file '%s': unkown error")
125 raise Exception("Failed to detect NDK version (does %s/source.properties have Pkg.Revision?)" % path)
128 def isHostOsSupported (hostOsName):
129 os = HostInfo.getOs()
130 bits = HostInfo.getArchBits()
131 hostOsParts = hostOsName.split('-')
133 if len(hostOsParts) > 1:
134 assert(len(hostOsParts) == 2)
135 assert(hostOsParts[1] == "x86_64")
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'
147 raise Exception("Unhandled HostInfo.getOs() '%d'" % os)
150 def detectHostOsName (path):
160 for name in hostOsNames:
161 if os.path.exists(os.path.join(path, "prebuilt", name)):
164 raise Exception("Failed to determine NDK host OS")
167 def __init__(self, sdk, ndk):
172 def __init__(self, env, buildPath, abis, nativeApi, javaApi, minApi, nativeBuildType, gtfTarget, verbose, layers, angle):
174 self.sourcePath = DEQP_DIR
175 self.buildPath = buildPath
177 self.nativeApi = nativeApi
178 self.javaApi = javaApi
180 self.nativeBuildType = nativeBuildType
181 self.gtfTarget = gtfTarget
182 self.verbose = verbose
185 self.cmakeGenerator = selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR])
188 if self.cmakeGenerator == None:
189 raise Exception("Failed to find build tools for CMake")
191 if not os.path.exists(self.env.ndk.path):
192 raise Exception("Android NDK not found at %s" % self.env.ndk.path)
194 if not NDKEnv.isHostOsSupported(self.env.ndk.hostOsName):
195 raise Exception("NDK '%s' is not supported on this machine" % self.env.ndk.hostOsName)
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]))
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))
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"))
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)
211 requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"]
212 for tool in requiredToolsInPath:
213 if which(tool) == None:
214 raise Exception("%s not in PATH" % tool)
216 def log (config, msg):
220 def executeAndLog (config, args):
222 print(" ".join(args))
227 class ResolvablePathComponent:
231 class SourceRoot (ResolvablePathComponent):
232 def resolve (self, config):
233 return config.sourcePath
235 class BuildRoot (ResolvablePathComponent):
236 def resolve (self, config):
237 return config.buildPath
239 class NativeBuildPath (ResolvablePathComponent):
240 def __init__ (self, abiName):
241 self.abiName = abiName
243 def resolve (self, config):
244 return getNativeBuildPath(config, self.abiName)
246 class GeneratedResSourcePath (ResolvablePathComponent):
247 def __init__ (self, package):
248 self.package = package
250 def resolve (self, config):
251 packageComps = self.package.getPackageName(config).split('.')
252 packageDir = os.path.join(*packageComps)
254 return os.path.join(config.buildPath, self.package.getAppDirName(), "src", packageDir, "R.java")
256 def resolvePath (config, path):
259 for component in path:
260 if isinstance(component, ResolvablePathComponent):
261 resolvedComps.append(component.resolve(config))
263 resolvedComps.append(str(component))
265 return os.path.join(*resolvedComps)
267 def resolvePaths (config, paths):
268 return list(map(lambda p: resolvePath(config, p), paths))
274 def getInputs (self):
277 def getOutputs (self):
281 def expandPathsToFiles (paths):
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.
287 def getFiles (dirPath):
288 for root, dirs, files in os.walk(dirPath):
290 yield os.path.join(root, file)
294 if os.path.isdir(path):
295 files += list(getFiles(path))
301 def isUpToDate (self, config):
302 inputs = resolvePaths(config, self.getInputs())
303 outputs = resolvePaths(config, self.getOutputs())
305 assert len(inputs) > 0 and len(outputs) > 0
307 expandedInputs = BuildStep.expandPathsToFiles(inputs)
308 expandedOutputs = BuildStep.expandPathsToFiles(outputs)
310 existingInputs = list(filter(os.path.exists, expandedInputs))
311 existingOutputs = list(filter(os.path.exists, expandedOutputs))
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")
319 if len(existingOutputs) != len(expandedOutputs):
320 return False # One or more output files are missing
322 lastInputChange = max(map(os.path.getmtime, existingInputs))
323 firstOutputChange = min(map(os.path.getmtime, existingOutputs))
325 return lastInputChange <= firstOutputChange
328 die("BuildStep.update() not implemented")
330 def getNativeBuildPath (config, abiName):
331 return os.path.join(config.buildPath, "%s-%s-%d" % (abiName, config.nativeBuildType, config.nativeApi))
333 def 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')
340 def 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)
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]
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")
360 if 'ANGLE_LIBS' in open(cMakeCachePath).read():
361 clearCMakeCacheVariables(args)
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
369 if angleLibsDir not in open(cMakeCachePath).read():
370 clearCMakeCacheVariables(args)
373 args.append('-DANGLE_LIBS=%s' % angleLibsDir)
377 nativeBuildPath = getNativeBuildPath(config, abiName)
378 buildConfig = BuildConfig(nativeBuildPath, config.nativeBuildType, getBuildArgs(config, abiName))
380 build(buildConfig, config.cmakeGenerator, ["deqp"])
382 def executeSteps (config, steps):
384 if not step.isUpToDate(config):
387 def parsePackageName (manifestPath):
388 tree = xml.etree.ElementTree.parse(manifestPath)
390 if not 'package' in tree.getroot().attrib:
391 raise Exception("'package' attribute missing from root element in %s" % manifestPath)
393 return tree.getroot().attrib['package']
395 class PackageDescription:
396 def __init__ (self, appDirName, appName, hasResources = True):
397 self.appDirName = appDirName
398 self.appName = appName
399 self.hasResources = hasResources
401 def getAppName (self):
404 def getAppDirName (self):
405 return self.appDirName
407 def getPackageName (self, config):
408 manifestPath = resolvePath(config, self.getManifestPath())
410 return parsePackageName(manifestPath)
412 def getManifestPath (self):
413 return [SourceRoot(), "android", self.appDirName, "AndroidManifest.xml"]
415 def getResPath (self):
416 return [SourceRoot(), "android", self.appDirName, "res"]
418 def getSourcePaths (self):
420 [SourceRoot(), "android", self.appDirName, "src"]
423 def getAssetsPath (self):
424 return [BuildRoot(), self.appDirName, "assets"]
426 def getClassesJarPath (self):
427 return [BuildRoot(), self.appDirName, "bin", "classes.jar"]
429 def getClassesDexPath (self):
430 return [BuildRoot(), self.appDirName, "bin", "classes.dex"]
432 def getAPKPath (self):
433 return [BuildRoot(), self.appDirName, "bin", self.appName + ".apk"]
435 # Build step implementations
437 class BuildNativeLibrary (BuildStep):
438 def __init__ (self, abi):
441 def isUpToDate (self, config):
444 def update (self, config):
445 log(config, "BuildNativeLibrary: %s" % self.abi)
446 buildNativeLibrary(config, self.abi)
448 class GenResourcesSrc (BuildStep):
449 def __init__ (self, package):
450 self.package = package
452 def getInputs (self):
453 return [self.package.getResPath(), self.package.getManifestPath()]
455 def getOutputs (self):
456 return [[GeneratedResSourcePath(self.package)]]
458 def update (self, config):
459 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()])
460 dstDir = os.path.dirname(resolvePath(config, [GeneratedResSourcePath(self.package)]))
462 if not os.path.exists(dstDir):
465 executeAndLog(config, [
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)
476 # Builds classes.jar from *.java files
477 class BuildJavaSource (BuildStep):
478 def __init__ (self, package, libraries = []):
479 self.package = package
480 self.libraries = libraries
482 def getSourcePaths (self):
483 srcPaths = self.package.getSourcePaths()
485 if self.package.hasResources:
486 srcPaths.append([BuildRoot(), self.package.getAppDirName(), "src"]) # Generated sources
490 def getInputs (self):
491 inputs = self.getSourcePaths()
493 for lib in self.libraries:
494 inputs.append(lib.getClassesJarPath())
498 def getOutputs (self):
499 return [self.package.getClassesJarPath()]
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 ":"
509 if os.path.exists(objPath):
510 shutil.rmtree(objPath)
514 for srcFile in srcFiles:
515 executeAndLog(config, [
520 "-bootclasspath", config.env.sdk.getPlatformLibrary(config.javaApi),
521 "-classpath", pathSep.join(classPaths),
522 "-sourcepath", pathSep.join(srcPaths),
526 if not os.path.exists(os.path.dirname(jarPath)):
527 os.makedirs(os.path.dirname(jarPath))
530 pushWorkingDir(objPath)
531 executeAndLog(config, [
540 class BuildDex (BuildStep):
541 def __init__ (self, package, libraries):
542 self.package = package
543 self.libraries = libraries
545 def getInputs (self):
546 return [self.package.getClassesJarPath()] + [lib.getClassesJarPath() for lib in self.libraries]
548 def getOutputs (self):
549 return [self.package.getClassesDexPath()]
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())]
557 for lib in self.libraries:
558 jarPaths.append(resolvePath(config, lib.getClassesJarPath()))
560 executeAndLog(config, [
566 class CreateKeystore (BuildStep):
568 self.keystorePath = [BuildRoot(), "debug.keystore"]
570 def getOutputs (self):
571 return [self.keystorePath]
573 def isUpToDate (self, config):
574 return os.path.exists(resolvePath(config, self.keystorePath))
576 def update (self, config):
577 executeAndLog(config, [
580 "-keystore", resolvePath(config, self.keystorePath),
581 "-storepass", "android",
582 "-alias", "androiddebugkey",
583 "-keypass", "android",
586 "-validity", "10000",
587 "-dname", "CN=, OU=, O=, L=, S=, C=",
590 # Builds APK without code
591 class 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"]
597 def getResPaths (self):
599 for pkg in [self.package] + self.libraries:
601 paths.append(pkg.getResPath())
604 def getInputs (self):
605 return [self.package.getManifestPath()] + self.getResPaths()
607 def getOutputs (self):
608 return [self.dstPath]
610 def update (self, config):
611 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()])
612 dstPath = resolvePath(config, self.dstPath)
614 if not os.path.exists(os.path.dirname(dstPath)):
615 os.makedirs(os.path.dirname(dstPath))
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),
628 for resPath in self.getResPaths():
629 args += ["-S", resolvePath(config, resPath)]
634 executeAndLog(config, args)
636 def addFilesToAPK (config, apkPath, baseDir, relFilePaths):
637 aaptPath = which("aapt", [config.env.sdk.getBuildToolsPath()])
640 pushWorkingDir(baseDir)
642 workQueue = list(relFilePaths)
643 # Workaround for Windows.
644 if os.path.sep == "\\":
645 workQueue = [i.replace("\\", "/") for i in workQueue]
647 while len(workQueue) > 0:
648 batchSize = min(len(workQueue), maxBatchSize)
649 items = workQueue[0:batchSize]
651 executeAndLog(config, [
657 del workQueue[0:batchSize]
661 def addFileToAPK (config, apkPath, baseDir, relFilePath):
662 addFilesToAPK(config, apkPath, baseDir, [relFilePath])
664 class 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"]
670 def getInputs (self):
673 self.package.getClassesDexPath(),
676 def getOutputs (self):
677 return [self.dstPath]
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())
684 shutil.copyfile(srcPath, dstPath)
685 addFileToAPK(config, dstPath, os.path.dirname(dexPath), os.path.basename(dexPath))
687 class 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"]
694 def getInputs (self):
697 self.buildPath + ["assets"]
700 def getOutputs (self):
701 return [self.dstPath]
704 def getAssetFiles (buildPath):
705 allFiles = BuildStep.expandPathsToFiles([os.path.join(buildPath, "assets")])
706 return [os.path.relpath(p, buildPath) for p in allFiles]
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)
714 shutil.copyfile(srcPath, dstPath)
716 addFilesToAPK(config, dstPath, buildPath, assetFiles)
718 class AddNativeLibsToAPK (BuildStep):
719 def __init__ (self, package, abis):
720 self.package = package
722 self.srcPath = AddAssetsToAPK(self.package, "").getOutputs()[0]
723 self.dstPath = [BuildRoot(), self.package.getAppDirName(), "tmp", "with-native-libs.apk"]
725 def getInputs (self):
726 paths = [self.srcPath]
727 for abi in self.abis:
728 paths.append([NativeBuildPath(abi), "libdeqp.so"])
731 def getOutputs (self):
732 return [self.dstPath]
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()])
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)
746 if not os.path.exists(os.path.dirname(libAbsPath)):
747 os.makedirs(os.path.dirname(libAbsPath))
749 shutil.copyfile(libSrcPath, libAbsPath)
750 libFiles.append(libRelPath)
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,))
764 angleGlob = os.path.join(config.angle, abi, "lib*_angle.so")
765 libAngle = glob.glob(angleGlob)
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,))
774 shutil.copyfile(srcPath, dstPath)
775 addFilesToAPK(config, dstPath, pkgPath, libFiles)
777 class 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]
784 def getInputs (self):
785 return [self.srcPath, self.keystorePath]
787 def getOutputs (self):
788 return [self.dstPath]
790 def update (self, config):
791 srcPath = resolvePath(config, self.srcPath)
792 dstPath = resolvePath(config, self.dstPath)
794 executeAndLog(config, [
796 "-keystore", resolvePath(config, self.keystorePath),
797 "-storepass", "android",
798 "-keypass", "android",
799 "-signedjar", dstPath,
804 def getBuildRootRelativeAPKPath (package):
805 return os.path.join(package.getAppDirName(), package.getAppName() + ".apk")
807 class 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]
814 def getInputs (self):
815 return [self.srcPath]
817 def getOutputs (self):
818 return [self.dstPath]
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")
825 executeAndLog(config, [
832 def getBuildStepsForPackage (abis, package, libraries = []):
837 # Build native code first
839 steps += [BuildNativeLibrary(abi)]
841 # Build library packages
842 for library in libraries:
843 if library.hasResources:
844 steps.append(GenResourcesSrc(library))
845 steps.append(BuildJavaSource(library))
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))
854 steps.append(BuildBaseAPK(package, libraries))
855 steps.append(AddJavaToAPK(package))
857 # Add assets from first ABI
858 steps.append(AddAssetsToAPK(package, abis[0]))
860 # Add native libs to APK
861 steps.append(AddNativeLibsToAPK(package, abis))
864 steps.append(CreateKeystore())
865 steps.append(SignAPK(package))
866 steps.append(FinalizeAPK(package))
870 def getPackageAndLibrariesForTarget (target):
871 deqpPackage = PackageDescription("package", "dEQP")
872 ctsPackage = PackageDescription("openglcts", "Khronos-CTS", hasResources = False)
875 return (deqpPackage, [])
876 elif target == 'openglcts':
877 return (ctsPackage, [deqpPackage])
879 raise Exception("Uknown target '%s'" % target)
882 ndkBuildPath = which('ndk-build')
883 if ndkBuildPath != None:
884 return os.path.dirname(ndkBuildPath)
889 sdkBuildPath = which('android')
890 if sdkBuildPath != None:
891 return os.path.dirname(os.path.dirname(sdkBuildPath))
895 def getDefaultBuildRoot ():
896 return os.path.join(tempfile.gettempdir(), "deqp-android-build")
899 nativeBuildTypes = ['Release', 'Debug', 'MinSizeRel', 'RelWithAsserts', 'RelWithDebInfo']
900 defaultNDKPath = findNDK()
901 defaultSDKPath = findSDK()
902 defaultBuildRoot = getDefaultBuildRoot()
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',
913 default=defaultBuildRoot,
914 help="Root build directory")
915 parser.add_argument('--abis',
917 default=",".join(NDKEnv.getKnownAbis()),
918 help="ABIs to build")
919 parser.add_argument('--native-api',
923 help="Android API level to target in native code")
924 parser.add_argument('--java-api',
928 help="Android API level to target in Java code")
929 parser.add_argument('--min-api',
933 help="Minimum Android API level for which the APK can be installed")
934 parser.add_argument('--sdk',
936 default=defaultSDKPath,
937 help="Android SDK path",
938 required=(True if defaultSDKPath == None else False))
939 parser.add_argument('--ndk',
941 default=defaultNDKPath,
942 help="Android NDK path",
943 required=(True if defaultNDKPath == None else False))
944 parser.add_argument('-v', '--verbose',
946 help="Verbose output",
949 parser.add_argument('--target',
952 choices=['deqp', 'openglcts'],
954 parser.add_argument('--kc-cts-target',
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',
963 parser.add_argument('--angle-path',
968 args = parser.parse_args()
970 def parseAbis (abisStr):
971 knownAbis = set(NDKEnv.getKnownAbis())
974 for abi in abisStr.split(','):
976 if not abi in knownAbis:
977 raise Exception("Unknown ABI: %s" % abi)
982 # Custom parsing & checks
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))
994 if __name__ == "__main__":
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)
1006 except Exception as e:
1007 print("ERROR: %s" % str(e))
1009 print("Please check your configuration:")
1010 print(" --sdk=%s" % args.sdkPath)
1011 print(" --ndk=%s" % args.ndkPath)
1014 pkg, libs = getPackageAndLibrariesForTarget(args.target)
1015 steps = getBuildStepsForPackage(config.abis, pkg, libs)
1017 executeSteps(config, steps)
1020 print("Built %s" % os.path.join(buildPath, getBuildRootRelativeAPKPath(pkg)))