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>
This commit is contained in:
Alexey Edelev 2021-07-09 19:29:37 +02:00 committed by Alexey Edelev
parent a5073956f8
commit 0a02d84555
3 changed files with 177 additions and 15 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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}")