From 0a02d845559e4fd9d1bd72942c5118f3bb1307bf Mon Sep 17 00:00:00 2001 From: Alexey Edelev Date: Fri, 9 Jul 2021 19:29:37 +0200 Subject: Add basic android multi-abi support for CMake projects Use CMake external project to crosscompile android libraries for mutliple android ABIs at the same time. The idea behind is to use pre-compiled Qt for each android ABI in external projects, compile libraries and then utilize results of compilation in common androiddeployqt call. By default multi-abi build uses the main ABI that qt toolchain file belongs to. The list of the autodetected Qt for Android ABIs is stored in QT_DEFAULT_ANDROID_ABIS cache variable. Users may change the set of the Android ABIs project-wide using QT_ANDROID_ABIS CMake variable or for the specific target by adding the ANDROID_ABIS argument when calling qt6_add_executable. To enable build for the autodetected ABIs user may set the QT_ANDROID_BUILD_ALL_ABIS option to TRUE. By default build procedure is looking for the respective android_ folders to run per-abi build. If user's Qt for Android folder structure is different then one is created by Qt installer, path to the each Qt for Android might be overwritten using QT_PATH_ANDROID_ABI_ CMake variable. Note: This only adds support for the multi-abi build of user's applications. TODO: This commit limits projects to not have in-tree library dependencies. That means that executable targets may have dependencies only from Qt directories or android sysroots. See QTBUG-94714 for details. [ChangeLog][Android][Platform Specific Changes] Added basic support for multi-abi builds of user projects. Task-number: QTBUG-88841 Change-Id: I3239ffe61e6b437e170c8decc5c36a9e774ed0fb Reviewed-by: Qt CI Bot Reviewed-by: Assam Boudjelthia --- src/corelib/Qt6AndroidMacros.cmake | 112 +++++++++++++++++++++++++++---- src/corelib/Qt6CoreConfigExtras.cmake.in | 1 + src/corelib/Qt6CoreMacros.cmake | 79 +++++++++++++++++++++- 3 files changed, 177 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 99c41fc733..a5cd1c8f98 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -81,16 +81,30 @@ function(qt6_android_generate_deployment_settings target) endif() endif() + set(abi_records "") + get_target_property(qt_android_abis ${target} _qt_android_abis) + if(qt_android_abis) + foreach(abi IN LISTS qt_android_abis) + _qt_internal_get_android_abi_path(qt_abi_path ${abi}) + file(TO_CMAKE_PATH "${qt_abi_path}" qt_android_install_dir_native) + list(APPEND abi_records "\"${abi}\": \"${qt_android_install_dir_native}\"") + endforeach() + endif() + # Required to build unit tests in developer build if(QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX) set(qt_android_install_dir "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}") else() set(qt_android_install_dir "${QT6_INSTALL_PREFIX}") endif() - file(TO_CMAKE_PATH "${qt_android_install_dir}" qt_android_install_dir_native) + list(APPEND abi_records "\"${CMAKE_ANDROID_ARCH_ABI}\": \"${qt_android_install_dir_native}\"") + + list(JOIN abi_records "," qt_android_install_dir_records) + set(qt_android_install_dir_records "{${qt_android_install_dir_records}}") + string(APPEND file_contents - " \"qt\": \"${qt_android_install_dir_native}\",\n") + " \"qt\": ${qt_android_install_dir_records},\n") # Android SDK path file(TO_CMAKE_PATH "${ANDROID_SDK_ROOT}" android_sdk_root_native) @@ -126,19 +140,24 @@ function(qt6_android_generate_deployment_settings target) string(APPEND file_contents " \"ndk-host\": \"${ANDROID_NDK_HOST_SYSTEM_NAME}\",\n") - if (CMAKE_ANDROID_ARCH_ABI STREQUAL "x86") - set(arch_value "i686-linux-android") - elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64") - set(arch_value "x86_64-linux-android") - elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a") - set(arch_value "aarch64-linux-android") - else() - set(arch_value "arm-linux-androideabi") - endif() + set(architecture_record_list "") + foreach(abi IN LISTS qt_android_abis CMAKE_ANDROID_ARCH_ABI) + if(abi STREQUAL "x86") + set(arch_value "i686-linux-android") + elseif(abi STREQUAL "x86_64") + set(arch_value "x86_64-linux-android") + elseif(abi STREQUAL "arm64-v8a") + set(arch_value "aarch64-linux-android") + elseif(abi) + set(arch_value "arm-linux-androideabi") + endif() + list(APPEND architecture_record_list "\"${abi}\":\"${arch_value}\"") + endforeach() + list(JOIN architecture_record_list "," architecture_records) # Architecture string(APPEND file_contents - " \"architectures\": { \"${CMAKE_ANDROID_ARCH_ABI}\" : \"${arch_value}\" },\n") + " \"architectures\": { ${architecture_records} },\n") # deployment dependencies _qt_internal_add_android_deployment_multi_value_property(file_contents ${target} @@ -300,13 +319,23 @@ function(qt6_android_add_apk_target target) set(apk_intermediate_file_path "${apk_intermediate_dir}/${apk_file_name}") set(dep_intermediate_file_path "${apk_intermediate_dir}/${dep_file_name}") + # Temporary location of the library target file. If the library is built as an external project + # inside multi-abi build the QT_ANDROID_ABI_TARGET_PATH variable will point to the ABI related + # folder in the top-level build directory. + set(copy_target_path "${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}") + if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT AND QT_ANDROID_ABI_TARGET_PATH) + set(copy_target_path "${QT_ANDROID_ABI_TARGET_PATH}") + endif() + # This target is used by Qt Creator's Android support and by the ${target}_make_apk target # in case DEPFILEs are not supported. + # Also the target is used to copy the library that belongs to ${target} when building multi-abi + # apk to the abi-specific directory. add_custom_target(${target}_prepare_apk_dir ALL DEPENDS ${target} COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - "${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$" + "${copy_target_path}/$" COMMENT "Copying ${target} binary to apk folder" ) @@ -573,3 +602,60 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) qt6_android_add_apk_target(${ARGV}) endfunction() endif() + +# The function returns the installation path to Qt for Android for the specified ${abi}. +# By default function expects to find a layout as is installed by the Qt online installer: +# Qt_install_dir/Version/ +# |__ gcc_64 +# |__ android_arm64_v8a +# |__ android_armv7 +# |__ android_x86 +# |__ android_x86_64 +function(_qt_internal_get_android_abi_path out_path abi) + if(DEFINED QT_PATH_ANDROID_ABI_${abi}) + get_filename_component(${out_path} "${QT_PATH_ANDROID_ABI_${abi}}" ABSOLUTE) + else() + # Map the ABI value to the Qt for Android folder. + if (abi STREQUAL "x86") + set(abi_directory_suffix "${abi}") + elseif (abi STREQUAL "x86_64") + set(abi_directory_suffix "${abi}") + elseif (abi STREQUAL "arm64-v8a") + set(abi_directory_suffix "arm64_v8a") + else() + set(abi_directory_suffix "armv7") + endif() + + get_filename_component(${out_path} + "${_qt_cmake_dir}/../../../android_${abi_directory_suffix}" ABSOLUTE) + endif() + set(${out_path} "${${out_path}}" PARENT_SCOPE) +endfunction() + +# The function collects list of existing Qt for Android using _qt_internal_get_android_abi_path +# and pre-defined set of known Android ABIs. The result is written to QT_DEFAULT_ANDROID_ABIS +# cache variable. +# Note that QT_DEFAULT_ANDROID_ABIS is not intended to be set outside the function and will be +# rewritten. +function(_qt_internal_collect_default_android_abis) + set(known_android_abis armeabi-v7a arm64-v8a x86 x86_64) + + set(default_abis) + foreach(abi IN LISTS known_android_abis) + _qt_internal_get_android_abi_path(qt_abi_path ${abi}) + # It's expected that Qt for Android contains ABI specific toolchain file. + if(EXISTS "${qt_abi_path}/lib/cmake/${QT_CMAKE_EXPORT_NAMESPACE}/qt.toolchain.cmake" + OR CMAKE_ANDROID_ARCH_ABI STREQUAL abi) + list(APPEND default_abis ${abi}) + endif() + endforeach() + set(QT_DEFAULT_ANDROID_ABIS "${default_abis}" CACHE STRING + "The list of autodetected Qt for Android ABIs" FORCE + ) + set(QT_ANDROID_ABIS "${CMAKE_ANDROID_ARCH_ABI}" CACHE STRING + "The list of Qt for Android ABIs used to build the project apk" + ) + set(QT_ANDROID_BUILD_ALL_ABIS FALSE CACHE BOOL + "Build project using the list of autodetected Qt for Android ABIs" + ) +endfunction() diff --git a/src/corelib/Qt6CoreConfigExtras.cmake.in b/src/corelib/Qt6CoreConfigExtras.cmake.in index f86d4ad5bf..8980f1bfa6 100644 --- a/src/corelib/Qt6CoreConfigExtras.cmake.in +++ b/src/corelib/Qt6CoreConfigExtras.cmake.in @@ -47,6 +47,7 @@ set(_Qt6CTestMacros "${_Qt6CoreConfigDir}/Qt6CTestMacros.cmake") if(ANDROID_PLATFORM) include("${CMAKE_CURRENT_LIST_DIR}/@QT_CMAKE_EXPORT_NAMESPACE@AndroidMacros.cmake") _qt_internal_create_global_apk_target() + _qt_internal_collect_default_android_abis() endif() if(EMSCRIPTEN) diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 565340325c..9785963f5d 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -542,9 +542,10 @@ function(qt6_add_executable target) endfunction() function(_qt_internal_create_executable target) + cmake_parse_arguments(arg "" "" "ANDROID_ABIS" ${ARGN}) if(ANDROID) list(REMOVE_ITEM ARGN "WIN32" "MACOSX_BUNDLE") - add_library("${target}" MODULE ${ARGN}) + add_library("${target}" MODULE ${arg_UNPARSED_ARGUMENTS}) # On our qmake builds we do don't compile the executables with # visibility=hidden. Not having this flag set will cause the # executable to have main() hidden and can then no longer be loaded @@ -555,8 +556,82 @@ function(_qt_internal_create_executable target) set_property(TARGET "${target}" PROPERTY OBJCXX_VISIBILITY_PRESET default) qt6_android_apply_arch_suffix("${target}") set_property(TARGET "${target}" PROPERTY _qt_is_android_executable TRUE) + # Build per-abi binaries for android + if(NOT QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT) + if(QT_ANDROID_BUILD_ALL_ABIS) + # Use autodetected Qt for Android ABIs. + set(android_abis ${QT_DEFAULT_ANDROID_ABIS}) + elseif(arg_ANDROID_ABIS) + # Use target-specific Qt for Android ABIs. + set(android_abis ${arg_ANDROID_ABIS}) + elseif(QT_ANDROID_ABIS) + # Use project-wide Qt for Android ABIs. + set(android_abis ${QT_ANDROID_ABIS}) + else() + # User have an empty list of Qt for Android ABIs. + message(FATAL_ERROR + "The list of Android ABIs is empty, when building ${target}.\n" + "You have the following options to select ABIs for a target:\n" + " - Set the QT_ANDROID_ABIS variable before calling qt6_add_executable\n" + " - Add the ANDROID_ABIS parameter to the qt6_add_executable call\n" + " - Set QT_ANDROID_BUILD_ALL_ABIS flag to try building with\n" + " the list of autodetected Qt for Android:\n ${QT_DEFAULT_ANDROID_ABIS}" + ) + endif() + + set(missing_qt_abi_toolchains "") + # Create external projects for each android ABI except the main one. + list(REMOVE_ITEM android_abis "${CMAKE_ANDROID_ARCH_ABI}") + include(ExternalProject) + foreach(abi IN ITEMS ${android_abis}) + if(NOT "${abi}" IN_LIST QT_DEFAULT_ANDROID_ABIS) + list(APPEND missing_qt_abi_toolchains ${abi}) + list(REMOVE_ITEM android_abis "${abi}") + continue() + endif() + + _qt_internal_get_android_abi_path(qt_abi_path ${abi}) + set(qt_abi_toolchain_path + "${qt_abi_path}/lib/cmake/${QT_CMAKE_EXPORT_NAMESPACE}/qt.toolchain.cmake") + set(abi_copy_target_path "${CMAKE_CURRENT_BINARY_DIR}/android-build/libs/${abi}") + ExternalProject_Add("${target}_${abi}" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${target}_${abi}" + CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${qt_abi_toolchain_path}" + "-DQT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT=ON" + "-DQT_ANDROID_ABI_TARGET_PATH=${abi_copy_target_path}" + STEP_TARGETS build + EXCLUDE_FROM_ALL TRUE + BUILD_COMMAND "${CMAKE_COMMAND}" + "--build" "${CMAKE_CURRENT_BINARY_DIR}/${target}_${abi}" + "--target" "${target}_prepare_apk_dir" + ) + add_dependencies(${target} "${target}_${abi}-build") + endforeach() + + if(missing_qt_abi_toolchains) + list(JOIN missing_qt_abi_toolchains ", " missing_qt_abi_toolchains_string) + message(FATAL_ERROR "Cannot find toolchain files for the manually specified Android" + " ABIs: ${missing_qt_abi_toolchains_string}" + "\nSkipping these ABIs." + "\nNote that you also may manually specify the path to the required Qt for" + " Android ABI using QT_PATH_ANDROID_ABI_ CMake variable.\n") + endif() + + list(JOIN android_abis ", " android_abis_string) + if(android_abis_string) + set(android_abis_string "${CMAKE_ANDROID_ARCH_ABI}(default), ${android_abis_string}") + else() + set(android_abis_string "${CMAKE_ANDROID_ARCH_ABI}(default)") + endif() + if(NOT QT_NO_ANDROID_ABI_STATUS_MESSAGE) + message(STATUS "Configuring '${target}' for the following Android ABIs:" + " ${android_abis_string}") + endif() + set_target_properties(${target} PROPERTIES _qt_android_abis "${android_abis}") + endif() else() - add_executable("${target}" ${ARGN}) + add_executable("${target}" ${arg_UNPARSED_ARGUMENTS}) endif() _qt_internal_set_up_static_runtime_library("${target}") -- cgit v1.2.3