From c5111c5b04f3d7c5dca153e47f949f3119cb0434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cotty?= Date: Sat, 13 Jun 2020 15:40:51 +0200 Subject: Android: Add support to generation of aab package This patch adds the Android.sdk packageType property which sets the target application. By default (previous behavior) qbs generates an apk package. When set to aab, qbs generates instead an aab package Change-Id: Ic09776d08e2a2ecd68fb4c08881201a7a0a25240 Reviewed-by: Christian Kandeler --- .travis.yml | 11 +- doc/reference/items/convenience/application.qdoc | 5 +- doc/reference/modules/android-sdk-module.qdoc | 9 + docker-compose.yml | 4 +- docker/bionic/test-android.Dockerfile | 4 + share/qbs/imports/qbs/base/AndroidApk.qbs | 2 +- share/qbs/imports/qbs/base/Application.qbs | 2 +- .../Qt/templates/android_support.qbs | 6 +- share/qbs/modules/Android/sdk/sdk.qbs | 65 ++- share/qbs/modules/Android/sdk/utils.js | 90 ++- src/lib/corelib/api/project.cpp | 5 +- src/lib/corelib/api/projectdata.cpp | 6 +- src/lib/corelib/api/projectdata_p.h | 4 +- .../testdata-android/aidl/AndroidManifest.xml | 1 + .../minimal-native/src/main/AndroidManifest.xml | 1 + tests/auto/blackbox/tst_blackboxandroid.cpp | 609 +++++++++++++-------- 16 files changed, 562 insertions(+), 262 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef86db701..e5a73e9f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,14 +64,21 @@ jobs: - docker-compose run --rm bionic scripts/run-analyzer.sh - <<: *build-on-bionic - name: With Qbs and with Qt for Android + name: With Qbs and with Qt 5.13 for Android before_install: - docker-compose pull bionic - docker-compose pull bionic-android-513 - - docker-compose pull bionic-android-514 script: - docker-compose run bionic qbs build modules.cpp.compilerWrapper:ccache modules.qbsbuildconfig.enableBundledQt:true config:release - docker-compose run bionic-android-513 scripts/test-qt-for-android.sh release/install-root/usr/local/bin + + - <<: *build-on-bionic + name: With Qbs and with Qt 5.14 for Android + before_install: + - docker-compose pull bionic + - docker-compose pull bionic-android-514 + script: + - docker-compose run bionic qbs build modules.cpp.compilerWrapper:ccache modules.qbsbuildconfig.enableBundledQt:true config:release - docker-compose run bionic-android-514 scripts/test-qt-for-android.sh release/install-root/usr/local/bin - &build-on-macos diff --git a/doc/reference/items/convenience/application.qdoc b/doc/reference/items/convenience/application.qdoc index e92247f5f..ca77e153c 100644 --- a/doc/reference/items/convenience/application.qdoc +++ b/doc/reference/items/convenience/application.qdoc @@ -41,8 +41,9 @@ The target artifact of this type of product is usually an executable binary tagged \c "application". However, on Android, unless you set \l{Product::}{consoleApplication} to \c true, - the application target will be an APK package tagged \c "android.apk", and a - dependency to the \l{Android.sdk} module is automatically added to the product. + the application target will be an APK or an AAB package tagged \c "android.package" + according to the \l{Android.sdk}{packageType} property. A dependency to the \l{Android.sdk} + module is automatically added to the product. */ /*! diff --git a/doc/reference/modules/android-sdk-module.qdoc b/doc/reference/modules/android-sdk-module.qdoc index 74599d0ba..676eaa825 100644 --- a/doc/reference/modules/android-sdk-module.qdoc +++ b/doc/reference/modules/android-sdk-module.qdoc @@ -241,3 +241,12 @@ \defaultvalue \c "aapt" */ + +/*! + \qmlproperty string Android.sdk::packageType + + Type of the package. Allowed options: "apk" and "aab". + Type "apk" generates a runnable package whereas "aab" generates a package for Google Play. + + \defaultvalue \c "apk" +*/ diff --git a/docker-compose.yml b/docker-compose.yml index ac9ba433f..b8b4528c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,7 @@ services: bionic-android-513: << : *linux hostname: bionic-android - image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.13.2-2 + image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.13.2-3 build: dockerfile: docker/bionic/test-android.Dockerfile context: . @@ -36,7 +36,7 @@ services: bionic-android-514: << : *linux hostname: bionic-android - image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.14.0-2 + image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.14.0-3 build: dockerfile: docker/bionic/test-android.Dockerfile context: . diff --git a/docker/bionic/test-android.Dockerfile b/docker/bionic/test-android.Dockerfile index 56bb0b9e6..3ec8b4808 100644 --- a/docker/bionic/test-android.Dockerfile +++ b/docker/bionic/test-android.Dockerfile @@ -89,6 +89,10 @@ RUN mkdir ${ANDROID_SDK_ROOT}/samples && \ rm -v master.zip && \ mv android-BasicMediaDecoder-master android-BasicMediaDecoder +# Download buildtool to generate aab packages in ${ANDROID_SDK_ROOT} +RUN cd ${ANDROID_SDK_ROOT} && \ + curl -sLO https://github.com/google/bundletool/releases/download/0.15.0/bundletool-all-0.15.0.jar + USER root # diff --git a/share/qbs/imports/qbs/base/AndroidApk.qbs b/share/qbs/imports/qbs/base/AndroidApk.qbs index 5f86d8457..70cf6aa93 100644 --- a/share/qbs/imports/qbs/base/AndroidApk.qbs +++ b/share/qbs/imports/qbs/base/AndroidApk.qbs @@ -32,7 +32,7 @@ import qbs.File import qbs.FileInfo Product { - type: ["android.apk"] + type: ["android.package"] qbs.targetPlatform: "android" Depends { name: "Android.sdk" } } diff --git a/share/qbs/imports/qbs/base/Application.qbs b/share/qbs/imports/qbs/base/Application.qbs index 63ffc6283..1e4f805a8 100644 --- a/share/qbs/imports/qbs/base/Application.qbs +++ b/share/qbs/imports/qbs/base/Application.qbs @@ -29,7 +29,7 @@ ****************************************************************************/ NativeBinary { - type: isForAndroid && !consoleApplication ? ["android.apk"] : ["application"] + type: isForAndroid && !consoleApplication ? ["android.package"] : ["application"] property bool usesNativeCode diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs index 342018321..2835a9336 100644 --- a/share/qbs/module-providers/Qt/templates/android_support.qbs +++ b/share/qbs/module-providers/Qt/templates/android_support.qbs @@ -18,7 +18,7 @@ Module { property string _androidDeployQtFilePath: FileInfo.joinPaths(_qtInstallDir, "bin", "androiddeployqt") property string _qtInstallDir - property bool _enableSdkSupport: product.type && product.type.contains("android.apk") + property bool _enableSdkSupport: product.type && product.type.contains("android.package") && !consoleApplication property bool _enableNdkSupport: !product.aggregate || product.multiplexConfigurationId property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android") @@ -312,7 +312,7 @@ Module { File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml", outputs["android.manifest_final"][0].filePath); var libsDir = product.Qt.android_support._deployQtOutDir + "/libs"; - var libDir = product.Android.sdk.apkContentsDir + "/lib"; + var libDir = product.Android.sdk.packageContentsDir + "/lib"; var listFilePath = outputs["android.deployqt_list"][0].filePath; var oldLibs = []; try { @@ -364,7 +364,7 @@ Module { for (var i in inputs["android.nativelibrary"]) architectures.push(inputs["android.nativelibrary"][i].Android.ndk.abi); for (var i in architectures) { - var abiDirPath = FileInfo.joinPaths(product.Android.sdk.apkContentsDir, + var abiDirPath = FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "lib", architectures[i]); var files = File.directoryEntries(abiDirPath, File.Files); for (var i = 0; i < files.length; ++i) { diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs index 681b3da94..31f3ed465 100644 --- a/share/qbs/modules/Android/sdk/sdk.qbs +++ b/share/qbs/modules/Android/sdk/sdk.qbs @@ -50,6 +50,14 @@ Module { environmentPaths: (ndkDir ? [ndkDir] : []).concat(base) } + Probes.PathProbe { + id: bundletoolProbe + platformSearchPaths: [Android.sdk.sdkDir] + names: ["bundletool-all"] + nameSuffixes: ["-0.11.0.jar", "-0.12.0.jar", "-0.13.0.jar", "-0.13.3.jar", "-0.13.4.jar", + "-0.14.0.jar", "-0.15.0.jar"] + } + property path sdkDir: sdkProbe.path property path ndkDir: ndkProbe.path property path ndkSamplesDir: ndkProbe.samplesDir @@ -60,6 +68,8 @@ Module { property int buildToolsVersionPatch: buildToolsVersionParts[2] property string platform: sdkProbe.platform + property path bundletoolFilePath: bundletoolProbe.filePath + // Product-specific properties and files property string packageName: { var idx = product.name.indexOf(".") @@ -146,6 +156,12 @@ Module { } property path aaptFilePath: FileInfo.joinPaths(buildToolsDir, aaptName) readonly property bool _enableAapt2: aaptName === "aapt2" + property string packageType: "apk" + PropertyOptions { + name: "packageType" + allowedValues: ["apk", "aab"] + } + readonly property bool _generateAab: packageType == "aab" property path apksignerFilePath: FileInfo.joinPaths(buildToolsDir, "apksigner") property path aidlFilePath: FileInfo.joinPaths(buildToolsDir, "aidl") @@ -160,7 +176,7 @@ Module { (packageName || "").split('.').join('/')) property path compiledResourcesDir: FileInfo.joinPaths(product.buildDirectory, "compiled_resources") - property string apkContentsDir: FileInfo.joinPaths(product.buildDirectory, "bin") + property string packageContentsDir: FileInfo.joinPaths(product.buildDirectory, packageType) property string debugKeyStorePath: FileInfo.joinPaths( Environment.getEnv(qbs.hostOS.contains("windows") ? "USERPROFILE" : "HOME"), @@ -186,6 +202,14 @@ Module { + "ANDROID_HOME environment variable to a valid " + "Android SDK location."); } + if (!bundletoolFilePath && _generateAab) { + throw ModUtils.ModuleError("Could not find Android bundletool at the following " + + "location:\n\t" + Android.sdk.sdkDir + + "\nInstall the Android bundletool to the above location, " + + "or set the Android.sdk.bundletoolFilePath property.\n" + + "Android bundletool can be downloaded from " + + "https://github.com/google/bundletool"); + } } FileTagger { @@ -366,12 +390,13 @@ Module { condition: _enableRules && _enableAapt2 multiplex: true inputs: ["android.resources_compiled", "android.assets", "android.manifest_final"] - outputFileTags: ["java.java", "android.apk_base"] + outputFileTags: ["java.java", "android.apk_resources"] outputArtifacts: { var artifacts = []; artifacts.push({ - filePath: product.Android.sdk.apkBaseName + ".apk_base", - fileTags: ["android.apk_base"] + filePath: product.Android.sdk.apkBaseName + (product.Android.sdk._generateAab ? + ".apk_aab" : ".apk_apk"), + fileTags: ["android.apk_resources"] }); var resources = inputs["android.resources_compiled"]; if (resources && resources.length) { @@ -419,7 +444,10 @@ Module { inputs: ["java.class"] inputsFromDependencies: ["java.jar"] Artifact { - filePath: FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "classes.dex") + filePath: product.Android.sdk._generateAab ? + FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "dex", + "classes.dex") : + FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "classes.dex") fileTags: ["android.dex"] } prepare: SdkUtils.prepareDex.apply(SdkUtils, arguments) @@ -431,7 +459,7 @@ Module { inputsFromDependencies: inputTags inputs: product.aggregate ? [] : inputTags Artifact { - filePath: FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "lib", + filePath: FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "lib", input.Android.ndk.abi, input.fileName) fileTags: "android.nativelibrary_deployed" } @@ -475,7 +503,7 @@ Module { } Rule { - condition: _enableRules && !_enableAapt2 + condition: _enableRules && !_enableAapt2 && !_generateAab multiplex: true inputs: [ "android.resources", "android.assets", "android.manifest_final", @@ -484,23 +512,38 @@ Module { ] Artifact { filePath: product.Android.sdk.apkBaseName + ".apk" - fileTags: "android.apk" + fileTags: "android.package" } prepare: SdkUtils.prepareAaptPackage.apply(SdkUtils, arguments) } Rule { - condition: _enableRules && _enableAapt2 + condition: _enableRules && _enableAapt2 && !_generateAab multiplex: true inputs: [ - "android.apk_base", "android.manifest_final", + "android.apk_resources", "android.manifest_final", "android.dex", "android.stl_deployed", "android.nativelibrary_deployed", "android.keystore" ] Artifact { filePath: product.Android.sdk.apkBaseName + ".apk" - fileTags: "android.apk" + fileTags: "android.package" } prepare: SdkUtils.prepareApkPackage.apply(SdkUtils, arguments) } + + Rule { + condition: _enableRules && _enableAapt2 && _generateAab + multiplex: true + inputs: [ + "android.apk_resources", "android.manifest_final", + "android.dex", "android.stl_deployed", + "android.nativelibrary_deployed" + ] + Artifact { + filePath: product.Android.sdk.apkBaseName + ".aab" + fileTags: "android.package" + } + prepare: SdkUtils.prepareBundletoolPackage.apply(SdkUtils, arguments) + } } diff --git a/share/qbs/modules/Android/sdk/utils.js b/share/qbs/modules/Android/sdk/utils.js index b1c929e68..63bf1f5c4 100644 --- a/share/qbs/modules/Android/sdk/utils.js +++ b/share/qbs/modules/Android/sdk/utils.js @@ -79,7 +79,7 @@ function prepareDex(project, product, inputs, outputs, input, output, explicitly args = args.concat(jarFiles); var cmd = new Command(dxFilePath, args); - cmd.description = "Creating " + output.fileName; + cmd.description = "creating " + output.fileName; return [cmd]; } @@ -183,13 +183,12 @@ function prepareAapt2CompileResource(project, product, inputs, outputs, input, o function prepareAapt2Link(project, product, inputs, outputs, input, output, explicitlyDependsOn) { var cmds = []; + var baseOutputFilePath = outputs["android.apk_resources"][0].filePath; var manifestFilePath = inputs["android.manifest_final"][0].filePath; var compilesResourcesDir = product.Android.sdk.compiledResourcesDir; - var apkOutputFilePath = outputs["android.apk_base"][0].filePath; var compiledResources = inputs["android.resources_compiled"]; - var args = ["link", "-o", apkOutputFilePath, "-I", product.Android.sdk.androidJarFilePath]; - //For aab: args.push("link", "--proto-format"); + var args = ["link", "-o", baseOutputFilePath, "-I", product.Android.sdk.androidJarFilePath]; var i = 0; if (compiledResources) { for (i = 0; i < compiledResources.length; ++i) @@ -217,9 +216,10 @@ function prepareAapt2Link(project, product, inputs, outputs, input, output, expl args.push("-A", assetDirs[i]); if (product.qbs.buildVariant === "debug") args.push("-v"); - + if (product.Android.sdk._generateAab) + args.push("--proto-format"); var cmd = new Command(product.Android.sdk.aaptFilePath, args); - cmd.description = "Linking resources"; + cmd.description = "linking resources"; cmd.workingDirectory = product.buildDirectory; cmds.push(cmd); @@ -234,18 +234,18 @@ function prepareAaptGenerate(project, product, inputs, outputs, input, output, if (resources && resources.length) args.push("-J", ModUtils.moduleProperty(product, "generatedJavaFilesBaseDir")); var cmd = new Command(product.Android.sdk.aaptFilePath, args); - cmd.description = "Processing resources"; + cmd.description = "processing resources"; return [cmd]; } function prepareAaptPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { var cmds = []; - var apkOutput = outputs["android.apk"][0]; + var apkOutput = outputs["android.package"][0]; var args = commonAaptPackageArgs.apply(this, arguments); args.push("-F", apkOutput.filePath + ".unaligned"); - args.push(product.Android.sdk.apkContentsDir); + args.push(product.Android.sdk.packageContentsDir); var cmd = new Command(product.Android.sdk.aaptFilePath, args); - cmd.description = "Generating " + apkOutput.filePath; + cmd.description = "generating " + apkOutput.fileName; cmds.push(cmd); if (!product.Android.sdk.useApksigner) { @@ -255,7 +255,7 @@ function prepareAaptPackage(project, product, inputs, outputs, input, output, ex apkOutput.filePath + ".unaligned", "androiddebugkey"]; cmd = new Command(product.java.jarsignerFilePath, args); - cmd.description = "Signing " + apkOutput.fileName; + cmd.description = "signing " + apkOutput.fileName; cmds.push(cmd); } @@ -277,7 +277,7 @@ function prepareAaptPackage(project, product, inputs, outputs, input, output, ex "--ks-pass", "pass:android", apkOutput.filePath]; cmd = new Command(product.Android.sdk.apksignerFilePath, args); - cmd.description = "Signing " + apkOutput.fileName; + cmd.description = "signing " + apkOutput.fileName; cmds.push(cmd); } @@ -286,9 +286,9 @@ function prepareAaptPackage(project, product, inputs, outputs, input, output, ex function prepareApkPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { var cmds = []; - var apkInputFilePath = inputs["android.apk_base"][0].filePath; - var apkOutput = outputs["android.apk"][0]; - var apkOutputFilePathUnaligned = outputs["android.apk"][0].filePath + ".unaligned"; + var apkInputFilePath = inputs["android.apk_resources"][0].filePath; + var apkOutput = outputs["android.package"][0]; + var apkOutputFilePathUnaligned = outputs["android.package"][0].filePath + ".unaligned"; var dexFilePath = inputs["android.dex"][0].filePath; var copyCmd = new JavaScriptCommand(); @@ -301,7 +301,7 @@ function prepareApkPackage(project, product, inputs, outputs, input, output, exp cmds.push(copyCmd); var jarArgs = ["-uvf", apkOutputFilePathUnaligned, "."]; - var libPath = FileInfo.joinPaths(product.Android.sdk.apkContentsDir); + var libPath = FileInfo.joinPaths(product.Android.sdk.packageContentsDir); var jarCmd = new Command(product.java.jarFilePath, jarArgs); jarCmd.description = "packaging files"; jarCmd.workingDirectory = libPath; @@ -314,7 +314,7 @@ function prepareApkPackage(project, product, inputs, outputs, input, output, exp apkOutputFilePathUnaligned, "androiddebugkey"]; cmd = new Command(product.java.jarsignerFilePath, args); - cmd.description = "Signing " + apkOutput.fileName; + cmd.description = "signing " + apkOutput.fileName; cmds.push(cmd); } @@ -336,13 +336,65 @@ function prepareApkPackage(project, product, inputs, outputs, input, output, exp "--ks-pass", "pass:android", apkOutput.filePath]; cmd = new Command(product.Android.sdk.apksignerFilePath, args); - cmd.description = "Signing " + apkOutput.fileName; + cmd.description = "signing " + apkOutput.fileName; cmds.push(cmd); } return cmds; } +function prepareBundletoolPackage(project, product, inputs, outputs, input, output, + explicitlyDependsOn) { + var cmds = []; + var baseModuleDir = product.Android.sdk.packageContentsDir; + var manifestDirName = FileInfo.joinPaths(baseModuleDir, "manifest"); + var pkgBaseFileName = inputs["android.apk_resources"][0].filePath; + + var jarResourcesArgs = ["xf", pkgBaseFileName]; + var jarResourcesCmd = new Command(product.java.jarFilePath, jarResourcesArgs); + jarResourcesCmd.description = "extracting resources apk"; + jarResourcesCmd.workingDirectory = baseModuleDir; + cmds.push(jarResourcesCmd); + + var moveManifestCmd = new JavaScriptCommand(); + moveManifestCmd.description = "moving manifest in manifest directory"; + moveManifestCmd.path = manifestDirName; + moveManifestCmd.manifestFilePath = baseModuleDir + "/AndroidManifest.xml"; + moveManifestCmd.sourceCode = function() { + if (!File.exists(path)) + File.makePath(path); + if (File.exists(manifestFilePath)) + File.move(manifestFilePath, path + "/AndroidManifest.xml"); + } + cmds.push(moveManifestCmd); + + var baseFilePath = FileInfo.joinPaths(product.buildDirectory, "base.zip"); + var jarBaseArgs = ["cfM", baseFilePath, "."]; + var jarBaseCmd = new Command(product.java.jarFilePath, jarBaseArgs); + jarBaseCmd.description = "compressing base module"; + jarBaseCmd.workingDirectory = baseModuleDir; + cmds.push(jarBaseCmd); + + var aabFilePath = outputs["android.package"][0].filePath; + var removeCmd = new JavaScriptCommand(); + removeCmd.description = "removing previous aab"; + removeCmd.filePath = aabFilePath; + removeCmd.sourceCode = function() { + if (File.exists(filePath)) + File.remove(filePath); + } + cmds.push(removeCmd); + + var args = ["-jar", product.Android.sdk.bundletoolFilePath, "build-bundle"]; + args.push("--modules=" + baseFilePath); + args.push("--output=" + aabFilePath); + var cmd = new Command(product.java.interpreterFilePath, args); + cmd.description = "generating " + outputs["android.package"][0].fileName; + cmds.push(cmd); + + return cmds; +} + function createDebugKeyStoreCommandString(keytoolFilePath, keystoreFilePath) { var args = ["-genkey", "-keystore", keystoreFilePath, "-alias", "androiddebugkey", "-storepass", "android", "-keypass", "android", "-keyalg", "RSA", @@ -365,7 +417,7 @@ function stlDeploymentData(product, inputs, type) uniqueFilePaths.push(currentInput.filePath); data.uniqueInputs.push(currentInput); var outputFileName = currentInput.fileName; - data.outputFilePaths.push(FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "lib", + data.outputFilePaths.push(FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "lib", currentInput.Android.ndk.abi, outputFileName)); } diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index 378b13dfe..f7a9eca09 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -635,7 +635,10 @@ static bool productIsRunnable(const ResolvedProductConstPtr &product) { const bool isBundle = product->moduleProperties->moduleProperty( QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); - return isRunnableArtifact(product->fileTags, isBundle); + const QString androidSdkPackageType = product->moduleProperties->moduleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(product->fileTags, isBundle, isAndroidApk); } static bool productIsMultiplexed(const ResolvedProductConstPtr &product) diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp index cb7d12b98..c378fbea4 100644 --- a/src/lib/corelib/api/projectdata.cpp +++ b/src/lib/corelib/api/projectdata.cpp @@ -323,8 +323,10 @@ bool ArtifactData::isExecutable() const { const bool isBundle = d->properties.getModuleProperty( QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); - return isRunnableArtifact( - FileTags::fromStringList(d->fileTags), isBundle); + const QString androidSdkPackageType = d->properties.getModuleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(FileTags::fromStringList(d->fileTags), isBundle, isAndroidApk); } /*! diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h index 834aeec23..e241ea92c 100644 --- a/src/lib/corelib/api/projectdata_p.h +++ b/src/lib/corelib/api/projectdata_p.h @@ -123,11 +123,11 @@ public: QString buildDir; }; -inline bool isRunnableArtifact(const FileTags &fileTags, bool isBundle) +inline bool isRunnableArtifact(const FileTags &fileTags, bool isBundle, bool isAndroidApk) { return (fileTags.contains("application") && (!isBundle || fileTags.contains("bundle.content"))) || fileTags.contains("bundle.application-executable") - || fileTags.contains("android.apk") + || (fileTags.contains("android.package") && isAndroidApk) || fileTags.contains("msi"); } diff --git a/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml index e8a950847..1d27681ac 100644 --- a/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml +++ b/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml @@ -1,5 +1,6 @@ diff --git a/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml index 575e95e8d..f61dc9850 100644 --- a/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml +++ b/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp index e8d44188c..5e850874e 100644 --- a/tests/auto/blackbox/tst_blackboxandroid.cpp +++ b/tests/auto/blackbox/tst_blackboxandroid.cpp @@ -81,6 +81,8 @@ void TestBlackboxAndroid::android() QFETCH(QList, expectedFilesLists); QFETCH(QStringList, qmlAppCustomProperties); QFETCH(bool, enableAapt2); + QFETCH(bool, generateAab); + QFETCH(bool, isIncrementalBuild); const SettingsPtr s = settings(); Profile p(theProfileName(projectDir == "qml-app"), s.get()); @@ -104,7 +106,7 @@ void TestBlackboxAndroid::android() && ndkSamplesDirs.contains(projectDir)) QSKIP("NDK samples directory not present"); - const QString aaptVersion = enableAapt2 ? "aapt2" : "aapt"; + const QString buildSubDir = enableAapt2 ? (generateAab ? "aab" : "aapt2") : "aapt"; QDir::setCurrent(testDataDir + "/" + projectDir); static const QStringList configNames { "debug", "release" }; @@ -112,29 +114,29 @@ void TestBlackboxAndroid::android() auto currentExpectedFilesLists = expectedFilesLists; const QString configArgument = "config:" + configName; QbsRunParameters resolveParams("resolve"); - resolveParams.buildDirectory = aaptVersion; + resolveParams.buildDirectory = buildSubDir; resolveParams.arguments << configArgument << qmlAppCustomProperties; resolveParams.profile = p.name(); QCOMPARE(runQbs(resolveParams), 0); QbsRunParameters buildParams(QStringList{"--command-echo-mode", "command-line", configArgument}); - buildParams.buildDirectory = aaptVersion; + buildParams.buildDirectory = buildSubDir; buildParams.profile = p.name(); QCOMPARE(runQbs(buildParams), 0); for (const QString &productName : qAsConst(productNames)) { const QByteArray tag(QTest::currentDataTag()); - const bool isIncrementalBuild = tag.startsWith("qml app") && tag != "qml app"; QCOMPARE(m_qbsStdout.count("Generating BuildConfig.java"), isIncrementalBuild ? 0 : productNames.size()); - QVERIFY(m_qbsStdout.contains(productName.toLocal8Bit() + ".apk")); - const QString apkFilePath = aaptVersion + "/" + relativeProductBuildDir(productName, - configName) - + '/' + productName + ".apk"; - QVERIFY2(regularFileExists(apkFilePath), qPrintable(apkFilePath)); + const QString packageName = productName + (generateAab ? ".aab" : ".apk"); + QVERIFY(m_qbsStdout.contains(packageName.toLocal8Bit())); + const QString packageFilePath = buildSubDir + "/" + relativeProductBuildDir(productName, + configName) + + '/' + packageName; + QVERIFY2(regularFileExists(packageFilePath), qPrintable(packageFilePath)); const QString jarFilePath = androidPaths["jar"]; QVERIFY(!jarFilePath.isEmpty()); QProcess jar; - jar.start(jarFilePath, QStringList() << "-tf" << apkFilePath); + jar.start(jarFilePath, QStringList() << "-tf" << packageFilePath); QVERIFY2(jar.waitForStarted(), qPrintable(jar.errorString())); QVERIFY2(jar.waitForFinished(), qPrintable(jar.errorString())); QVERIFY2(jar.exitCode() == 0, qPrintable(jar.readAllStandardError().constData())); @@ -209,7 +211,8 @@ void TestBlackboxAndroid::android_data() const auto cxxLibPath = [&p, &pQt](const QByteArray &oldcxxLib, bool forQt) { const bool usesClang = (forQt ? pQt : p).value(QStringLiteral("qbs.toolchainType")) .toString() == "clang"; - return QByteArray("lib/${ARCH}/") + (usesClang ? "libc++_shared.so" : oldcxxLib); + const QByteArray path = "lib/${ARCH}/"; + return path + (usesClang ? "libc++_shared.so" : oldcxxLib); }; bool usingOldQt = true; QStringList qmakeFilePaths = pQt.value(QStringLiteral("moduleProviders.Qt.qmakeFilePaths")). @@ -246,129 +249,229 @@ void TestBlackboxAndroid::android_data() .replace("arm64", "arm64-v8a"); }); - auto expandArchs = [] (const QByteArrayList &archs, const QByteArrayList &lst) { + auto expandArchs = [] (const QByteArrayList &archs, const QByteArrayList &lst, bool aabPackage) { const QByteArray &archPlaceHolder = "${ARCH}"; QByteArrayList result; + QByteArray base( aabPackage ? "base/" : QByteArray()); for (const QByteArray &entry : lst) { if (entry.contains(archPlaceHolder)) { for (const QByteArray &arch : qAsConst(archs)) - result << QByteArray(entry).replace(archPlaceHolder, arch); + result << (base + QByteArray(entry).replace(archPlaceHolder, arch)); } else { - result << entry; + result << (base + entry); } } return result; }; - const QByteArrayList commonFiles = expandArchs(archs, { - "AndroidManifest.xml", "META-INF/ANDROIDD.RSA", "META-INF/ANDROIDD.SF", - "META-INF/MANIFEST.MF", "classes.dex" - }); + auto commonFiles = [](bool generateAab) { + if (generateAab) + return (QByteArrayList() + << "base/manifest/AndroidManifest.xml" << "base/dex/classes.dex" + << "BundleConfig.pb"); + return (QByteArrayList() + << "AndroidManifest.xml" << "META-INF/ANDROIDD.RSA" << "META-INF/ANDROIDD.SF" + << "META-INF/MANIFEST.MF" << "classes.dex"); + }; QTest::addColumn("projectDir"); QTest::addColumn("productNames"); QTest::addColumn>("expectedFilesLists"); QTest::addColumn("qmlAppCustomProperties"); QTest::addColumn("enableAapt2"); + QTest::addColumn("generateAab"); + QTest::addColumn("isIncrementalBuild"); const auto aaptVersion = [](bool enableAapt2) { return QString("modules.Android.sdk.aaptName:") + (enableAapt2 ? "aapt2" : "aapt"); }; bool enableAapt2 = false; - QByteArrayList teaPotAppExpectedFiles; - teaPotAppExpectedFiles << commonFiles + expandArchs(archs, { - "resources.arsc", - "assets/Shaders/ShaderPlain.fsh", - "assets/Shaders/VS_ShaderPlain.vsh", - cxxLibPath("libgnustl_shared.so", false), - "lib/${ARCH}/libTeapotNativeActivity.so", - "res/layout/widgets.xml", - "res/mipmap-hdpi-v4/ic_launcher.png", - "res/mipmap-mdpi-v4/ic_launcher.png", - "res/mipmap-xhdpi-v4/ic_launcher.png", - "res/mipmap-xxhdpi-v4/ic_launcher.png"}); + const auto packageType = [](bool generateAab) { + return QString("modules.Android.sdk.packageType:") + (generateAab ? "aab" : "apk"); + }; + bool generateAab = false; + bool isIncrementalBuild = false; + + auto teaPotAppExpectedFiles = [&](const QByteArrayList &archs, bool generateAab) { + QByteArrayList expectedFile; + expectedFile << commonFiles(generateAab) + expandArchs(archs, { + "assets/Shaders/ShaderPlain.fsh", + "assets/Shaders/VS_ShaderPlain.vsh", + cxxLibPath("libgnustl_shared.so", false), + "lib/${ARCH}/libTeapotNativeActivity.so", + "res/layout/widgets.xml", + "res/mipmap-hdpi-v4/ic_launcher.png", + "res/mipmap-mdpi-v4/ic_launcher.png", + "res/mipmap-xhdpi-v4/ic_launcher.png", + "res/mipmap-xxhdpi-v4/ic_launcher.png"}, generateAab); + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + QTest::newRow("teapot") << "teapot" << QStringList("TeapotNativeActivity") - << (QList() << teaPotAppExpectedFiles) - << QStringList{aaptVersion(enableAapt2)} << enableAapt2; + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("teapot") + QTest::newRow("teapot aapt2") + << "teapot" << QStringList("TeapotNativeActivity") + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("teapot aapt2 aab") << "teapot" << QStringList("TeapotNativeActivity") - << (QList() << teaPotAppExpectedFiles) - << QStringList{aaptVersion(enableAapt2)} << enableAapt2; + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; QTest::newRow("minimal-native") << "minimal-native" << QStringList("minimalnative") - << (QList() << commonFiles + expandArchs({archs.first()}, { + << (QList() << commonFiles(generateAab) + expandArchs({archs.first()}, { "lib/${ARCH}/libminimalnative.so", cxxLibPath("libstlport_shared.so", false), - "lib/${ARCH}/libdependency.so"})) + "lib/${ARCH}/libdependency.so"}, generateAab)) << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", "modules.qbs.architecture:" + archsStringList.first(), aaptVersion(enableAapt2)} - << enableAapt2; + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("minimal-native") + QTest::newRow("minimal-native aapt2") << "minimal-native" << QStringList("minimalnative") - << (QList() << commonFiles + expandArchs({archs.first()}, { - "resources.arsc", + << (QList() << commonFiles(generateAab) + + (QByteArrayList() << "resources.arsc") + expandArchs({archs.first()}, { "lib/${ARCH}/libminimalnative.so", cxxLibPath("libstlport_shared.so", false), - "lib/${ARCH}/libdependency.so"})) + "lib/${ARCH}/libdependency.so"}, generateAab)) << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", "modules.qbs.architecture:" + archsStringList.first(), - aaptVersion(enableAapt2)} - << enableAapt2; - QByteArrayList qmlAppExpectedFiles; - QByteArrayList qmlAppMinistroExpectedFiles; - QByteArrayList qmlAppCustomMetaDataExpectedFiles; - QStringList qmlAppCustomProperties; - if (usingOldQt) { - qmlAppExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", - "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/plugins.qmltypes", - "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/qmldir", - "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/plugins.qmltypes", - "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/qmldir", - "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", - cxxLibPath("libgnustl_shared.so", true), - "lib/${ARCH}/libplugins_bearer_libqandroidbearer.so", - "lib/${ARCH}/libplugins_imageformats_libqgif.so", - "lib/${ARCH}/libplugins_imageformats_libqicns.so", - "lib/${ARCH}/libplugins_imageformats_libqico.so", - "lib/${ARCH}/libplugins_imageformats_libqjpeg.so", - "lib/${ARCH}/libplugins_imageformats_libqtga.so", - "lib/${ARCH}/libplugins_imageformats_libqtiff.so", - "lib/${ARCH}/libplugins_imageformats_libqwbmp.so", - "lib/${ARCH}/libplugins_imageformats_libqwebp.so", - "lib/${ARCH}/libplugins_platforms_android_libqtforandroid.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_debugger.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_inspector.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_local.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_messages.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_native.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_nativedebugger.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_profiler.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_preview.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_quickprofiler.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_server.so", - "lib/${ARCH}/libplugins_qmltooling_libqmldbg_tcp.so", - "lib/${ARCH}/libqml_QtQuick.2_libqtquick2plugin.so", - "lib/${ARCH}/libqml_QtQuick_Window.2_libwindowplugin.so", - "lib/${ARCH}/libQt5Core.so", - "lib/${ARCH}/libQt5Gui.so", - "lib/${ARCH}/libQt5Network.so", - "lib/${ARCH}/libQt5Qml.so", - "lib/${ARCH}/libQt5QuickParticles.so", - "lib/${ARCH}/libQt5Quick.so", - "lib/${ARCH}/libqmlapp.so"}); - qmlAppMinistroExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("minimal-native aapt2 aab") + << "minimal-native" << QStringList("minimalnative") + << (QList() << commonFiles(generateAab) + + (QByteArrayList() << "base/resources.pb" << "base/native.pb") + + expandArchs({archs.first()}, { + "lib/${ARCH}/libminimalnative.so", + cxxLibPath("libstlport_shared.so", false), + "lib/${ARCH}/libdependency.so"}, generateAab)) + << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", + "modules.qbs.architecture:" + archsStringList.first(), + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + auto qmlAppExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/qmldir", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/qmldir", + "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_libqandroidbearer.so", + "lib/${ARCH}/libplugins_imageformats_libqgif.so", + "lib/${ARCH}/libplugins_imageformats_libqicns.so", + "lib/${ARCH}/libplugins_imageformats_libqico.so", + "lib/${ARCH}/libplugins_imageformats_libqjpeg.so", + "lib/${ARCH}/libplugins_imageformats_libqtga.so", + "lib/${ARCH}/libplugins_imageformats_libqtiff.so", + "lib/${ARCH}/libplugins_imageformats_libqwbmp.so", + "lib/${ARCH}/libplugins_imageformats_libqwebp.so", + "lib/${ARCH}/libplugins_platforms_android_libqtforandroid.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_debugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_inspector.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_local.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_messages.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_native.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_nativedebugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_profiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_preview.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_quickprofiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_server.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_tcp.so", + "lib/${ARCH}/libqml_QtQuick.2_libqtquick2plugin.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_libwindowplugin.so", + "lib/${ARCH}/libQt5Core.so", + "lib/${ARCH}/libQt5Gui.so", + "lib/${ARCH}/libQt5Network.so", + "lib/${ARCH}/libQt5Qml.so", + "lib/${ARCH}/libQt5QuickParticles.so", + "lib/${ARCH}/libQt5Quick.so", + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/android_rcc_bundle.rcc", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_qandroidbearer_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qgif_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qicns_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qico_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qjpeg_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtga_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so", + "lib/${ARCH}/libplugins_platforms_qtforandroid_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_debugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_inspector_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_local_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_messages_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_native_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_nativedebugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_profiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_preview_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_quickprofiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_server_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_tcp_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick.2_qtquick2plugin_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_windowplugin_${ARCH}.so", + "lib/${ARCH}/libQt5Core_${ARCH}.so", + "lib/${ARCH}/libQt5Gui_${ARCH}.so", + "lib/${ARCH}/libQt5Network_${ARCH}.so", + "lib/${ARCH}/libQt5Qml_${ARCH}.so", + "lib/${ARCH}/libQt5QuickParticles_${ARCH}.so", + "lib/${ARCH}/libQt5Quick_${ARCH}.so", + "lib/${ARCH}/libQt5QmlModels_${ARCH}.so", + "lib/${ARCH}/libQt5QmlWorkerScript_${ARCH}.so", + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + + auto qmlAppMinistroExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", cxxLibPath("libgnustl_shared.so", true), - "lib/${ARCH}/libqmlapp.so"}); - qmlAppCustomMetaDataExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/android_rcc_bundle.rcc", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + auto qmlAppCustomMetaDataExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/plugins.qmltypes", "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/qmldir", "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/plugins.qmltypes", @@ -405,53 +508,9 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libQt5Qml.so", "lib/${ARCH}/libQt5QuickParticles.so", "lib/${ARCH}/libQt5Quick.so", - "lib/${ARCH}/libqmlapp.so"}); - qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false", - "modules.qbs.architecture:" + archsForQt.first()}; - } else { - qmlAppExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", - "assets/android_rcc_bundle.rcc", - cxxLibPath("libgnustl_shared.so", true), - "lib/${ARCH}/libplugins_bearer_qandroidbearer_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qgif_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qicns_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qico_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qjpeg_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qtga_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so", - "lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so", - "lib/${ARCH}/libplugins_platforms_qtforandroid_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_debugger_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_inspector_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_local_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_messages_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_native_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_nativedebugger_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_profiler_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_preview_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_quickprofiler_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_server_${ARCH}.so", - "lib/${ARCH}/libplugins_qmltooling_qmldbg_tcp_${ARCH}.so", - "lib/${ARCH}/libqml_QtQuick.2_qtquick2plugin_${ARCH}.so", - "lib/${ARCH}/libqml_QtQuick_Window.2_windowplugin_${ARCH}.so", - "lib/${ARCH}/libQt5Core_${ARCH}.so", - "lib/${ARCH}/libQt5Gui_${ARCH}.so", - "lib/${ARCH}/libQt5Network_${ARCH}.so", - "lib/${ARCH}/libQt5Qml_${ARCH}.so", - "lib/${ARCH}/libQt5QuickParticles_${ARCH}.so", - "lib/${ARCH}/libQt5Quick_${ARCH}.so", - "lib/${ARCH}/libQt5QmlModels_${ARCH}.so", - "lib/${ARCH}/libQt5QmlWorkerScript_${ARCH}.so", - "lib/${ARCH}/libqmlapp_${ARCH}.so"}); - qmlAppMinistroExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", - "assets/android_rcc_bundle.rcc", - cxxLibPath("libgnustl_shared.so", true), - "lib/${ARCH}/libqmlapp_${ARCH}.so"}); - qmlAppCustomMetaDataExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { - "resources.arsc", + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { "assets/android_rcc_bundle.rcc", "assets/dummyasset.txt", cxxLibPath("libgnustl_shared.so", true), @@ -486,61 +545,115 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libQt5Quick_${ARCH}.so", "lib/${ARCH}/libQt5QmlModels_${ARCH}.so", "lib/${ARCH}/libQt5QmlWorkerScript_${ARCH}.so", - "lib/${ARCH}/libqmlapp_${ARCH}.so"}); + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + QStringList qmlAppCustomProperties; + if (usingOldQt) { + qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false", + "modules.qbs.architecture:" + archsForQt.first()}; + } else { qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false"}; } + // aapt tool for the resources works with a directory option pointing to the parent directory // of the resources (res). // The Qt.android_support module adds res/values/libs.xml (from Qt install dir). So the res from // Qt install res directory is added to aapt. This results in adding res/layout/splash.xml to // the package eventhough the file is not needed. // On the other hand aapt2 requires giving all the resources files. - // Also when enabling aapt2 the resources.arsc is always created, eventhough no resources is + // Also when enabling aapt2 the resources.arsc is always created, eventhough no resources are // declared. enableAapt2 = false; + generateAab = false; QTest::newRow("qml app") << "qml-app" << QStringList("qmlapp") - << (QList() << (QByteArrayList() << qmlAppExpectedFiles + << (QList() << (QByteArrayList() << qmlAppExpectedFiles(generateAab) << "res/layout/splash.xml")) - << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2)) << enableAapt2; + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; - QTest::newRow("qml app") + QTest::newRow("qml app aapt2") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppExpectedFiles(generateAab)) + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app aab") << "qml-app" << QStringList("qmlapp") - << (QList() << qmlAppExpectedFiles) - << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2)) << enableAapt2; + << (QList() << qmlAppExpectedFiles(generateAab)) + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; + isIncrementalBuild = true; QTest::newRow("qml app using Ministro") << "qml-app" << QStringList("qmlapp") - << (QList() << (QByteArrayList() << qmlAppMinistroExpectedFiles + << (QList() << (QByteArrayList() + << qmlAppMinistroExpectedFiles(generateAab) << "res/layout/splash.xml")) - << QStringList{"modules.Qt.android_support.useMinistro:true", - "modules.Android.sdk.automaticSources:false", - aaptVersion(enableAapt2)} << enableAapt2; + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("qml app using Ministro") + QTest::newRow("qml app using Ministro aapt2") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppMinistroExpectedFiles(generateAab)) + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app using Ministro aab") << "qml-app" << QStringList("qmlapp") - << (QList() << qmlAppMinistroExpectedFiles) - << QStringList{"modules.Qt.android_support.useMinistro:true", - "modules.Android.sdk.automaticSources:false", - aaptVersion(enableAapt2)} << enableAapt2; + << (QList() << qmlAppMinistroExpectedFiles(generateAab)) + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; QTest::newRow("qml app with custom metadata") << "qml-app" << QStringList("qmlapp") - << (QList() << (QByteArrayList() << qmlAppCustomMetaDataExpectedFiles + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab) << "res/layout/splash.xml")) << QStringList{"modules.Android.sdk.automaticSources:true", - aaptVersion(enableAapt2)} << enableAapt2; + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("qml app with custom metadata") + QTest::newRow("qml app with custom metadata aapt2") << "qml-app" << QStringList("qmlapp") - << (QList() << qmlAppCustomMetaDataExpectedFiles) - << QStringList{"modules.Android.sdk.automaticSources:true", - aaptVersion(enableAapt2)} << enableAapt2; + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab))) + << QStringList{"modules.Android.sdk.automaticSources:true", aaptVersion(enableAapt2), + packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app with custom metadata aab") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab))) + << QStringList{"modules.Android.sdk.automaticSources:true", aaptVersion(enableAapt2), + packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + isIncrementalBuild = false; enableAapt2 = false; + generateAab = false; QTest::newRow("no native") << "no-native" << QStringList("com.example.android.basicmediadecoder") - << (QList() << commonFiles + expandArchs(archs, { + << (QList() << commonFiles(generateAab) + expandArchs(archs, { "resources.arsc", "res/drawable-hdpi-v4/ic_action_play_disabled.png", "res/drawable-hdpi-v4/ic_action_play.png", @@ -557,87 +670,151 @@ void TestBlackboxAndroid::android_data() "res/layout/sample_main.xml", "res/menu/action_menu.xml", "res/menu-v11/action_menu.xml", - "res/raw/vid_bigbuckbunny.mp4"})) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + "res/raw/vid_bigbuckbunny.mp4"}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("no native") + auto noNativeExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + expectedFile << commonFiles(generateAab) + expandArchs(archs, { + "res/drawable-hdpi-v4/ic_action_play_disabled.png", + "res/drawable-hdpi-v4/ic_action_play.png", + "res/drawable-hdpi-v4/ic_launcher.png", + "res/drawable-hdpi-v4/tile.9.png", + "res/drawable-mdpi-v4/ic_action_play_disabled.png", + "res/drawable-mdpi-v4/ic_action_play.png", + "res/drawable-mdpi-v4/ic_launcher.png", + "res/drawable/selector_play.xml", + "res/drawable-xhdpi-v4/ic_action_play_disabled.png", + "res/drawable-xhdpi-v4/ic_action_play.png", + "res/drawable-xhdpi-v4/ic_launcher.png", + "res/drawable-xxhdpi-v4/ic_launcher.png", + "res/layout/sample_main.xml", + "res/menu/action_menu.xml", + // I have no idea why this file is generated with aapt and not with aapt2 + //"res/menu-v11/action_menu.xml", + "res/raw/vid_bigbuckbunny.mp4"}, generateAab); + if (generateAab) + expectedFile << "base/resources.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + QTest::newRow("no native aapt2") << "no-native" << QStringList("com.example.android.basicmediadecoder") - << (QList() << commonFiles + expandArchs(archs, { - "resources.arsc", - "res/drawable-hdpi-v4/ic_action_play_disabled.png", - "res/drawable-hdpi-v4/ic_action_play.png", - "res/drawable-hdpi-v4/ic_launcher.png", - "res/drawable-hdpi-v4/tile.9.png", - "res/drawable-mdpi-v4/ic_action_play_disabled.png", - "res/drawable-mdpi-v4/ic_action_play.png", - "res/drawable-mdpi-v4/ic_launcher.png", - "res/drawable/selector_play.xml", - "res/drawable-xhdpi-v4/ic_action_play_disabled.png", - "res/drawable-xhdpi-v4/ic_action_play.png", - "res/drawable-xhdpi-v4/ic_launcher.png", - "res/drawable-xxhdpi-v4/ic_launcher.png", - "res/layout/sample_main.xml", - "res/menu/action_menu.xml", - // I have no idea why this file is generated with aapt and not with aapt2 - //"res/menu-v11/action_menu.xml", - "res/raw/vid_bigbuckbunny.mp4"})) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + << (QList() << noNativeExpectedFiles(generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("no native aab") + << "no-native" + << QStringList("com.example.android.basicmediadecoder") + << (QList() << noNativeExpectedFiles(generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") - << (QList() << (QByteArrayList() << commonFiles)) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + << (QList() << (QByteArrayList() + << commonFiles(generateAab))) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") - << (QList() << (QByteArrayList() << commonFiles - << "resources.arsc")) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + << (QList() << (QByteArrayList() + << commonFiles(generateAab) + << "resources.arsc")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") + << (QList() << (QByteArrayList() + << commonFiles(generateAab) + << "base/resources.pb")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; QTest::newRow("multiple libs") << "multiple-libs-per-apk" << QStringList("twolibs") - << (QList() << commonFiles + expandArchs(archs, { + << (QList() << commonFiles(generateAab) + expandArchs(archs, { "resources.arsc", "lib/${ARCH}/liblib1.so", "lib/${ARCH}/liblib2.so", - cxxLibPath("libstlport_shared.so", false)})) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - QTest::newRow("multiple libs") + QTest::newRow("multiple libs aapt2") << "multiple-libs-per-apk" << QStringList("twolibs") - << (QList() << commonFiles + expandArchs(archs, { + << (QList() << commonFiles(generateAab) + expandArchs(archs, { "resources.arsc", "lib/${ARCH}/liblib1.so", "lib/${ARCH}/liblib2.so", - cxxLibPath("libstlport_shared.so", false)})) - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; - QByteArrayList expectedFiles1 = qbs::toList(qbs::toSet(commonFiles - + expandArchs(QByteArrayList{"armeabi-v7a", "x86"}, { - "resources.arsc", - "lib/${ARCH}/libp1lib1.so", - cxxLibPath("libstlport_shared.so", false)}) - + expandArchs(QByteArrayList{archs}, { - "resources.arsc", - "lib/${ARCH}/libp1lib2.so", - cxxLibPath("libstlport_shared.so", false)}))); - QByteArrayList expectedFiles2 = commonFiles + expandArchs(archs, { - "lib/${ARCH}/libp2lib1.so", - "lib/${ARCH}/libp2lib2.so", - cxxLibPath("libstlport_shared.so", false)}); + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("multiple libs aab") + << "multiple-libs-per-apk" + << QStringList("twolibs") + << (QList() << commonFiles(generateAab) + expandArchs(archs, { + "resources.pb", "native.pb", + "lib/${ARCH}/liblib1.so", + "lib/${ARCH}/liblib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = false; + generateAab = false; + auto expectedFiles1 = [&](bool generateAab) { + QByteArrayList expectedFile = qbs::toList(qbs::toSet(commonFiles(generateAab) + + expandArchs(QByteArrayList{"armeabi-v7a", "x86"}, { + "lib/${ARCH}/libp1lib1.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab) + + expandArchs(QByteArrayList{archs}, { + "lib/${ARCH}/libp1lib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab))); + if (generateAab) + expectedFile << "base/resources.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + auto expectedFiles2 = [&](bool generateAab) { + QByteArrayList expectedFile = commonFiles(generateAab) + expandArchs(archs, { + "lib/${ARCH}/libp2lib1.so", + "lib/${ARCH}/libp2lib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab); + return expectedFile; + }; + QTest::newRow("multiple apks") << "multiple-apks-per-project" << (QStringList() << "twolibs1" << "twolibs2") - << QList{expectedFiles1, expectedFiles2} - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + << QList{expectedFiles1(generateAab), expectedFiles2(generateAab)} + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; enableAapt2 = true; - expectedFiles2 << "resources.arsc"; - QTest::newRow("multiple apks") + QTest::newRow("multiple apks aapt2") + << "multiple-apks-per-project" + << (QStringList() << "twolibs1" << "twolibs2") + << (QList() << expectedFiles1(generateAab) + << (QByteArrayList() << expectedFiles2(generateAab) << "resources.arsc")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("multiple apks aab") << "multiple-apks-per-project" << (QStringList() << "twolibs1" << "twolibs2") - << QList{expectedFiles1, expectedFiles2} - << QStringList(aaptVersion(enableAapt2)) << enableAapt2; + << (QList() << expectedFiles1(generateAab) + << (QByteArrayList() << expectedFiles2(generateAab) << "base/resources.pb" + << "base/native.pb")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; } QTEST_MAIN(TestBlackboxAndroid) -- cgit v1.2.3