diff options
author | Olli Vuolteenaho <olli.vuolteenaho@qt.io> | 2025-07-08 07:16:53 +0300 |
---|---|---|
committer | Olli Vuolteenaho <olli.vuolteenaho@qt.io> | 2025-09-09 10:39:16 +0300 |
commit | e6ae3582d5dc6ecfeb590b05e528cd20d6f51581 (patch) | |
tree | 0ba9889ff5c8d71cf5dfd6da35d50ec9a6001aae | |
parent | fa8ca1b4737b5e5b6effba3264dda972ebf2f29e (diff) |
The logic regarding extraCMakeArguments, qt.abiPath and multi-ABI builds
has been very iffy and potentially confusing for the user. We have had
configure commands where the same argument could be defined twice and
with different values. Also builds could have had more ABIs selected
than the user wanted.
The aim is to fix that by improving the parsing of QT_ANDROID_ABIS
and QT_ANDROID_BUILD_ALL_ABIS and outputting warnings or errors after
bad or useless configurations, including but not limited to:
* Setting QT_ANDROID_BUILD_ALL_ABIS=OFF without qt.abiPath, resulting in
the first ABI alphabetically, which is probably unwanted
* Setting an ABI with QT_ANDROID_ABIS might include another
uncontrollable ABI
* Setting both qt.abiPath and QT_ANDROID_ABIS may not work as the user
intended
Fixes: QTTA-407
Fixes: QTTA-350
Change-Id: I09c3fea3b0670a2809bc90e291def7b37933984c
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
3 files changed, 78 insertions, 31 deletions
diff --git a/src/main/groovy/org/qtproject/qt/gradleplugin/QtBuildTask.groovy b/src/main/groovy/org/qtproject/qt/gradleplugin/QtBuildTask.groovy index 53a257e..882cc14 100644 --- a/src/main/groovy/org/qtproject/qt/gradleplugin/QtBuildTask.groovy +++ b/src/main/groovy/org/qtproject/qt/gradleplugin/QtBuildTask.groovy @@ -36,6 +36,8 @@ class QtBuildTask extends DefaultTask { private final String NDK_PATH_CMAKE_ARG = "ANDROID_NDK_ROOT" private final String SDK_PATH_CMAKE_ARG = "ANDROID_SDK_ROOT" private final String MAKE_PROGRAM_CMAKE_ARG = "CMAKE_MAKE_PROGRAM" + private final String BUILD_ALL_ABIS_ARG = "QT_ANDROID_BUILD_ALL_ABIS" + private final String ANDROID_ABIS_ARG = "QT_ANDROID_ABIS" private String pluginVersion = "NA" @@ -81,11 +83,8 @@ class QtBuildTask extends DefaultTask { include: ["${targetName}.aar"])) } - // Declared as @Internal so gradle will ignore this path when checking if the task is - // up-to-date or not. - @Internal - def getQtCMakeWrapperPath() { - def qtCMakeWrapperPath = "$qtKitDir/bin/qt-cmake" + def getQtCMakeWrapperPath(File qtAbiPath) { + def qtCMakeWrapperPath = "$qtAbiPath/bin/qt-cmake" if (isWindows()) qtCMakeWrapperPath += '.bat' return qtCMakeWrapperPath @@ -204,7 +203,7 @@ class QtBuildTask extends DefaultTask { // @TODO: QTTA-297 Instead of taking the first ABI found take one that, // matches with the running device/emulator. def listAndroidABIsAndSelectFirst() { - def firstMatch = new File(qtPath).listFiles().find { file -> + def firstMatch = new File(qtPath).listFiles().sort().find { file -> file.name ==~ Utils.androidAbiDirNameFormat() } @@ -213,8 +212,18 @@ class QtBuildTask extends DefaultTask { return new File(firstMatch.path) } - def resolveCoreJson() { - def qtCoreJsonPath = "$qtKitDir/modules/Core.json" + def findFirstMatchingAbiDirectory(ArrayList androidABIs) { + def firstMatch = new File(qtPath).listFiles().find { file -> + file.name in androidABIs + } + + if (!firstMatch) + Utils.logAndThrowException("No Qt for Android kit from $androidABIs found from: $qtPath") + return new File(firstMatch.path) + } + + def resolveCoreJson(File qtAbiPath) { + def qtCoreJsonPath = "$qtAbiPath/modules/Core.json" if (!new File(qtCoreJsonPath).exists()) Utils.logAndThrowException("No Core.json file found under $qtCoreJsonPath.") @@ -250,14 +259,41 @@ class QtBuildTask extends DefaultTask { } def configCommand() { - def buildMultiABI = !qtKitDir - if (!qtKitDir) - qtKitDir = listAndroidABIsAndSelectFirst() - def qtCoreJSon = resolveCoreJson() + def buildAllAbis = extractValueFromCMakeArgs(BUILD_ALL_ABIS_ARG) + def androidAbis = extractValueFromCMakeArgs(ANDROID_ABIS_ARG) + def qtAbiPath = "" + + // if qt.abiPath is set, build a single ABI build and ignore other ABI arguments + if (qtKitDir) { + qtAbiPath = new File(qtKitDir) + for (argument in [BUILD_ALL_ABIS_ARG, ANDROID_ABIS_ARG]) { + if (extraCMakeArguments.any { it.contains(argument) } ) { + System.err.println("Warning: Setting qt.abiPath means $argument will be ignored.") + extraCMakeArguments.removeAll { it.contains(argument) } + } + } + + } else if (Utils.parseCMakeBoolean(buildAllAbis) == true) { + qtAbiPath = listAndroidABIsAndSelectFirst() + + // if QT_ANDROID_ABIS is defined, build a multi-ABI build with those ABIs + } else if (androidAbis) { + androidAbis = androidAbis.replace("-", "_").split(";").collect{"android_$it".toString()} + qtAbiPath = findFirstMatchingAbiDirectory(androidAbis) + + } else if (Utils.parseCMakeBoolean(buildAllAbis) == false) { + Utils.logAndThrowException( + "Setting $BUILD_ALL_ABIS_ARG=$buildAllAbis without defining $ANDROID_ABIS_ARG " + + "or qt.abiPath would result in a single ABI build with the first one in alphabetical " + + "order. This is probably not what is wanted." + ) - def architecture = getCoreJsonArchitecture(qtCoreJSon) - def currentABI = Utils.abiFromArchitecture(architecture) + // the "standard" all-ABIs build + } else { + qtAbiPath = listAndroidABIsAndSelectFirst() + } + def qtCoreJSon = resolveCoreJson(qtAbiPath) def platform = getCoreJsonPlatform(qtCoreJSon) if (platform.toString().toLowerCase() != 'android') { Utils.logAndThrowException( @@ -265,7 +301,7 @@ class QtBuildTask extends DefaultTask { "The kit's path: $qtKitDir") } - def qtCMakeWrapperPath = getQtCMakeWrapperPath() + def qtCMakeWrapperPath = getQtCMakeWrapperPath(qtAbiPath) def cmd = [ qtCMakeWrapperPath, @@ -274,10 +310,11 @@ class QtBuildTask extends DefaultTask { '-G', 'Ninja', '-DQT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS=ON', '-DQT_USE_TARGET_ANDROID_BUILD_DIR=ON', - "-DANDROID_ABI=$currentABI", - "-DQT_ANDROID_BUILD_ALL_ABIS=${buildMultiABI ? 'ON': 'OFF'}" ] + if (!buildAllAbis && !androidAbis && !qtKitDir) + cmd += "-DQT_ANDROID_BUILD_ALL_ABIS=ON" + def ninjaPathFromExtraCMakeArgs = extractValueFromCMakeArgs(MAKE_PROGRAM_CMAKE_ARG) if (!ninjaPathFromExtraCMakeArgs) resolveNinjaPath() diff --git a/src/main/groovy/org/qtproject/qt/gradleplugin/Utils.groovy b/src/main/groovy/org/qtproject/qt/gradleplugin/Utils.groovy index d1b2bdb..3eea60f 100644 --- a/src/main/groovy/org/qtproject/qt/gradleplugin/Utils.groovy +++ b/src/main/groovy/org/qtproject/qt/gradleplugin/Utils.groovy @@ -8,20 +8,8 @@ import org.gradle.api.Project @Singleton class Utils { - static String ANDROID_ABI_X86_64 = "x86_64" - static String ANDROID_ABI_X86 = "x86" - - static def abis = [ - 'arm64': 'arm64-v8a', 'arm': 'armeabi-v7a', - 'x86_64': ANDROID_ABI_X86_64, 'i386': ANDROID_ABI_X86 - ] - static String androidAbiDirNameFormat() { - return "android_(arm64_v8a|armv7|$ANDROID_ABI_X86_64|$ANDROID_ABI_X86)" - } - - static String abiFromArchitecture(String architecture) { - return abis.find { it.key == architecture }?.value + return "android_(arm64_v8a|armv7|x86_64|x86)" } static def getAndroidBuildTargetName(File buildDir) { @@ -51,4 +39,26 @@ class Utils { file = project.rootProject.file(path) return file } + + // CMake truthy values are 1, ON, TRUE, Y and non-zero numbers including floats + static parseCMakeBoolean(String value) { + if (value == "") { + return null + } + + try { + def returnValue = value.toFloat() + return returnValue != 0.0 + } + catch(java.lang.NumberFormatException ignored) { + // Not a number, proceed to string processing + } + + // toBoolean() returns true for "true", "y" or "1" (ignoring the case) + // we need to handle the rest + return value.toUpperCase() + .replace("ON", "TRUE") + .replace("YES", "TRUE") + .toBoolean() + } } diff --git a/src/test/groovy/org/qtproject/qt/gradleplugin/QtBuildTaskTest.groovy b/src/test/groovy/org/qtproject/qt/gradleplugin/QtBuildTaskTest.groovy index 8be3242..a96701a 100644 --- a/src/test/groovy/org/qtproject/qt/gradleplugin/QtBuildTaskTest.groovy +++ b/src/test/groovy/org/qtproject/qt/gradleplugin/QtBuildTaskTest.groovy @@ -134,7 +134,7 @@ class QtBuildTaskTest extends Specification { def mockQtCMakeFile = createQtCMakeFileMock(buildTask) when: "getQtCMakeWrapperPath is called" - def cmakeDir = buildTask.getQtCMakeWrapperPath() + def cmakeDir = buildTask.getQtCMakeWrapperPath(new File(buildTask.qtKitDir)) then: "qt-cmake file is found at the appropriate path" assert new File(cmakeDir).exists() |