diff options
author | Raphaël Cotty <raphael.cotty@gmail.com> | 2020-01-20 15:22:11 +0100 |
---|---|---|
committer | Raphaël Cotty <raphael.cotty@gmail.com> | 2020-02-04 12:40:58 +0000 |
commit | e130e492ebe6ad266a74df7b7d5efdef55dc0ff7 (patch) | |
tree | d46ae28bb6471b9dca6cccff316ac6842742ff6a | |
parent | 7811e70970294ff4c4c1fd76221727696e479cfb (diff) |
Android: Update support to qt 5.14 and multi-arch apks for Qt apps
Before qt 5.14.
The profile property "moduleProviders.Qt.qmakeFilePaths" is set with one or more
paths to the different android architectures. Each qmake path belongs to a
different android architecture installation.
So each qmake is used to generated the Qt module corresponding to the architecture.
Although qbs can generate multi-arch apks using multiplex mode, this is not
possible when the project depends on qt libraries. This is because of the
restriction of the qt tool androiddeployqt used by qbs.
Now with qt 5.14.
All android architectures are installed in the same directory.
So the profile property "moduleProviders.Qt.qmakeFilePaths" is set with one
qmake path.
This directly impacts the qbs-setup-android tool and the generation of
the Qt modules.
Because qt libraries are installed in the the same directory, they have the
abi in their name (libQt5Core_armeabi-v7a.so).
So the rules that generate the apks are also impacted.
The new androiddeployqt have a new interface (json config file format and requires
to have the input libraries installed in the deployment directory) which allows
the generation of multi-arch apks.
So Qt.android_support modules needs to be updated as well.
Fixes: QBS-1497
Change-Id: Ibd546f356c38a05f42dfcac0a4ec92bd82d6f700
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
-rw-r--r-- | .travis.yml | 4 | ||||
-rwxr-xr-x | scripts/test-qt-for-android.sh | 23 | ||||
-rw-r--r-- | share/qbs/imports/qbs/base/QtApplication.qbs | 4 | ||||
-rw-r--r-- | share/qbs/imports/qbs/base/QtGuiApplication.qbs | 2 | ||||
-rw-r--r-- | share/qbs/module-providers/Qt/setup-qt.js | 225 | ||||
-rw-r--r-- | share/qbs/module-providers/Qt/templates/android_support.qbs | 150 | ||||
-rw-r--r-- | share/qbs/modules/Android/sdk/sdk.qbs | 29 | ||||
-rw-r--r-- | share/qbs/modules/Android/sdk/utils.js | 7 | ||||
-rw-r--r-- | share/qbs/modules/cpp/android-gcc.qbs | 2 | ||||
-rw-r--r-- | src/app/qbs-setup-android/android-setup.cpp | 37 | ||||
-rw-r--r-- | tests/auto/blackbox/find/find-android.qbs | 49 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxandroid.cpp | 253 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxbase.cpp | 18 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxbase.h | 1 |
14 files changed, 610 insertions, 194 deletions
diff --git a/.travis.yml b/.travis.yml index d941d4cff..1853fbe0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,13 +60,15 @@ jobs: - docker-compose run --rm bionic scripts/run-analyzer.sh - <<: *build-on-bionic - name: With Qbs and with Qt for Android (Qt 5.13) + name: With Qbs and with Qt 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 + - docker-compose run bionic-android-514 scripts/test-qt-for-android.sh release/install-root/usr/local/bin - &build-on-macos stage: Build Qbs and and run autotests diff --git a/scripts/test-qt-for-android.sh b/scripts/test-qt-for-android.sh index 877afbc30..44a64db21 100755 --- a/scripts/test-qt-for-android.sh +++ b/scripts/test-qt-for-android.sh @@ -50,16 +50,33 @@ echo "Android SDK installed at ${ANDROID_SDK_ROOT}" echo "Android NDK installed at ${ANDROID_NDK_ROOT}" echo "Qt installed at ${QT_INSTALL_DIR}" +# Cleaning profiles +qbs config --unset profiles.qbs_autotests-android +qbs config --unset profiles.qbs_autotests-android-qt + +# Setting auto test profiles qbs setup-android --ndk-dir ${ANDROID_HOME}/ndk-bundle --sdk-dir ${ANDROID_HOME} qbs_autotests-android qbs setup-android --ndk-dir ${ANDROID_HOME}/ndk-bundle --sdk-dir ${ANDROID_HOME} --qt-dir ${QT_INSTALL_DIR} qbs_autotests-android-qt +export QBS_AUTOTEST_PROFILE=qbs_autotests-android +export QBS_AUTOTEST_ALWAYS_LOG_STDERR=true + +if [ ! "${QT_VERSION}" \< "5.14.0" ]; then + echo "Using multi-arch data sets for qml tests (only for qt version >= 5.14) with all architectures" + qbs config --list + tst_blackbox-android + + echo "Using multi-arch data sets for qml tests (only for qt version >= 5.14) with only armv7a and x86_64" + qbs config profiles.qbs_autotests-android-qt.qbs.architectures '["armv7a","x86_64"]' + qbs config --list + tst_blackbox-android +fi; + +echo "Using single-arch (armv7a) data sets for qml tests" qbs config --unset profiles.qbs_autotests-android-qt.qbs.architectures qbs config profiles.qbs_autotests-android-qt.qbs.architecture armv7a qbs config --list -export QBS_AUTOTEST_PROFILE=qbs_autotests-android -export QBS_AUTOTEST_ALWAYS_LOG_STDERR=true - tst_blackbox-android diff --git a/share/qbs/imports/qbs/base/QtApplication.qbs b/share/qbs/imports/qbs/base/QtApplication.qbs index 32800d294..b6776dca0 100644 --- a/share/qbs/imports/qbs/base/QtApplication.qbs +++ b/share/qbs/imports/qbs/base/QtApplication.qbs @@ -30,4 +30,8 @@ CppApplication { Depends { name: "Qt.core" } + Properties { + condition: isForAndroid && Qt.android_support._multiAbi + targetName: name + "_" + Android.ndk.abi + } } diff --git a/share/qbs/imports/qbs/base/QtGuiApplication.qbs b/share/qbs/imports/qbs/base/QtGuiApplication.qbs index 61bc69752..7b2abf017 100644 --- a/share/qbs/imports/qbs/base/QtGuiApplication.qbs +++ b/share/qbs/imports/qbs/base/QtGuiApplication.qbs @@ -28,6 +28,6 @@ ** ****************************************************************************/ -CppApplication { +QtApplication { Depends { name: "Qt.gui" } } diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js index 3ddc214d3..6960b15d9 100644 --- a/share/qbs/module-providers/Qt/setup-qt.js +++ b/share/qbs/module-providers/Qt/setup-qt.js @@ -219,6 +219,19 @@ function fillEntryPointLibs(qtProps, debug) { return result; } +function abiToArchitecture(abi) { + switch (abi) { + case "armeabi-v7a": + return "armv7a"; + case "arm64-v8a": + return "arm64"; + case "x86": + case "x86_64": + default: + return abi; + } +} + function getQtProperties(qmakeFilePath, qbs) { var queryResult = queryQmake(qmakeFilePath); var qtProps = {}; @@ -297,6 +310,14 @@ function getQtProperties(qmakeFilePath, qbs) { if (!File.exists(qtProps.mkspecPath)) throw "mkspec '" + toNative(qtProps.mkspecPath) + "' does not exist"; + // Starting with qt 5.14, android sdk provides multi-abi + if (Utilities.versionCompare(qtProps.qtVersion, "5.14.0") >= 0 + && qtProps.mkspecPath.contains("android")) { + var qdeviceContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath, + "qdevice.pri")); + qtProps.androidAbis = configVariable(qdeviceContent, "DEFAULT_ANDROID_ABIS").split(' '); + } + // determine MSVC version if (isMsvcQt(qtProps)) { var msvcMajor = configVariable(qconfigContent, "QT_MSVC_MAJOR_VERSION"); @@ -591,7 +612,7 @@ function replaceQtLibNamesWithFilePath(modules, qtProps) { } } -function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles) { +function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles, androidAbi) { if (!modInfo.hasLibrary) return; // Can happen for Qt4 convenience modules, like "widgets". @@ -644,7 +665,15 @@ function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles) { && !modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5; if (isNonStaticQt4OnWindows) prlFilePath = prlFilePath.slice(0, prlFilePath.length - 1); // The prl file base name does *not* contain the version number... + if (androidAbi.length > 0 + && modInfo.name !== "QtBootstrap" + && modInfo.name !== "QtQmlDevTools") { + prlFilePath += "_"; + prlFilePath += androidAbi; + } + prlFilePath += ".prl"; + try { var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly); while (!prlFile.atEof()) { @@ -738,9 +767,9 @@ function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles) { modInfo.libFilePathRelease = libFilePath; } -function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles) { - doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles); - doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles); +function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles, androidAbi) { + doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles, androidAbi); + doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles, androidAbi); } function allQt4Modules(qtProps) { @@ -862,7 +891,7 @@ function allQt4Modules(qtProps) { module.mustExist = false; if (qtProps.staticBuild) module.isStaticLibrary = true; - setupLibraries(module, qtProps, nonExistingPrlFiles); + setupLibraries(module, qtProps, nonExistingPrlFiles, ""); } replaceQtLibNamesWithFilePath(modules, qtProps); @@ -1003,7 +1032,7 @@ function removeDuplicatedDependencyLibs(modules) { traverse(rootModules[i], []); } -function allQt5Modules(qtProps) { +function allQt5Modules(qtProps, androidAbi) { var nonExistingPrlFiles = []; var modules = []; var modulesDir = FileInfo.joinPaths(qtProps.mkspecBasePath, "modules"); @@ -1120,7 +1149,7 @@ function allQt5Modules(qtProps) { } } - setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles); + setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles, androidAbi); modules.push(moduleInfo); if (moduleInfo.qbsName === "testlib") @@ -1144,7 +1173,6 @@ function extractQbsArchs(module, qtProps) { var qbsArch = Utilities.canonicalArchitecture(qtProps.architecture); if (qbsArch === "arm" && qtProps.mkspecPath.contains("android")) qbsArch = "armv7a"; - // Qt4 has "QT_ARCH = windows" in qconfig.pri for both MSVC and mingw. if (qbsArch === "windows") return [] @@ -1286,9 +1314,14 @@ function minVersionJsString(minVersion) { return !minVersion ? "original" : toJSLiteral(minVersion); } -function replaceSpecialValues(content, module, qtProps) { +function replaceSpecialValues(content, module, qtProps, abi) { + var architectures = []; + if (abi.length > 0) + architectures.push(abiToArchitecture(abi)); + else + architectures = extractQbsArchs(module, qtProps); var dict = { - archs: toJSLiteral(extractQbsArchs(module, qtProps)), + archs: toJSLiteral(architectures), targetPlatform: toJSLiteral(qbsTargetPlatformFromQtMkspec(qtProps)), config: toJSLiteral(qtProps.configItems), qtConfig: toJSLiteral(qtProps.qtConfigItems), @@ -1399,8 +1432,8 @@ function replaceSpecialValues(content, module, qtProps) { return content; } -function copyTemplateFile(fileName, targetDirectory, qtProps, location, allFiles, module, pluginMap, - nonEssentialPlugins) +function copyTemplateFile(fileName, targetDirectory, qtProps, abi, location, allFiles, module, + pluginMap, nonEssentialPlugins) { if (!File.makePath(targetDirectory)) { throw "Cannot create directory '" + toNative(targetDirectory) + "'."; @@ -1409,12 +1442,13 @@ function copyTemplateFile(fileName, targetDirectory, qtProps, location, allFiles TextFile.ReadOnly); var newContent = sourceFile.readAll(); if (module) { - newContent = replaceSpecialValues(newContent, module, qtProps); + newContent = replaceSpecialValues(newContent, module, qtProps, abi); } else { newContent = newContent.replace("@allPluginsByType@", '(' + toJSLiteral(pluginMap) + ')'); newContent = newContent.replace("@nonEssentialPlugins@", toJSLiteral(nonEssentialPlugins)); + newContent = newContent.replace("@version@", toJSLiteral(qtProps.qtVersion)); } sourceFile.close(); var targetPath = FileInfo.joinPaths(targetDirectory, fileName); @@ -1428,74 +1462,102 @@ function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) { if (!File.exists(qmakeFilePath)) throw "The specified qmake file path '" + toNative(qmakeFilePath) + "' does not exist."; var qtProps = getQtProperties(qmakeFilePath, qbs); - var modules = qtProps.qtMajorVersion < 5 ? allQt4Modules(qtProps) : allQt5Modules(qtProps); - var pluginsByType = {}; - var nonEssentialPlugins = []; - for (var i = 0; i < modules.length; ++i) { - var m = modules[i]; - if (m.isPlugin) { - if (!pluginsByType[m.pluginData.type]) - pluginsByType[m.pluginData.type] = []; - pluginsByType[m.pluginData.type].push(m.name); - if (!m.pluginData.autoLoad) - nonEssentialPlugins.push(m.name); + var androidAbis = []; + if (qtProps.androidAbis !== undefined) + // Multiple androidAbis detected: Qt >= 5.14 + androidAbis = qtProps.androidAbis; + else + // Single abi detected: Qt < 5.14 + androidAbis.push(''); + if (androidAbis.length > 1) + console.info("Qt with multiple abi detected: '" + androidAbis + "'"); + + var relativeSearchPaths = []; + for (a = 0; a < androidAbis.length; ++a) { + if (androidAbis.length > 1) + console.info("Configuring abi '" + androidAbis[a] + "'..."); + var modules = qtProps.qtMajorVersion < 5 ? allQt4Modules(qtProps) : + allQt5Modules(qtProps,androidAbis[a]); + var pluginsByType = {}; + var nonEssentialPlugins = []; + for (var i = 0; i < modules.length; ++i) { + var m = modules[i]; + if (m.isPlugin) { + if (!pluginsByType[m.pluginData.type]) + pluginsByType[m.pluginData.type] = []; + pluginsByType[m.pluginData.type].push(m.name); + if (!m.pluginData.autoLoad) + nonEssentialPlugins.push(m.name); + } } - } - - var relativeSearchPath = uniquify ? Utilities.getHash(qmakeFilePath) : ""; - var qbsQtModuleBaseDir = FileInfo.joinPaths(outputBaseDir, relativeSearchPath, "modules", "Qt"); - if (File.exists(qbsQtModuleBaseDir)) - File.remove(qbsQtModuleBaseDir); - var allFiles = []; - copyTemplateFile("QtModule.qbs", qbsQtModuleBaseDir, qtProps, location, allFiles); - copyTemplateFile("QtPlugin.qbs", qbsQtModuleBaseDir, qtProps, location, allFiles); - copyTemplateFile("plugin_support.qbs", FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), - qtProps, location, allFiles, undefined, pluginsByType, nonEssentialPlugins); + var relativeSearchPath = uniquify ? Utilities.getHash(qmakeFilePath) : ""; + relativeSearchPath = FileInfo.joinPaths(relativeSearchPath, androidAbis[a]); + var qbsQtModuleBaseDir = FileInfo.joinPaths(outputBaseDir, relativeSearchPath, + "modules", "Qt"); + if (File.exists(qbsQtModuleBaseDir)) + File.remove(qbsQtModuleBaseDir); + + var allFiles = []; + copyTemplateFile("QtModule.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("QtPlugin.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("plugin_support.qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), qtProps, + androidAbis[a], location, allFiles, undefined, pluginsByType, + nonEssentialPlugins); - for (i = 0; i < modules.length; ++i) { - var module = modules[i]; - var qbsQtModuleDir = FileInfo.joinPaths(qbsQtModuleBaseDir, module.qbsName); - var moduleTemplateFileName; - if (module.qbsName === "core") { - moduleTemplateFileName = "core.qbs"; - copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, location, allFiles); - copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, location, allFiles); - } else if (module.qbsName === "gui") { - moduleTemplateFileName = "gui.qbs"; - } else if (module.qbsName === "scxml") { - moduleTemplateFileName = "scxml.qbs"; - } else if (module.qbsName === "dbus") { - moduleTemplateFileName = "dbus.qbs"; - copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, location, allFiles); - } else if (module.qbsName === "qml") { - moduleTemplateFileName = "qml.qbs"; - copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, location, allFiles); - var qmlcacheStr = "qmlcache"; - if (File.exists(FileInfo.joinPaths(qtProps.binaryPath, - "qmlcachegen" + exeSuffix(qbs)))) { - copyTemplateFile(qmlcacheStr + ".qbs", - FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps, - location, allFiles); + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + var qbsQtModuleDir = FileInfo.joinPaths(qbsQtModuleBaseDir, module.qbsName); + var moduleTemplateFileName; + if (module.qbsName === "core") { + moduleTemplateFileName = "core.qbs"; + copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.qbsName === "gui") { + moduleTemplateFileName = "gui.qbs"; + } else if (module.qbsName === "scxml") { + moduleTemplateFileName = "scxml.qbs"; + } else if (module.qbsName === "dbus") { + moduleTemplateFileName = "dbus.qbs"; + copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.qbsName === "qml") { + moduleTemplateFileName = "qml.qbs"; + copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + var qmlcacheStr = "qmlcache"; + if (File.exists(FileInfo.joinPaths(qtProps.binaryPath, + "qmlcachegen" + exeSuffix(qbs)))) { + copyTemplateFile(qmlcacheStr + ".qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps, + androidAbis[a], location, allFiles); + } + } else if (module.qbsName === "quick") { + moduleTemplateFileName = "quick.qbs"; + copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.isPlugin) { + moduleTemplateFileName = "plugin.qbs"; + } else { + moduleTemplateFileName = "module.qbs"; } - } else if (module.qbsName === "quick") { - moduleTemplateFileName = "quick.qbs"; - copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, location, allFiles); - } else if (module.isPlugin) { - moduleTemplateFileName = "plugin.qbs"; - } else { - moduleTemplateFileName = "module.qbs"; + copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, androidAbis[a], + location, allFiles, module); } - copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, location, allFiles, - module); - } - // Note that it's not strictly necessary to copy this one, as it has no variable content. - // But we do it anyway for consistency. - copyTemplateFile("android_support.qbs", - FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"), - qtProps, location, allFiles); - return relativeSearchPath; + // Note that it's not strictly necessary to copy this one, as it has no variable content. + // But we do it anyway for consistency. + copyTemplateFile("android_support.qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"), + qtProps, androidAbis[a], location, allFiles); + relativeSearchPaths.push(relativeSearchPath) + } + return relativeSearchPaths; } function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) { @@ -1503,19 +1565,20 @@ function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) { if (!qmakeFilePaths || qmakeFilePaths.length === 0) return []; var uniquifySearchPath = qmakeFilePaths.length > 1; - var searchPaths = []; + var allSearchPaths = []; for (var i = 0; i < qmakeFilePaths.length; ++i) { try { console.info("Setting up Qt at '" + toNative(qmakeFilePaths[i]) + "'..."); - var searchPath = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath, - location, qbs); - if (searchPath !== undefined) { - searchPaths.push(searchPath); + var searchPaths = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath, + location, qbs); + if (searchPaths.length > 0) { + for (var j = 0; j < searchPaths.length; ++j ) + allSearchPaths.push(searchPaths[j]); console.info("Qt was set up successfully."); } } catch (e) { console.warn("Error setting up Qt for '" + toNative(qmakeFilePaths[i]) + "': " + e); } } - return searchPaths; + return allSearchPaths; } diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs index c5f842a1f..342018321 100644 --- a/share/qbs/module-providers/Qt/templates/android_support.qbs +++ b/share/qbs/module-providers/Qt/templates/android_support.qbs @@ -3,8 +3,10 @@ import qbs.FileInfo import qbs.ModUtils import qbs.TextFile import qbs.Utilities +import qbs.Process Module { + version: @version@ property bool useMinistro: false property string qmlRootDir: product.sourceDirectory property stringList extraPrefixDirs @@ -22,6 +24,8 @@ Module { property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android") property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out") + property bool _multiAbi: Utilities.versionCompare(version, "5.14") >= 0 + Depends { name: "Android.sdk"; condition: _enableSdkSupport } Depends { name: "Android.ndk"; condition: _enableNdkSupport } Depends { name: "java"; condition: _enableSdkSupport } @@ -44,6 +48,11 @@ Module { condition: _enableNdkSupport && (Android.ndk.abi === "armeabi-v7a" || Android.ndk.abi === "x86") cpp.defines: "ANDROID_HAS_WSTRING" } + Properties { + condition: _enableSdkSupport + Android.sdk._archInName: _multiAbi + Android.sdk._bundledInAssets: _multiAbi + } Rule { condition: _enableSdkSupport @@ -61,30 +70,55 @@ Module { cmd.sourceCode = function() { var theBinary; var nativeLibs = inputs["android.nativelibrary"]; + var architectures = []; + var triples = []; + var hostArch; + var targetArchitecture; if (nativeLibs.length === 1) { theBinary = nativeLibs[0]; + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + if (product.Qt.android_support._multiAbi) { + architectures.push(theBinary.Android.ndk.abi); + triples.push(theBinary.cpp.toolchainTriple); + } } else { for (i = 0; i < nativeLibs.length; ++i) { var candidate = nativeLibs[i]; - if (!candidate.fileName.contains(candidate.product.targetName)) - continue; - if (!theBinary) { - theBinary = candidate; - continue; - } - if (theBinary.product.name === product.name - && candidate.product.name !== product.name) { - continue; // We already have a better match. - } - if (candidate.product.name === product.name - && theBinary.product.name !== product.name) { - theBinary = candidate; // The new candidate is a better match. - continue; + if (product.Qt.android_support._multiAbi) { + if (candidate.product.name === product.name) { + architectures.push(candidate.Android.ndk.abi); + triples.push(candidate.cpp.toolchainTriple); + hostArch = candidate.Android.ndk.hostArch; + targetArchitecture = candidate.Android.ndk.abi; + theBinary = candidate; + } + } else { + if (!candidate.fileName.contains(candidate.product.targetName)) + continue; + if (!theBinary) { + theBinary = candidate; + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + continue; + } + if (theBinary.product.name === product.name + && candidate.product.name !== product.name) { + continue; // We already have a better match. + } + if (candidate.product.name === product.name + && theBinary.product.name !== product.name) { + theBinary = candidate; // The new candidate is a better match. + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + continue; + } + + throw "Qt applications for Android support only one native binary " + + "per package.\n" + + "In particular, you cannot build a Qt app for more than " + + "one architecture at the same time."; } - throw "Qt applications for Android support only one native binary " - + "per package.\n" - + "In particular, you cannot build a Qt app for more than " - + "one architecture at the same time."; } } var f = new TextFile(output.filePath, TextFile.WriteOnly); @@ -99,8 +133,20 @@ Module { f.writeLine('"toolchain-prefix": "llvm",'); f.writeLine('"tool-prefix": "llvm",'); f.writeLine('"useLLVM": true,'); - f.writeLine('"ndk-host": "' + theBinary.Android.ndk.hostArch + '",'); - f.writeLine('"target-architecture": "' + theBinary.Android.ndk.abi + '",'); + f.writeLine('"ndk-host": "' + hostArch + '",'); + if (!product.Qt.android_support._multiAbi) { + f.writeLine('"target-architecture": "' + targetArchitecture + '",'); + } + else { + var line = '"architectures": {'; + for (var i in architectures) { + line = line + '"' + architectures[i] + '":"' + triples[i] + '"'; + if (i < architectures.length-1) + line = line + ','; + } + line = line + "},"; + f.writeLine(line); + } f.writeLine('"qml-root-path": "' + product.Qt.android_support.qmlRootDir + '",'); var deploymentDeps = product.Qt.android_support.deploymentDependencies; if (deploymentDeps && deploymentDeps.length > 0) @@ -124,11 +170,16 @@ Module { f.writeLine('"qml-import-paths": "' + product.qmlImportPaths.join(',') + '",'); // QBS-1429 - f.writeLine('"stdcpp-path": "' + (product.cpp.sharedStlFilePath + if (!product.Qt.android_support._multiAbi) { + f.writeLine('"stdcpp-path": "' + (product.cpp.sharedStlFilePath ? product.cpp.sharedStlFilePath : product.cpp.staticStlFilePath) + '",'); - - f.writeLine('"application-binary": "' + theBinary.filePath + '"'); + f.writeLine('"application-binary": "' + theBinary.filePath + '"'); + } else { + f.writeLine('"stdcpp-path": "' + product.Android.sdk.ndkDir + + '/toolchains/llvm/prebuilt/' + hostArch + '/sysroot/usr/lib/",'); + f.writeLine('"application-binary": "' + theBinary.product.name + '"'); + } f.writeLine("}"); f.close(); }; @@ -167,7 +218,12 @@ Module { Rule { condition: _enableSdkSupport multiplex: true - inputs: ["qt_androiddeployqt_input", "android.manifest_processed"] + property stringList defaultInputs: ["qt_androiddeployqt_input", + "android.manifest_processed"] + property stringList allInputs: ["qt_androiddeployqt_input", "android.manifest_processed", + "android.nativelibrary"] + inputsFromDependencies: "android.nativelibrary" + inputs: product.aggregate ? defaultInputs : allInputs outputFileTags: [ "android.manifest_final", "android.resources", "android.assets", "bundled_jar", "android.deployqt_list", @@ -216,9 +272,21 @@ Module { File.copy(product.Qt.android_support._templatesBaseDir + "/templates/res/values/libs.xml", product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml"); - try { - File.remove(FileInfo.path(outputs["android.assets"][0].filePath)); - } catch (e) { + if (!product.Qt.android_support._multiAbi) { + try { + File.remove(FileInfo.path(outputs["android.assets"][0].filePath)); + } catch (e) { + } + } + else { + for (var i in inputs["android.nativelibrary"]) { + var input = inputs["android.nativelibrary"][i]; + File.copy(input.filePath, + FileInfo.joinPaths(product.Qt.android_support._deployQtOutDir, + "libs", + input.Android.ndk.abi, + input.fileName)); + } } }; var androidDeployQtArgs = [ @@ -228,7 +296,7 @@ Module { "--android-platform", product.Android.sdk.platform, ]; if (product.Qt.android_support.verboseAndroidDeployQt) - args.push("--verbose"); + androidDeployQtArgs.push("--verbose"); var androidDeployQtCmd = new Command( product.Qt.android_support._androidDeployQtFilePath, androidDeployQtArgs); androidDeployQtCmd.description = "running androiddeployqt"; @@ -286,7 +354,31 @@ Module { File.remove(oldLibs[i]); } }; - return [copyCmd, androidDeployQtCmd, moveCmd]; + + // androiddeployqt doesn't strip the deployed libraries anymore so it has to done here + var stripLibsCmd = new JavaScriptCommand(); + stripLibsCmd.description = "Stripping unneeded symbols from deployed qt libraries"; + stripLibsCmd.sourceCode = function() { + var stripArgs = ["--strip-all"]; + var architectures = []; + 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, + "lib", architectures[i]); + var files = File.directoryEntries(abiDirPath, File.Files); + for (var i = 0; i < files.length; ++i) { + var filePath = FileInfo.joinPaths(abiDirPath, files[i]); + if (FileInfo.suffix(filePath) == "so") { + stripArgs.push(filePath); + } + } + } + var process = new Process(); + process.exec(product.cpp.stripPath, stripArgs, false); + } + + return [copyCmd, androidDeployQtCmd, moveCmd, stripLibsCmd]; } } diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs index f8a046c3a..ecf64a188 100644 --- a/share/qbs/modules/Android/sdk/sdk.qbs +++ b/share/qbs/modules/Android/sdk/sdk.qbs @@ -84,6 +84,9 @@ Module { property bool _enableRules: !product.multiplexConfigurationId && !!packageName + property bool _archInName: false + property bool _bundledInAssets: true + Group { name: "java sources" condition: Android.sdk.automaticSources @@ -277,6 +280,30 @@ Module { rootElem.setAttribute("android:versionCode", product.Android.sdk.versionCode); if (product.Android.sdk.versionName !== undefined) rootElem.setAttribute("android:versionName", product.Android.sdk.versionName); + + if (product.Android.sdk._bundledInAssets) { + // Remove <meta-data android:name="android.app.bundled_in_assets_resource_id" + // android:resource="@array/bundled_in_assets"/> + // custom AndroidManifest.xml because assets are in rcc files for qt >= 5.14 + var appElem = rootElem.firstChild("application"); + if (!appElem || !appElem.isElement() || appElem.tagName() != "application") + throw "No application tag found in '" + input.filePath + "'."; + var activityElem = appElem.firstChild("activity"); + if (!activityElem || !activityElem.isElement() || + activityElem.tagName() != "activity") + throw "No activity tag found in '" + input.filePath + "'."; + var metaDataElem = activityElem.firstChild("meta-data"); + while (metaDataElem && metaDataElem.isElement()) { + if (SdkUtils.elementHasBundledAttributes(metaDataElem)) { + var elemToRemove = metaDataElem; + metaDataElem = metaDataElem.nextSibling("meta-data"); + activityElem.removeChild(elemToRemove); + } else { + metaDataElem = metaDataElem.nextSibling("meta-data"); + } + } + } + manifestData.save(output.filePath, 4); } return cmd; @@ -415,7 +442,7 @@ Module { var deploymentData = SdkUtils.gdbserverOrStlDeploymentData(product, inputs); for (var i = 0; i < deploymentData.uniqueInputs.length; ++i) { var input = deploymentData.uniqueInputs[i]; - var stripArgs = ["--strip-unneeded", "-o", deploymentData.outputFilePaths[i], + var stripArgs = ["--strip-all", "-o", deploymentData.outputFilePaths[i], input.filePath]; var cmd = new Command(input.cpp.stripPath, stripArgs); cmd.description = "deploying " + input.fileName; diff --git a/share/qbs/modules/Android/sdk/utils.js b/share/qbs/modules/Android/sdk/utils.js index 90c8bc1bf..6d3837d57 100644 --- a/share/qbs/modules/Android/sdk/utils.js +++ b/share/qbs/modules/Android/sdk/utils.js @@ -222,3 +222,10 @@ function gdbserverOrStlDeploymentData(product, inputs, type) } return data; } + +function elementHasBundledAttributes(element) +{ + return element.hasAttribute("android:name") && + (element.attribute("android:name") === "android.app.bundled_in_assets_resource_id") || + (element.attribute("android:name") === "android.app.bundled_in_lib_resource_id"); +} diff --git a/share/qbs/modules/cpp/android-gcc.qbs b/share/qbs/modules/cpp/android-gcc.qbs index 3e44f4ff3..10190308a 100644 --- a/share/qbs/modules/cpp/android-gcc.qbs +++ b/share/qbs/modules/cpp/android-gcc.qbs @@ -182,7 +182,7 @@ LinuxGCC { fileTags: "android.nativelibrary" } prepare: { - var stripArgs = ["--strip-unneeded", "-o", output.filePath, input.filePath]; + var stripArgs = ["--strip-all", "-o", output.filePath, input.filePath]; var stripCmd = new Command(product.cpp.stripPath, stripArgs); stripCmd.description = "Stripping unneeded symbols from " + input.fileName; return stripCmd; diff --git a/src/app/qbs-setup-android/android-setup.cpp b/src/app/qbs-setup-android/android-setup.cpp index 029628419..a0a9d2948 100644 --- a/src/app/qbs-setup-android/android-setup.cpp +++ b/src/app/qbs-setup-android/android-setup.cpp @@ -45,6 +45,7 @@ #include <tools/profile.h> #include <tools/settings.h> #include <tools/version.h> +#include <tools/qttools.h> #include <QtCore/qbytearraylist.h> #include <QtCore/qcoreapplication.h> @@ -94,10 +95,10 @@ static QString mapArch(const QString &androidName) } struct QtAndroidInfo { - bool isValid() const { return !arch.isEmpty(); } + bool isValid() const { return !archs.isEmpty(); } QString qmakePath; - QString arch; + QStringList archs; QString platform; }; @@ -111,19 +112,30 @@ static QtAndroidInfo getInfoForQtDir(const QString &qtDir) if (!qdevicepri.open(QIODevice::ReadOnly)) return info; while (!qdevicepri.atEnd()) { + // For Qt < 5.14 use DEFAULT_ANDROID_TARGET_ARCH (which is the abi) to compute + // the architecture + // DEFAULT_ANDROID_ABIS doesn't exit + // For Qt >= 5.14: + // DEFAULT_ANDROID_TARGET_ARCH doesn't exist, use DEFAULT_ANDROID_ABIS to compute + // the architectures const QByteArray line = qdevicepri.readLine().simplified(); const bool isArchLine = line.startsWith("DEFAULT_ANDROID_TARGET_ARCH"); + const bool isAbisLine = line.startsWith("DEFAULT_ANDROID_ABIS"); const bool isPlatformLine = line.startsWith("DEFAULT_ANDROID_PLATFORM"); - if (!isArchLine && !isPlatformLine) + if (!isArchLine && !isPlatformLine && !isAbisLine) continue; const QList<QByteArray> elems = line.split('='); if (elems.size() != 2) continue; const QString rhs = QString::fromLatin1(elems.at(1).trimmed()); - if (isArchLine) - info.arch = mapArch(rhs); - else + if (isArchLine) { + info.archs << mapArch(rhs); + } else if (isAbisLine) { + for (const QString &abi: rhs.split(QLatin1Char(' '))) + info.archs << mapArch(abi); + } else { info.platform = rhs; + } } return info; } @@ -136,13 +148,16 @@ static QtInfoPerArch getQtAndroidInfo(const QString &qtSdkDir) return archs; QStringList qtDirs(qtSdkDir); - QDirIterator dit(qtSdkDir, QStringList() << QStringLiteral("android_*"), QDir::Dirs); + const QStringList nameFilters{QStringLiteral("android_*"), QStringLiteral("android")}; + QDirIterator dit(qtSdkDir, nameFilters, QDir::Dirs); while (dit.hasNext()) qtDirs << dit.next(); for (const auto &qtDir : qtDirs) { const QtAndroidInfo info = getInfoForQtDir(qtDir); - if (info.isValid()) - archs.insert(info.arch, info); + if (info.isValid()) { + for (const QString &arch: info.archs) + archs.insert(arch, info); + } } return archs; } @@ -224,8 +239,10 @@ static void setupNdk(qbs::Settings *settings, const QString &profileName, const qmakeFilePaths << qtAndroidInfo.qmakePath; platform = maximumPlatform(platform, qtAndroidInfo.platform); } - if (!qmakeFilePaths.empty()) + if (!qmakeFilePaths.empty()) { + qmakeFilePaths.removeDuplicates(); mainProfile.setValue(qls("moduleProviders.Qt.qmakeFilePaths"), qmakeFilePaths); + } if (!platform.isEmpty()) mainProfile.setValue(qls("Android.ndk.platform"), platform); } diff --git a/tests/auto/blackbox/find/find-android.qbs b/tests/auto/blackbox/find/find-android.qbs index 26dedc60f..de5c78d10 100644 --- a/tests/auto/blackbox/find/find-android.qbs +++ b/tests/auto/blackbox/find/find-android.qbs @@ -3,12 +3,23 @@ import qbs.TextFile Product { property string packageName: "" qbs.targetPlatform: "android" + multiplexByQbsProperties: ["architectures"] + + Properties { + condition: qbs.architectures && qbs.architectures.length > 1 + aggregate: true + multiplexedType: "json_arch" + } Depends { name: "Android.sdk"; required: false } Depends { name: "Android.ndk"; required: false } type: ["json"] + Rule { multiplex: true + property stringList inputTags: "json_arch" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags Artifact { filePath: ["android.json"] fileTags: ["json"] @@ -18,17 +29,50 @@ Product { cmd.description = output.filePath; cmd.sourceCode = function() { var tools = {}; + + for (var i in inputs["json_arch"]) { + var tf = new TextFile(inputs["json_arch"][i].filePath, TextFile.ReadOnly); + var json = JSON.parse(tf.readAll()); + tools["ndk"] = json["ndk"]; + tools["ndk-samples"] = json["ndk-samples"]; + tf.close(); + } + if (product.moduleProperty("Android.sdk", "present")) { tools["sdk"] = product.moduleProperty("Android.sdk", "sdkDir"); tools["sdk-build-tools-dx"] = product.Android.sdk.dxFilePath; } + if (product.java && product.java.present) + tools["jar"] = product.java.jarFilePath; + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } + Rule { + multiplex: true + Artifact { + filePath: ["android_arch.json"] + fileTags: ["json_arch"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; if (product.moduleProperty("Android.ndk", "present")) { tools["ndk"] = product.moduleProperty("Android.ndk", "ndkDir"); tools["ndk-samples"] = product.Android.ndk.ndkSamplesDir; } - if (product.java && product.java.present) - tools["jar"] = product.java.jarFilePath; var tf; try { @@ -43,3 +87,4 @@ Product { } } } + diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp index 371d15f10..eb0303a07 100644 --- a/tests/auto/blackbox/tst_blackboxandroid.cpp +++ b/tests/auto/blackbox/tst_blackboxandroid.cpp @@ -79,7 +79,7 @@ void TestBlackboxAndroid::android() QFETCH(QString, projectDir); QFETCH(QStringList, productNames); QFETCH(QList<QByteArrayList>, expectedFilesLists); - QFETCH(QStringList, customProperties); + QFETCH(QStringList, qmlAppCustomProperties); const SettingsPtr s = settings(); Profile p(theProfileName(projectDir == "qml-app"), s.get()); @@ -110,7 +110,7 @@ void TestBlackboxAndroid::android() auto currentExpectedFilesLists = expectedFilesLists; const QString configArgument = "config:" + configName; QbsRunParameters resolveParams("resolve"); - resolveParams.arguments << configArgument << customProperties; + resolveParams.arguments << configArgument << qmlAppCustomProperties; resolveParams.profile = p.name(); QCOMPARE(runQbs(resolveParams), 0); QbsRunParameters buildParams(QStringList{"--command-echo-mode", "command-line", @@ -206,14 +206,41 @@ void TestBlackboxAndroid::android_data() .toString() == "clang"; return QByteArray("lib/${ARCH}/") + (usesClang ? "libc++_shared.so" : oldcxxLib); }; - const QByteArrayList archsForQt = { pQt.value("qbs.architecture").toString().toUtf8() }; - QByteArrayList ndkArchsForQt = archsForQt; - if (ndkArchsForQt.first() == "armv7a") - ndkArchsForQt.first() = "armeabi-v7a"; - else if (ndkArchsForQt.first() == "armv5te") - ndkArchsForQt.first() = "armeabi"; - else if (ndkArchsForQt.first() == "arm64") - ndkArchsForQt.first() = "arm64-v8a"; + + bool usingOldQt = true; + QStringList qmakeFilePaths = pQt.value(QStringLiteral("moduleProviders.Qt.qmakeFilePaths")). + toStringList(); + if (qmakeFilePaths.size() == 1) { + qbs::Version version = TestBlackboxBase::qmakeVersion(qmakeFilePaths[0]); + if (version.isValid() && version >= qbs::Version(5, 14)) + usingOldQt = false; + } + + QByteArrayList archsForQt; + if (usingOldQt) { + archsForQt = { pQt.value("qbs.architecture").toString().toUtf8() }; + if (archsStringList.empty()) + archsStringList << QStringLiteral("armv7a"); // must match default in common.qbs + } else { + QStringList archsForQtStringList = pQt.value(QStringLiteral("qbs.architectures")) + .toStringList(); + if (archsForQtStringList.empty()) + archsForQtStringList << pQt.value("qbs.architecture").toString(); + std::transform(archsForQtStringList.begin(), + archsForQtStringList.end(), + std::back_inserter(archsForQt), + [] (const QString &s) { + return s.toUtf8(); + }); + } + + QByteArrayList ndkArchsForQt; + std::transform(archsForQt.begin(), archsForQt.end(), std::back_inserter(ndkArchsForQt), + [] (const QString &s) { + return s.toUtf8().replace("armv7a", "armeabi-v7a") + .replace("armv5te", "armeabi") + .replace("arm64", "arm64-v8a"); + }); auto expandArchs = [] (const QByteArrayList &archs, const QByteArrayList &lst) { const QByteArray &archPlaceHolder = "${ARCH}"; @@ -237,7 +264,7 @@ void TestBlackboxAndroid::android_data() QTest::addColumn<QString>("projectDir"); QTest::addColumn<QStringList>("productNames"); QTest::addColumn<QList<QByteArrayList>>("expectedFilesLists"); - QTest::addColumn<QStringList>("customProperties"); + QTest::addColumn<QStringList>("qmlAppCustomProperties"); QTest::newRow("teapot") << "teapot" << QStringList("TeapotNativeActivity") << (QList<QByteArrayList>() << commonFiles + expandArchs(archs, { @@ -257,9 +284,12 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libdependency.so"})) << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", "modules.qbs.architecture:" + archsStringList.first()}; - QTest::newRow("qml app") - << "qml-app" << QStringList("qmlapp") - << (QList<QByteArrayList>() << commonFiles + expandArchs(ndkArchsForQt, { + 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", @@ -298,64 +328,157 @@ void TestBlackboxAndroid::android_data() "lib/${ARCH}/libQt5QuickParticles.so", "lib/${ARCH}/libQt5Quick.so", "lib/${ARCH}/libqmlapp.so", - "res/layout/splash.xml"})) - << QStringList{"modules.Android.sdk.automaticSources:false", - "modules.qbs.architecture:" + archsForQt.first()}; - QTest::newRow("qml app using Ministro") - << "qml-app" << QStringList("qmlapp") - << (QList<QByteArrayList>() << commonFiles + expandArchs(ndkArchsForQt, { + "res/layout/splash.xml"}); + qmlAppMinistroExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { + "resources.arsc", + "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + "lib/${ARCH}/libgdbserver.so", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libqmlapp.so", + "res/layout/splash.xml"}); + qmlAppCustomMetaDataExpectedFiles << 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", + "assets/dummyasset.txt", + "lib/${ARCH}/libgdbserver.so", + 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", + "res/layout/splash.xml"}); + qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false", + "modules.qbs.architecture:" + archsForQt.first()}; + } else { + qmlAppExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { "resources.arsc", - "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + "assets/android_rcc_bundle.rcc", "lib/${ARCH}/libgdbserver.so", cxxLibPath("libgnustl_shared.so", true), - "lib/${ARCH}/libqmlapp.so", - "res/layout/splash.xml"})) + "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", + "res/layout/splash.xml"}); + qmlAppMinistroExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { + "resources.arsc", + "assets/android_rcc_bundle.rcc", + "lib/${ARCH}/libgdbserver.so", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libqmlapp_${ARCH}.so", + "res/layout/splash.xml"}); + qmlAppCustomMetaDataExpectedFiles << commonFiles + expandArchs(ndkArchsForQt, { + "resources.arsc", + "assets/android_rcc_bundle.rcc", + "assets/dummyasset.txt", + "lib/${ARCH}/libgdbserver.so", + 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", + "res/layout/splash.xml"}); + qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false"}; + } + QTest::newRow("qml app") + << "qml-app" << QStringList("qmlapp") + << (QList<QByteArrayList>() << qmlAppExpectedFiles) + << qmlAppCustomProperties; + QTest::newRow("qml app using Ministro") + << "qml-app" << QStringList("qmlapp") + << (QList<QByteArrayList>() << qmlAppMinistroExpectedFiles) << QStringList{"modules.Qt.android_support.useMinistro:true", "modules.Android.sdk.automaticSources:false"}; QTest::newRow("qml app with custom metadata") << "qml-app" << QStringList("qmlapp") - << (QList<QByteArrayList>() << 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", - "assets/dummyasset.txt", - "lib/${ARCH}/libgdbserver.so", - 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", - "res/layout/splash.xml"})) - << QStringList("modules.Android.sdk.automaticSources:true"); + << (QList<QByteArrayList>() << qmlAppCustomMetaDataExpectedFiles) + << QStringList("modules.Android.sdk.automaticSources:true"); QTest::newRow("no native") << "no-native" << QStringList("com.example.android.basicmediadecoder") diff --git a/tests/auto/blackbox/tst_blackboxbase.cpp b/tests/auto/blackbox/tst_blackboxbase.cpp index 61b0271f6..4550edcac 100644 --- a/tests/auto/blackbox/tst_blackboxbase.cpp +++ b/tests/auto/blackbox/tst_blackboxbase.cpp @@ -232,3 +232,21 @@ QMap<QString, QString> TestBlackboxBase::findJdkTools(int *status) {"jar", QDir::fromNativeSeparators(tools["jar"].toString())} }; } + +qbs::Version TestBlackboxBase::qmakeVersion(const QString &qmakeFilePath) +{ + QStringList arguments; + arguments << "-query" << "QT_VERSION"; + QProcess qmakeProcess; + qmakeProcess.start(qmakeFilePath, arguments); + if (!qmakeProcess.waitForStarted() || !qmakeProcess.waitForFinished() + || qmakeProcess.exitStatus() != QProcess::NormalExit) { + qDebug() << "qmake '" << qmakeFilePath << "' could not be run."; + return qbs::Version(); + } + QByteArray result = qmakeProcess.readAll().simplified(); + qbs::Version version = qbs::Version::fromString(result); + if (!version.isValid()) + qDebug() << "qmake '" << qmakeFilePath << "' version is not valid."; + return version; +} diff --git a/tests/auto/blackbox/tst_blackboxbase.h b/tests/auto/blackbox/tst_blackboxbase.h index c1c4d39b2..518cc80d0 100644 --- a/tests/auto/blackbox/tst_blackboxbase.h +++ b/tests/auto/blackbox/tst_blackboxbase.h @@ -92,6 +92,7 @@ protected: static void ccp(const QString &sourceDirPath, const QString &targetDirPath); static QString findExecutable(const QStringList &fileNames); QMap<QString, QString> findJdkTools(int *status); + static qbs::Version qmakeVersion(const QString &qmakeFilePath); const QString testDataDir; const QString testSourceDir; |