Update scripts for Android build
authorAitor Camacho <aitor@lunarg.com>
Tue, 3 Jan 2023 18:25:23 +0000 (19:25 +0100)
committeraitor_lunarg <aitor@lunarg.com>
Tue, 17 Jan 2023 22:40:21 +0000 (23:40 +0100)
 - Use d8 instead of dx due to deprecation
 - Use apksigner instead of jarsigner for signature scheme v2 required by newer SDKs
 - Move apk signing to be last step (required for signature scheme v2)
 - Force uncompressed arsc files (required for SDK versions 30 and up)
 - Add '--tool-api' argument for selecting build tools version
 - Add permission for reading external storage in manifest

VK-GL-CTS Issue: 4163

Components: Framework
Change-Id: Iea4429f861148168ffe680cdf9a497cb467b8d6a

android/package/AndroidManifest.xml
external/vulkancts/README.md
scripts/android/build_apk.py

index 53b1108..ddd8919 100644 (file)
@@ -35,6 +35,7 @@
        </application>
 
        <uses-feature android:glEsVersion="0x00020000"/>
+       <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.GET_TASKS" />
        <uses-permission android:name="android.permission.INTERNET" />
index 1ba58f6..b66b139 100644 (file)
@@ -224,7 +224,9 @@ Test log will be written into TestResults.qpa
 
 ### Android
 
-       adb push <vulkancts>/external/vulkancts/mustpass/master/vk-default.txt /sdcard/vk-default.txt
+For Android build using SDK 29 or greater, it is recommended to use `/sdcard/Documents/` instead of `/sdcard/` due to scoped storage.
+
+       adb push <vulkancts>/external/vulkancts/mustpass/main/vk-default.txt /sdcard/vk-default.txt
        adb shell
 
 In device shell:
index cad3269..b39d4b7 100644 (file)
@@ -44,38 +44,40 @@ from build.config import *
 from build.build import *
 
 class SDKEnv:
-       def __init__(self, path):
+       def __init__(self, path, desired_version):
                self.path                               = path
-               self.buildToolsVersion  = SDKEnv.selectBuildToolsVersion(self.path)
+               self.buildToolsVersion  = SDKEnv.selectBuildToolsVersion(self.path, desired_version)
 
        @staticmethod
        def getBuildToolsVersions (path):
                buildToolsPath  = os.path.join(path, "build-tools")
-               versions                = []
+               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))))
+                                       versions[int(m.group(1))] = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
 
                return versions
 
        @staticmethod
-       def selectBuildToolsVersion (path):
-               preferred       = [(25, 0, 2)]
+       def selectBuildToolsVersion (path, preferred):
                versions        = SDKEnv.getBuildToolsVersions(path)
 
                if len(versions) == 0:
                        return (0,0,0)
 
-               for candidate in preferred:
-                       if candidate in versions:
-                               return candidate
+               if preferred == -1:
+                       return max(versions.values())
+
+               if preferred in versions:
+                       return versions[preferred]
 
                # Pick newest
-               versions.sort()
-               return versions[-1]
+               newest_version = max(versions.values())
+               print("Couldn't find Android Tool version %d, %d was selected." % (preferred, newest_version[0]))
+               return newest_version
 
        def getPlatformLibrary (self, apiVersion):
                return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar")
@@ -169,19 +171,20 @@ class Environment:
                self.ndk                = ndk
 
 class Configuration:
-       def __init__(self, env, buildPath, abis, nativeApi, minApi, nativeBuildType, gtfTarget, verbose, layers, angle):
+       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                    = 28
+               self.javaApi                    = javaApi
                self.minApi                             = minApi
                self.nativeBuildType    = nativeBuildType
                self.gtfTarget                  = gtfTarget
                self.verbose                    = verbose
                self.layers                             = layers
                self.angle                              = angle
+               self.dCompilerName              = "d8"
                self.cmakeGenerator             = selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR])
 
        def check (self):
@@ -203,12 +206,20 @@ class Configuration:
                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"]
+               if not os.path.exists(os.path.join(self.env.sdk.path, "platforms", "android-%d" % self.javaApi)):
+                       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")))
+
+               # Try to find first d8 since dx was deprecated
+               if which(self.dCompilerName, [self.env.sdk.getBuildToolsPath()]) == None:
+                       print("Couldn't find %s, will try to find dx", self.dCompilerName)
+                       self.dCompilerName = "dx"
+
+               androidBuildTools = ["aapt", "zipalign", "apksigner", self.dCompilerName]
                for tool in androidBuildTools:
                        if which(tool, [self.env.sdk.getBuildToolsPath()]) == None:
-                               raise Exception("Missing Android build tool: %s" % tool)
+                               raise Exception("Missing Android build tool: %s in %s" % (tool, self.env.sdk.getBuildToolsPath()))
 
-               requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"]
+               requiredToolsInPath = ["javac", "jar", "keytool"]
                for tool in requiredToolsInPath:
                        if which(tool) == None:
                                raise Exception("%s not in PATH" % tool)
