diff options
-rw-r--r-- | doc/config/macros.qdocconf | 1 | ||||
-rw-r--r-- | doc/reference/modules/codesign-module.qdoc | 65 | ||||
-rwxr-xr-x | scripts/test-qt-for-android.sh | 1 | ||||
-rw-r--r-- | share/qbs/modules/Android/sdk/sdk.qbs | 61 | ||||
-rw-r--r-- | share/qbs/modules/Android/sdk/utils.js | 64 | ||||
-rw-r--r-- | share/qbs/modules/codesign/android.qbs | 116 | ||||
-rw-r--r-- | share/qbs/modules/codesign/codesign.js | 72 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata-android/qt-app/test.keystore | bin | 0 -> 1284 bytes | |||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxandroid.cpp | 108 |
9 files changed, 370 insertions, 118 deletions
diff --git a/doc/config/macros.qdocconf b/doc/config/macros.qdocconf index a8abe25e5..6811215c0 100644 --- a/doc/config/macros.qdocconf +++ b/doc/config/macros.qdocconf @@ -4,6 +4,7 @@ macro.qbsversion = $QBS_VERSION macro.defaultvalue = "Default:" macro.nodefaultvalue = "Default: Undefined" macro.appleproperty = "This property is specific to Apple platforms." +macro.androidproperty = "This property is specific to Android platforms." macro.unixproperty = "This property is specific to Unix platforms." macro.windowsproperty = "This property is specific to Windows." macro.baremetalproperty = "This property is specific to bare-metal platforms." diff --git a/doc/reference/modules/codesign-module.qdoc b/doc/reference/modules/codesign-module.qdoc index ab95798a7..70a37477b 100644 --- a/doc/reference/modules/codesign-module.qdoc +++ b/doc/reference/modules/codesign-module.qdoc @@ -33,7 +33,8 @@ \brief Provides code signing support. - The \c codesign module contains properties and rules for code signing on Apple platforms. + The \c codesign module contains properties and rules for code signing on Apple and Android + platforms. \section2 Relevant File Tags @@ -244,3 +245,65 @@ \appleproperty */ + +/*! + \qmlproperty bool codesign::useApksigner + + If \c true, the package is signed using apksignerFilePath binary. + Set this property to \c false to use the jarsignerFilePath one. + Set by the Android.sdk module. + + \since Qbs 1.19 + + \defaultvalue \c true + + \androidproperty +*/ + +/*! + \qmlproperty string codesign::keystorePath + + The absolute path to the keystore file. + + \since Qbs 1.19 + + \defaultvalue \c "${HOME}/.android/debug.keystore" + + \androidproperty +*/ + +/*! + \qmlproperty string codesign::keystorePassword + + The keystore password. + + \since Qbs 1.19 + + \defaultvalue \c "android" + + \androidproperty +*/ + +/*! + \qmlproperty string codesign::keyPassword + + The key password. + + \since Qbs 1.19 + + \defaultvalue \c "android" + + \androidproperty +*/ + +/*! + \qmlproperty string codesign::keyAlias + + The key alias. + + \since Qbs 1.19 + + \defaultvalue \c "androiddebugkey" + + \androidproperty +*/ diff --git a/scripts/test-qt-for-android.sh b/scripts/test-qt-for-android.sh index 4d476624b..55ef991d4 100755 --- a/scripts/test-qt-for-android.sh +++ b/scripts/test-qt-for-android.sh @@ -62,6 +62,7 @@ qbs setup-android --ndk-dir ${ANDROID_HOME}/ndk-bundle --sdk-dir ${ANDROID_HOME} export QBS_AUTOTEST_PROFILE=qbs_autotests-android export QBS_AUTOTEST_ALWAYS_LOG_STDERR=true +export QTEST_FUNCTION_TIMEOUT=9000000 if [ ! "${QT_VERSION}" \< "5.14.0" ] && [ "${QT_VERSION}" \< "6.0.0" ]; then echo "Using multi-arch data sets for qml tests (only for qt version >= 5.14 and < 6.0.0) with all architectures" diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs index 0e40d059b..cae5fc2e5 100644 --- a/share/qbs/modules/Android/sdk/sdk.qbs +++ b/share/qbs/modules/Android/sdk/sdk.qbs @@ -178,20 +178,18 @@ Module { property path compiledResourcesDir: FileInfo.joinPaths(product.buildDirectory, "compiled_resources") property string packageContentsDir: FileInfo.joinPaths(product.buildDirectory, packageType) - property string debugKeyStorePath: FileInfo.joinPaths( - Environment.getEnv(qbs.hostOS.contains("windows") - ? "USERPROFILE" : "HOME"), - ".android", "debug.keystore") - property bool useApksigner: buildToolsVersion && Utilities.versionCompare( - buildToolsVersion, "24.0.3") >= 0 property stringList aidlSearchPaths Depends { name: "java"; condition: _enableRules } + Depends { name: "codesign"; condition: _enableRules } Properties { condition: _enableRules java.languageVersion: platformJavaVersion java.runtimeVersion: platformJavaVersion java.bootClassPaths: androidJarFilePath + codesign.apksignerFilePath: Android.sdk.apksignerFilePath + codesign._packageName: Android.sdk.apkBaseName + (_generateAab ? ".aab" : ".apk") + codesign.useApksigner: !_generateAab } validate: { @@ -211,6 +209,11 @@ Module { + "Android bundletool can be downloaded from " + "https://github.com/google/bundletool"); } + if (Utilities.versionCompare(buildToolsVersion, "24.0.3") < 0) { + throw ModUtils.ModuleError("Version of Android SDK build tools too old. This version " + + "is " + buildToolsVersion + " and minimum version is " + + "24.0.3. Please update the Android SDK.") + } } FileTagger { @@ -223,36 +226,6 @@ Module { fileTags: ["android.aidl"] } - FileTagger { - patterns: ["*.keystore"] - fileTags: ["android.keystore"] - } - - // Typically there is a debug keystore in ~/.android/debug.keystore which gets created - // by the native build tools the first time a build is done. However, we don't want to create it - // ourselves, because writing to a location outside the qbs build directory is both polluting - // and has the potential for race conditions. So we'll instruct the user what to do. - Group { - name: "Android debug keystore" - files: { - if (!File.exists(Android.sdk.debugKeyStorePath)) { - throw ModUtils.ModuleError("Could not find an Android debug keystore at " + - Android.sdk.debugKeyStorePath + ". " + - "If you are developing for Android on this machine for the first time and " + - "have never built an application using the native Gradle / Android Studio " + - "tooling, this is normal. You must create the debug keystore now using the " + - "following command, in order to continue:\n\n" + - SdkUtils.createDebugKeyStoreCommandString(java.keytoolFilePath, - Android.sdk.debugKeyStorePath) + - "\n\n" + - "See the following URL for more information: " + - "https://developer.android.com/studio/publish/app-signing.html#debug-mode"); - } - return [Android.sdk.debugKeyStorePath]; - } - fileTags: ["android.keystore"] - } - Parameter { property bool embedJar: true } @@ -525,11 +498,11 @@ Module { inputs: [ "android.resources", "android.assets", "android.manifest_final", "android.dex", "android.stl_deployed", - "android.nativelibrary_deployed", "android.keystore" + "android.nativelibrary_deployed" ] Artifact { - filePath: product.Android.sdk.apkBaseName + ".apk" - fileTags: "android.package" + filePath: product.Android.sdk.apkBaseName + ".apk_unsigned" + fileTags: "android.package_unsigned" } prepare: SdkUtils.prepareAaptPackage.apply(SdkUtils, arguments) } @@ -540,11 +513,11 @@ Module { inputs: [ "android.apk_resources", "android.manifest_final", "android.dex", "android.stl_deployed", - "android.nativelibrary_deployed", "android.keystore" + "android.nativelibrary_deployed" ] Artifact { - filePath: product.Android.sdk.apkBaseName + ".apk" - fileTags: "android.package" + filePath: product.Android.sdk.apkBaseName + ".apk_unsigned" + fileTags: "android.package_unsigned" } prepare: SdkUtils.prepareApkPackage.apply(SdkUtils, arguments) } @@ -558,8 +531,8 @@ Module { "android.nativelibrary_deployed" ] Artifact { - filePath: product.Android.sdk.apkBaseName + ".aab" - fileTags: "android.package" + filePath: product.Android.sdk.apkBaseName + ".aab_unsigned" + fileTags: "android.package_unsigned" } 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 6fa200822..9511ae9de 100644 --- a/share/qbs/modules/Android/sdk/utils.js +++ b/share/qbs/modules/Android/sdk/utils.js @@ -242,7 +242,7 @@ function prepareAaptGenerate(project, product, inputs, outputs, input, output, function prepareAaptPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { var cmds = []; - var apkOutput = outputs["android.package"][0]; + var apkOutput = outputs["android.package_unsigned"][0]; var args = commonAaptPackageArgs.apply(this, arguments); args.push("-F", apkOutput.filePath + ".unaligned"); args.push(product.Android.sdk.packageContentsDir); @@ -250,17 +250,6 @@ function prepareAaptPackage(project, product, inputs, outputs, input, output, ex cmd.description = "generating " + apkOutput.fileName; cmds.push(cmd); - if (!product.Android.sdk.useApksigner) { - args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1", - "-keystore", inputs["android.keystore"][0].filePath, - "-storepass", "android", - apkOutput.filePath + ".unaligned", - "androiddebugkey"]; - cmd = new Command(product.java.jarsignerFilePath, args); - cmd.description = "signing " + apkOutput.fileName; - cmds.push(cmd); - } - cmd = new Command(product.Android.sdk.zipalignFilePath, ["-f", "4", apkOutput.filePath + ".unaligned", apkOutput.filePath]); cmd.silent = true; @@ -271,26 +260,14 @@ function prepareAaptPackage(project, product, inputs, outputs, input, output, ex cmd.unalignedApk = apkOutput.filePath + ".unaligned"; cmd.sourceCode = function() { File.remove(unalignedApk); }; cmds.push(cmd); - - if (product.Android.sdk.useApksigner) { - // TODO: Implement full signing support, not just using the debug keystore - args = ["sign", - "--ks", inputs["android.keystore"][0].filePath, - "--ks-pass", "pass:android", - apkOutput.filePath]; - cmd = new Command(product.Android.sdk.apksignerFilePath, args); - cmd.description = "signing " + apkOutput.fileName; - cmds.push(cmd); - } - return cmds; } function prepareApkPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { var cmds = []; var apkInputFilePath = inputs["android.apk_resources"][0].filePath; - var apkOutput = outputs["android.package"][0]; - var apkOutputFilePathUnaligned = outputs["android.package"][0].filePath + ".unaligned"; + var apkOutput = outputs["android.package_unsigned"][0]; + var apkOutputFilePathUnaligned = apkOutput.filePath + ".unaligned"; var dexFilePath = inputs["android.dex"][0].filePath; var copyCmd = new JavaScriptCommand(); @@ -309,17 +286,6 @@ function prepareApkPackage(project, product, inputs, outputs, input, output, exp jarCmd.workingDirectory = libPath; cmds.push(jarCmd); - if (!product.Android.sdk.useApksigner) { - args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1", - "-keystore", inputs["android.keystore"][0].filePath, - "-storepass", "android", - apkOutputFilePathUnaligned, - "androiddebugkey"]; - cmd = new Command(product.java.jarsignerFilePath, args); - cmd.description = "signing " + apkOutput.fileName; - cmds.push(cmd); - } - cmd = new Command(product.Android.sdk.zipalignFilePath, ["-f", "4", apkOutputFilePathUnaligned, apkOutput.filePath]); cmd.silent = true; @@ -330,18 +296,6 @@ function prepareApkPackage(project, product, inputs, outputs, input, output, exp cmd.unalignedApk = apkOutputFilePathUnaligned; cmd.sourceCode = function() { File.remove(unalignedApk); }; cmds.push(cmd); - - if (product.Android.sdk.useApksigner) { - // TODO: Implement full signing support, not just using the debug keystore - args = ["sign", - "--ks", inputs["android.keystore"][0].filePath, - "--ks-pass", "pass:android", - apkOutput.filePath]; - cmd = new Command(product.Android.sdk.apksignerFilePath, args); - cmd.description = "signing " + apkOutput.fileName; - cmds.push(cmd); - } - return cmds; } @@ -377,7 +331,7 @@ function prepareBundletoolPackage(project, product, inputs, outputs, input, outp jarBaseCmd.workingDirectory = baseModuleDir; cmds.push(jarBaseCmd); - var aabFilePath = outputs["android.package"][0].filePath; + var aabFilePath = outputs["android.package_unsigned"][0].filePath; var removeCmd = new JavaScriptCommand(); removeCmd.description = "removing previous aab"; removeCmd.filePath = aabFilePath; @@ -391,20 +345,12 @@ function prepareBundletoolPackage(project, product, inputs, outputs, input, outp args.push("--modules=" + baseFilePath); args.push("--output=" + aabFilePath); var cmd = new Command(product.java.interpreterFilePath, args); - cmd.description = "generating " + outputs["android.package"][0].fileName; + cmd.description = "generating " + aabFilePath.fileName; cmds.push(cmd); return cmds; } -function createDebugKeyStoreCommandString(keytoolFilePath, keystoreFilePath) { - var args = ["-genkey", "-keystore", keystoreFilePath, "-alias", "androiddebugkey", - "-storepass", "android", "-keypass", "android", "-keyalg", "RSA", - "-keysize", "2048", "-validity", "10000", "-dname", - "CN=Android Debug,O=Android,C=US"]; - return Process.shellQuote(keytoolFilePath, args); -} - function stlDeploymentData(product, inputs, type) { var data = { uniqueInputs: [], outputFilePaths: []}; diff --git a/share/qbs/modules/codesign/android.qbs b/share/qbs/modules/codesign/android.qbs new file mode 100644 index 000000000..be96d42de --- /dev/null +++ b/share/qbs/modules/codesign/android.qbs @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Raphaƫl Cotty <raphael.cotty@gmail.com> +** Contact: http://www.qt.io/licensing +** +** This file is part of the Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +import qbs +import qbs.Environment +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import "codesign.js" as CodeSign + +CodeSignModule { + condition: qbs.targetOS.contains("android") + priority: 1 + enableCodeSigning: true + + property bool useApksigner: true + property path apksignerFilePath + + Probes.JdkProbe { + id: jdk + environmentPaths: (jdkPath ? [jdkPath] : []).concat(base) + } + property string jdkPath: jdk.path + property string jarsignerFilePath: FileInfo.joinPaths(jdkPath, "bin", jarsignerName) + property string jarsignerName: "jarsigner" + property string keytoolFilePath: FileInfo.joinPaths(jdkPath, "bin", keytoolName) + property string keytoolName: "keytool" + + property string debugKeystorePath: FileInfo.joinPaths( + Environment.getEnv(qbs.hostOS.contains("windows") + ? "USERPROFILE" : "HOME"), + ".android", "debug.keystore") + readonly property string debugKeystorePassword: "android" + readonly property string debugPassword: "android" + readonly property string debugKeyAlias: "androiddebugkey" + + property string keystorePath: debugKeystorePath + property string keystorePassword: debugKeystorePassword + property string keyPassword: debugPassword + property string keyAlias: debugKeyAlias + + // Private property set by the Android.sdk module + property string _packageName + + Rule { + condition: useApksigner + inputs: ["android.package_unsigned"] + Artifact { + filePath: product.codesign._packageName + fileTags: "android.package" + } + prepare: CodeSign.signApkPackage.apply(this, arguments) + } + + Rule { + condition: !useApksigner + inputs: ["android.package_unsigned"] + Artifact { + filePath: product.codesign._packageName + fileTags: "android.package" + } + prepare: CodeSign.signAabPackage.apply(this, arguments) + } + + validate: { + // Typically there is a debug keystore in ~/.android/debug.keystore which gets created + // by the native build tools the first time a build is done. However, we don't want to + // create it ourselves, because writing to a location outside the qbs build directory is + // both polluting and has the potential for race conditions. So we'll instruct the user what + // to do. + if (keystorePath === debugKeystorePath && !File.exists(debugKeystorePath)) { + throw ModUtils.ModuleError("Could not find an Android debug keystore at " + + codesign.debugKeystorePath + ". " + + "If you are developing for Android on this machine for the first time and " + + "have never built an application using the native Gradle / Android Studio " + + "tooling, this is normal. You must create the debug keystore now using the " + + "following command, in order to continue:\n\n" + + CodeSign.createDebugKeyStoreCommandString(codesign.keytoolFilePath, + codesign.debugKeystorePath, + codesign.debugKeystorePassword, + codesign.debugPassword, + codesign.debugKeyAlias) + + "\n\n" + + "See the following URL for more information: " + + "https://developer.android.com/studio/publish/app-signing.html#debug-mode"); + } + } +} diff --git a/share/qbs/modules/codesign/codesign.js b/share/qbs/modules/codesign/codesign.js index 1d039e7ca..bf7e95224 100644 --- a/share/qbs/modules/codesign/codesign.js +++ b/share/qbs/modules/codesign/codesign.js @@ -32,6 +32,7 @@ var File = require("qbs.File"); var FileInfo = require("qbs.FileInfo"); var PathTools = require("qbs.PathTools"); +var Process = require("qbs.Process"); var PropertyList = require("qbs.PropertyList"); var Utilities = require("qbs.Utilities"); @@ -277,3 +278,74 @@ function prepareSign(project, product, inputs, outputs, input, output) { return cmds; } + +function signApkPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var apkInput = inputs["android.package_unsigned"][0]; + var apkOutput = outputs["android.package"][0]; + var cmd; + if (product.codesign.enableCodeSigning) { + var args = ["sign", + "--ks", product.codesign.keystorePath, + "--ks-pass", "pass:" + product.codesign.keystorePassword, + "--ks-key-alias", product.codesign.keyAlias, + "--key-pass", "pass:" + product.codesign.keyPassword, + "--out", apkOutput.filePath, + apkInput.filePath]; + cmd = new Command(product.codesign.apksignerFilePath, args); + cmd.description = "signing " + apkOutput.fileName; + } else { + cmd = new JavaScriptCommand(); + cmd.description = "copying without signing " + apkOutput.fileName; + cmd.source = apkInput.filePath; + cmd.target = apkOutput.filePath; + cmd.silent = true; + cmd.sourceCode = function() { + // If enableCodeSigning is changed to false without any change to unsigned package then + // the copy won't happen because of timestamps. So the target file needs file needs to + // be removed to avoid it. + File.remove(target); + File.copy(source, target); + } + } + return cmd; +} + +function signAabPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var aabInput = inputs["android.package_unsigned"][0]; + var aabOutput = outputs["android.package"][0]; + var cmd; + if (product.codesign.enableCodeSigning) { + args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1", + "-keystore", product.codesign.keystorePath, + "-storepass", product.codesign.keystorePassword, + "-keypass", product.codesign.keyPassword, + "-signedjar", aabOutput.filePath, + aabInput.filePath, + product.codesign.keyAlias]; + cmd = new Command(product.codesign.jarsignerFilePath, args); + cmd.description = "signing " + aabOutput.fileName; + } else { + cmd = new JavaScriptCommand(); + cmd.description = "copying without signing " + aabOutput.fileName; + cmd.source = aabInput.filePath; + cmd.target = aabOutput.filePath; + cmd.silent = true; + cmd.sourceCode = function() { + // If enableCodeSigning is changed to false without any change to unsigned package then + // the copy won't happen because of timestamps. So the target file needs file needs to + // be removed to avoid it. + File.remove(target); + File.copy(source, target); + } + } + return cmd; +} + +function createDebugKeyStoreCommandString(keytoolFilePath, keystoreFilePath, keystorePassword, + keyPassword, keyAlias) { + var args = ["-genkey", "-keystore", keystoreFilePath, "-alias", keyAlias, + "-storepass", keystorePassword, "-keypass", keyPassword, "-keyalg", "RSA", + "-keysize", "2048", "-validity", "10000", "-dname", + "CN=Android Debug,O=Android,C=US"]; + return Process.shellQuote(keytoolFilePath, args); +} diff --git a/tests/auto/blackbox/testdata-android/qt-app/test.keystore b/tests/auto/blackbox/testdata-android/qt-app/test.keystore Binary files differnew file mode 100644 index 000000000..5713d10d2 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qt-app/test.keystore diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp index a03c9e318..f8ed8a0b9 100644 --- a/tests/auto/blackbox/tst_blackboxandroid.cpp +++ b/tests/auto/blackbox/tst_blackboxandroid.cpp @@ -268,14 +268,19 @@ void TestBlackboxAndroid::android_data() return result; }; - auto commonFiles = [](bool generateAab) { + auto commonFiles = [](bool generateAab, bool codeSign = true, + QString keyAlias="androiddebugkey") { + QByteArrayList files; 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"); + files << "base/manifest/AndroidManifest.xml" << "base/dex/classes.dex" + << "BundleConfig.pb"; + else + files << "AndroidManifest.xml" << "classes.dex"; + if (codeSign) + files << QByteArray("META-INF/" + keyAlias.toUpper().left(8).toUtf8() + ".RSA") + << QByteArray("META-INF/" + keyAlias.toUpper().left(8).toUtf8() + ".SF") + << "META-INF/MANIFEST.MF"; + return files; }; QTest::addColumn<QString>("projectDir"); @@ -296,10 +301,11 @@ void TestBlackboxAndroid::android_data() bool generateAab = false; bool isIncrementalBuild = false; - auto qtAppExpectedFiles = [&](bool generateAab, bool enableAapt2) { + auto qtAppExpectedFiles = [&](bool generateAab, bool enableAapt2, bool codeSign = true, + QString keyAlias="androiddebugkey") { QByteArrayList expectedFile; if (singleArchQt) { - expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + expectedFile << commonFiles(generateAab, codeSign, keyAlias) + expandArchs(ndkArchsForQt, { cxxLibPath("libgnustl_shared.so", true), "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", "lib/${ARCH}/libplugins_imageformats_libqgif.so", @@ -317,7 +323,7 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libQt5Widgets.so", "lib/${ARCH}/libqt-app.so"}, generateAab); } else { - expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + expectedFile << commonFiles(generateAab, codeSign, keyAlias) + expandArchs(ndkArchsForQt, { cxxLibPath("libgnustl_shared.so", true), "lib/${ARCH}/libplugins_imageformats_qgif_${ARCH}.so", "lib/${ARCH}/libplugins_imageformats_qico_${ARCH}.so", @@ -329,7 +335,7 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libqt-app_${ARCH}.so"}, generateAab); } if (generateAab) - expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + expectedFile << "base/resources.pb" << "base/native.pb"; else expectedFile << "resources.arsc"; if (version >= qbs::Version(5, 14)) @@ -346,11 +352,82 @@ void TestBlackboxAndroid::android_data() expectedFile << "res/layout/splash.xml"; return expectedFile; }; + auto codeSignProperties = [&](bool codeSign, QString keyStorePath, QString keystorePassword, + QString keyPassword, QString keyAlias) { + if (!codeSign) + return QStringList{"modules.codesign.enableCodeSigning:false"}; + return QStringList{ + "modules.codesign.enableCodeSigning:true", + "modules.codesign.keystorePath:" + keyStorePath, + "modules.codesign.keystorePassword:" + keystorePassword, + "modules.codesign.keyPassword:" + keyPassword, + "modules.codesign.keyAlias:" + keyAlias, + }; + }; + bool codeSign = true; + QString keyStorePath(testDataDir + "/qt-app/test.keystore"); + QString keystorePassword("qbsKeystoreTest"); + QString keyPassword("qbsKeyTest"); + QString keyAlias("qbsTest"); QTest::newRow("qt app") << "qt-app" << QStringList("qt-app") << (QList<QByteArrayList>() << (QByteArrayList() << qtAppExpectedFiles(generateAab, - enableAapt2))) - << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + enableAapt2, + codeSign, + keyAlias))) + << (QStringList() << codeSignProperties(codeSign, keyStorePath, keystorePassword, + keyPassword, keyAlias) + << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + codeSign = false; + QTest::newRow("qt app no signing") + << "qt-app" << QStringList("qt-app") + << (QList<QByteArrayList>() << (QByteArrayList() << qtAppExpectedFiles(generateAab, + enableAapt2, + codeSign, + keyAlias))) + << (QStringList() << codeSignProperties(codeSign, keyStorePath, keystorePassword, + keyPassword, keyAlias) + << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + codeSign = true; + QTest::newRow("qt app aapt2") + << "qt-app" << QStringList("qt-app") + << (QList<QByteArrayList>() << (QByteArrayList() << qtAppExpectedFiles(generateAab, + enableAapt2, + codeSign, + keyAlias))) + << (QStringList() << codeSignProperties(codeSign, keyStorePath, keystorePassword, + keyPassword, keyAlias) + << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qt app aab") + << "qt-app" << QStringList("qt-app") + << (QList<QByteArrayList>() << (QByteArrayList() << qtAppExpectedFiles(generateAab, + enableAapt2, + codeSign, + keyAlias))) + << (QStringList() << codeSignProperties(codeSign, keyStorePath, keystorePassword, + keyPassword, keyAlias) + << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + codeSign = false; + QTest::newRow("qt app aab no signing") + << "qt-app" << QStringList("qt-app") + << (QList<QByteArrayList>() << (QByteArrayList() << qtAppExpectedFiles(generateAab, + enableAapt2, + codeSign, + keyAlias))) + << (QStringList() << codeSignProperties(codeSign, keyStorePath, keystorePassword, + keyPassword, keyAlias) + << aaptVersion(enableAapt2) + << packageType(generateAab)) << enableAapt2 << generateAab << isIncrementalBuild; const QByteArrayList ndkArchsForQtSave = ndkArchsForQt; @@ -383,6 +460,8 @@ void TestBlackboxAndroid::android_data() return expectedFile; }; + generateAab = false; + enableAapt2 = false; QTest::newRow("teapot") << "teapot" << QStringList("TeapotNativeActivity") << (QList<QByteArrayList>() << teaPotAppExpectedFiles(archs, generateAab)) @@ -437,6 +516,7 @@ void TestBlackboxAndroid::android_data() "modules.qbs.architecture:" + archsStringList.first(), aaptVersion(enableAapt2), packageType(generateAab)} << enableAapt2 << generateAab << isIncrementalBuild; + auto qmlAppExpectedFiles = [&](bool generateAab, bool enableAapt2) { QByteArrayList expectedFile; if (singleArchQt) { @@ -730,7 +810,6 @@ void TestBlackboxAndroid::android_data() } 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 @@ -851,6 +930,7 @@ void TestBlackboxAndroid::android_data() expectedFile << "resources.arsc"; return expectedFile; }; + QTest::newRow("no native") << "no-native" << QStringList("com.example.android.basicmediadecoder") |