summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexey Edelev <alexey.edelev@qt.io>2021-07-09 19:29:37 +0200
committerAlexey Edelev <semlanik@gmail.com>2021-10-05 17:56:20 +0200
commit0a02d845559e4fd9d1bd72942c5118f3bb1307bf (patch)
tree3affb1ada4450777593c95a86559fc7e60b8199c
parenta5073956f89f47493cb17d162c8d51821c5368f1 (diff)
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_<abi> 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_<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 <qt_ci_bot@qt-project.org> Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake112
-rw-r--r--src/corelib/Qt6CoreConfigExtras.cmake.in1
-rw-r--r--src/corelib/Qt6CoreMacros.cmake79
3 files changed, 177 insertions, 15 deletions
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 $<TARGET_FILE:${target}>
- "${apk_final_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
+ "${copy_target_path}/$<TARGET_FILE_NAME:${target}>"
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_<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}")