diff --git a/src/android/CMakeLists.txt b/src/android/CMakeLists.txt index 045076c8d3c..007430598ea 100644 --- a/src/android/CMakeLists.txt +++ b/src/android/CMakeLists.txt @@ -8,5 +8,6 @@ if (ANDROID) add_subdirectory(jar) add_subdirectory(java) add_subdirectory(templates) + add_subdirectory(templates_aar) endif() diff --git a/src/android/templates_aar/AndroidManifest.xml b/src/android/templates_aar/AndroidManifest.xml new file mode 100644 index 00000000000..9c9e91e650c --- /dev/null +++ b/src/android/templates_aar/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + diff --git a/src/android/templates_aar/CMakeLists.txt b/src/android/templates_aar/CMakeLists.txt new file mode 100644 index 00000000000..07c421fbab7 --- /dev/null +++ b/src/android/templates_aar/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Android aar specific template files + +set(templates_aar_files + "${CMAKE_CURRENT_SOURCE_DIR}/AndroidManifest.xml") + +add_custom_target(Qt${QtBase_VERSION_MAJOR}AndroidAarTemplates + SOURCES + ${templates_aar_files} +) + +qt_path_join(destination ${QT_INSTALL_DIR} ${INSTALL_DATADIR} "src/android/templates_aar") + +qt_copy_or_install(FILES ${templates_aar_files} + DESTINATION "${destination}") + +if(NOT QT_WILL_INSTALL) + qt_internal_copy_at_build_time(TARGET Qt${QtBase_VERSION_MAJOR}AndroidAarTemplates + FILES ${templates_aar_files} + DESTINATION ${destination} + ) +endif() diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 6218df19476..96b24ee680e 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -381,6 +381,9 @@ function(qt6_android_add_apk_target target) if(TARGET aab) add_dependencies(aab ${target}_make_aab) endif() + if(TARGET aar) + add_dependencies(aar ${target}_make_aar) + endif() if(TARGET apk) add_dependencies(apk ${target}_make_apk) _qt_internal_create_global_apk_all_target_if_needed() @@ -418,8 +421,10 @@ function(qt6_android_add_apk_target target) endif() set(apk_file_name "${target}.apk") + set(aar_file_name "${target}.aar") set(dep_file_name "${target}.d") set(apk_final_file_path "${apk_final_dir}/${apk_file_name}") + set(aar_final_file_path "${apk_final_dir}/${aar_file_name}") set(dep_file_path "${apk_final_dir}/${dep_file_name}") set(target_file_copy_relative_path "libs/${CMAKE_ANDROID_ARCH_ABI}/$") @@ -523,10 +528,33 @@ function(qt6_android_add_apk_target target) VERBATIM ${uses_terminal} ) + + # Add custom command that creates the aar and triggers rebuild if files listed in + # ${dep_file_path} are changed. + add_custom_command(OUTPUT "${aar_final_file_path}" + COMMAND ${CMAKE_COMMAND} + -E copy "$" + "${apk_final_dir}/${target_file_copy_relative_path}" + COMMAND "${deployment_tool}" + --input "${deployment_file}" + --output "${apk_final_dir}" + --apk "${aar_final_file_path}" + --depfile "${dep_file_path}" + --builddir "${relative_to_dir}" + --build-aar + ${extra_args} + COMMENT "Creating AAR for ${target}" + DEPENDS "${target}" "${deployment_file}" ${extra_deps} + DEPFILE "${dep_file_path}" + VERBATIM + ${uses_terminal} + ) cmake_policy(POP) # Create a ${target}_make_apk target to trigger the apk build. add_custom_target(${target}_make_apk DEPENDS "${apk_final_file_path}") + # Create a ${target}_make_aar target to trigger the aar build. + add_custom_target(${target}_make_aar DEPENDS "${aar_final_file_path}") else() add_custom_target(${target}_make_apk DEPENDS ${target}_prepare_apk_dir @@ -540,6 +568,19 @@ function(qt6_android_add_apk_target target) VERBATIM ${uses_terminal} ) + + add_custom_target(${target}_make_aar + DEPENDS ${target}_prepare_apk_dir + COMMAND ${deployment_tool} + --input ${deployment_file} + --output ${apk_final_dir} + --apk ${aar_final_file_path} + --build-aar + ${extra_args} + COMMENT "Creating AAR for ${target}" + VERBATIM + ${uses_terminal} + ) endif() # Add target triggering AAB creation. Since the _make_aab target is not added to the ALL @@ -638,6 +679,11 @@ function(_qt_internal_create_global_android_targets) # It will trigger building all the apk build targets that are added as part of the project. # Allow opting out. _qt_internal_create_global_android_targets_impl(aab) + + # Create a top-level "aar" target for convenience, so that users can call 'ninja aar'. + # It will trigger building all the aar build targets that are added as part of the project. + # Allow opting out. + _qt_internal_create_global_android_targets_impl(aar) endfunction() # The function collects all known non-imported shared libraries that are created in the build tree. diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index cbe6336c975..c2af8d381cf 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -105,6 +105,7 @@ struct Options , installApk(false) , uninstallApk(false) , qmlImportScannerBinaryPath() + , buildAar(false) {} enum DeploymentMechanism @@ -240,6 +241,7 @@ struct Options // Override qml import scanner path QString qmlImportScannerBinaryPath; bool qmlSkipImportScanning = false; + bool buildAar; }; static const QHash elfArchitectures = { @@ -526,6 +528,8 @@ Options parseOptions() options.protectedAuthenticationPath = true; } else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) { options.auxMode = true; + } else if (argument.compare("--build-aar"_L1, Qt::CaseInsensitive) == 0) { + options.buildAar = true; } else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) { options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed(); } else if (argument.compare("--no-rcc-bundle-cleanup"_L1, @@ -537,6 +541,23 @@ Options parseOptions() } } + if (options.buildAar) { + if (options.installApk || options.uninstallApk) { + fprintf(stderr, "Warning: Skipping %s, AAR packages are not installable.\n", + options.uninstallApk ? "--reinstall" : "--install"); + options.installApk = false; + options.uninstallApk = false; + } + if (options.buildAAB) { + fprintf(stderr, "Warning: Skipping -aab as --build-aar is present.\n"); + options.buildAAB = false; + } + if (!options.keyStore.isEmpty()) { + fprintf(stderr, "Warning: Skipping --sign, signing AAR packages is not supported.\n"); + options.keyStore.clear(); + } + } + if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty()) options.helpRequested = true; @@ -644,6 +665,9 @@ Optional arguments: --apk : Path where to copy the built apk. + --build-aar: Build an AAR package. This option skips --aab, --install, + --reinstall, and --sign options if they are provided. + --qml-importscanner-binary : Override the default qmlimportscanner binary path. By default the qmlimportscanner binary is located using the Qt directory @@ -1434,6 +1458,9 @@ bool copyAndroidTemplate(const Options &options) if (!copyAndroidTemplate(options, "/src/android/templates"_L1)) return false; + if (options.buildAar) + return copyAndroidTemplate(options, "/src/android/templates_aar"_L1); + return true; } @@ -2850,7 +2877,9 @@ bool buildAndroidProject(const Options &options) abiList.append(it.key()); } gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ... - gradleProperties["qtGradlePluginType"] = "com.android.application"; + gradleProperties["qtGradlePluginType"] = options.buildAar + ? "com.android.library" + : "com.android.application"; if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties)) return false; @@ -2938,6 +2967,7 @@ bool uninstallApk(const Options &options) enum PackageType { AAB, + AAR, UnsignedAPK, SignedAPK }; @@ -2945,7 +2975,14 @@ enum PackageType { QString packagePath(const Options &options, PackageType pt) { QString path(options.outputDirectory); - path += "/build/outputs/%1/"_L1.arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle")); + // The package type is always AAR if option.buildAar has been set + if (options.buildAar) + pt = AAR; + static QHash packageTypeToPath{ { AAB, QStringLiteral("bundle") }, + { AAR, QStringLiteral("aar") }, + { UnsignedAPK, QStringLiteral("apk") }, + { SignedAPK, QStringLiteral("apk") } }; + path += "/build/outputs/%1/"_L1.arg(packageTypeToPath[pt]); QString buildType(options.releasePackage ? "release/"_L1 : "debug/"_L1); if (QDir(path + buildType).exists()) path += buildType; @@ -2956,6 +2993,9 @@ QString packagePath(const Options &options, PackageType pt) if (pt == UnsignedAPK) path += "un"_L1; path += "signed.apk"_L1; + } else if (pt == AAR){ + path.chop(1); + path += ".aar"_L1; } else { path.chop(1); path += ".aab"_L1; @@ -2966,6 +3006,8 @@ QString packagePath(const Options &options, PackageType pt) if (pt == SignedAPK) path += "-signed"_L1; path += ".apk"_L1; + } else if (pt == AAR){ + path += ".aar"_L1; } else { path += ".aab"_L1; }