@@ -426,6 +437,9 @@ class PackageDescription:
        def getClassesJarPath (self):
                return [BuildRoot(), self.appDirName, "bin", "classes.jar"]
 
+       def getClassesDexDirectory (self):
+               return [BuildRoot(), self.appDirName, "bin",]
+
        def getClassesDexPath (self):
                return [BuildRoot(), self.appDirName, "bin", "classes.dex"]
 
@@ -549,19 +563,23 @@ class BuildDex (BuildStep):
                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())
+               dxPath          = which(config.dCompilerName, [config.env.sdk.getBuildToolsPath()])
+               dexPath         = resolvePath(config, self.package.getClassesDexDirectory())
                jarPaths        = [resolvePath(config, self.package.getClassesJarPath())]
 
                for lib in self.libraries:
                        jarPaths.append(resolvePath(config, lib.getClassesJarPath()))
 
-               executeAndLog(config, [
-                               dxPath,
-                               "--dex",
-                               "--output", dexPath
-                       ] + jarPaths)
+               args = [ dxPath ]
+               if config.dCompilerName == "d8":
+                       args.append("--lib")
+                       args.append(config.env.sdk.getPlatformLibrary(config.javaApi))
+               else:
+                       args.append("--dex")
+               args.append("--output")
+               args.append(dexPath)
+
+               executeAndLog(config, args + jarPaths)
 
 class CreateKeystore (BuildStep):
        def __init__ (self):
@@ -576,7 +594,7 @@ class CreateKeystore (BuildStep):
        def update (self, config):
                executeAndLog(config, [
                                "keytool",
-                               "-genkey",
+                               "-genkeypair",
                                "-keystore", resolvePath(config, self.keystorePath),
                                "-storepass", "android",
                                "-alias", "androiddebugkey",
@@ -623,6 +641,7 @@ class BuildBaseAPK (BuildStep):
                        "-M", resolvePath(config, self.package.getManifestPath()),
                        "-I", config.env.sdk.getPlatformLibrary(config.javaApi),
                        "-F", dstPath,
+                       "-0", "arsc" # arsc files need to be uncompressed for SDK version 30 and up
                ]
 
                for resPath in self.getResPaths():
@@ -777,8 +796,8 @@ class AddNativeLibsToAPK (BuildStep):
 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.srcPath            = AlignAPK(self.package).getOutputs()[0]
+               self.dstPath            = [BuildRoot(), getBuildRootRelativeAPKPath(self.package)]
                self.keystorePath       = CreateKeystore().getOutputs()[0]
 
        def getInputs (self):
@@ -788,27 +807,31 @@ class SignAPK (BuildStep):
                return [self.dstPath]
 
        def update (self, config):
+               apksigner       = which("apksigner", [config.env.sdk.getBuildToolsPath()])
                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"
+                               apksigner,
+                               "sign",
+                               "--ks", resolvePath(config, self.keystorePath),
+                               "--ks-key-alias", "androiddebugkey",
+                               "--ks-pass", "pass:android",
+                               "--key-pass", "pass:android",
+                               "--min-sdk-version", str(config.minApi),
+                               "--max-sdk-version", str(config.javaApi),
+                               "--out", dstPath,
+                               srcPath
                        ])
 
 def getBuildRootRelativeAPKPath (package):
        return os.path.join(package.getAppDirName(), package.getAppName() + ".apk")
 
-class FinalizeAPK (BuildStep):
+class AlignAPK (BuildStep):
        def __init__ (self, package):
                self.package            = package
-               self.srcPath            = SignAPK(self.package).getOutputs()[0]
-               self.dstPath            = [BuildRoot(), getBuildRootRelativeAPKPath(self.package)]
+               self.srcPath            = AddNativeLibsToAPK(self.package, []).getOutputs()[0]
+               self.dstPath            = [BuildRoot(), self.package.getAppDirName(), "tmp", "aligned.apk"]
                self.keystorePath       = CreateKeystore().getOutputs()[0]
 
        def getInputs (self):
@@ -862,8 +885,8 @@ def getBuildStepsForPackage (abis, package, libraries = []):
 
        # Finalize APK
        steps.append(CreateKeystore())
+       steps.append(AlignAPK(package))
        steps.append(SignAPK(package))
-       steps.append(FinalizeAPK(package))
 
        return steps
 
@@ -921,6 +944,16 @@ def parseArgs ():
                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('--tool-api',
+               type=int,
+               dest='toolApi',
+               default=-1,
+               help="Android Tools level to target (-1 being maximum present)")
        parser.add_argument('--min-api',
                type=int,
                dest='minApi',
@@ -990,10 +1023,10 @@ if __name__ == "__main__":
        args            = parseArgs()
 
        ndk                     = NDKEnv(os.path.realpath(args.ndkPath))
-       sdk                     = SDKEnv(os.path.realpath(args.sdkPath))
+       sdk                     = SDKEnv(os.path.realpath(args.sdkPath), args.toolApi)
        buildPath       = os.path.realpath(args.buildRoot)
        env                     = Environment(sdk, ndk)
-       config          = Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, minApi=args.minApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget,
+       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: