CMake: Split out some Android functions into a public API file

To fix CMake Qt For Android projects to configure, we need to move
some functions from a private CMake API file only, to a public one.

Add Qt6AndroidMacros.cmake which will be loaded by Qt6Core package.

We'll have to decide how we proceed with Qt5AndroidSupport.cmake,
because that file automatically runs code when included in Qt5, and we
usually don't want to do it.

We'll also have to decide how to handle the define_property() calls
that are still left in the private QtPlatformAndroid.cmake file.

With this fix, Qt example CMake projects that use
add_qt_gui_executable should now be buildable. An APK can be created
with 'ninja apk'.

Unfortunately Qt Creator 4.13 does not currently seem to support
opening and building CMake Qt For Android projects properly.
While the build succeeds after fiddling with the Kit settings, the APK
deploy step fails to run (at least on my machine).

So the simplest way to run the built APK is to open the android-build
dir with Android Studio and launch the example application from there.

Task-number: QTBUG-85399
Change-Id: I77f246331de7a6e9e6d4ba7d973730190138f136
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Alexandru Croitor 2020-08-14 17:53:37 +02:00
parent c1038019fc
commit 32121e9882
5 changed files with 270 additions and 241 deletions

View File

@ -76,6 +76,9 @@ if(NOT QT_BUILD_STANDALONE_TESTS)
#include CoreMacros() for qt6_generate_meta_types() #include CoreMacros() for qt6_generate_meta_types()
include(src/corelib/Qt6CoreMacros.cmake) include(src/corelib/Qt6CoreMacros.cmake)
# Needed when building qtbase for android.
include(src/corelib/Qt6AndroidMacros.cmake)
## Setup documentation ## Setup documentation
add_subdirectory(doc) add_subdirectory(doc)

View File

@ -138,246 +138,6 @@ define_property(TARGET
" " " "
) )
# Generate deployment tool json
function(qt_android_generate_deployment_settings target)
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
if (NOT TARGET ${target})
message(SEND_ERROR "${target} is not a cmake target")
return()
endif()
get_target_property(target_type ${target} TYPE)
if (NOT "${target_type}" STREQUAL "MODULE_LIBRARY")
message(SEND_ERROR "QT_ANDROID_GENERATE_DEPLOYMENT_SETTINGS only works on Module targets")
return()
endif()
get_target_property(target_source_dir ${target} SOURCE_DIR)
get_target_property(target_binary_dir ${target} BINARY_DIR)
get_target_property(target_output_name ${target} OUTPUT_NAME)
if (NOT target_output_name)
set(target_output_name ${target})
endif()
set(deploy_file "${target_binary_dir}/android-lib${target_output_name}.so-deployment-settings.json")
set(file_contents "{\n")
# content begin
string(APPEND file_contents
" \"description\": \"This file is generated by cmake to be read by androiddeployqt and should not be modified by hand.\",\n")
# Host Qt Android install path
if (NOT QT_BUILDING_QT OR QT_STANDALONE_TEST_PATH)
set(qt_path "${QT6_INSTALL_PREFIX}")
set(android_plugin_dir_path "${qt_path}/${QT6_INSTALL_PLUGINS}/platforms")
set(glob_expression "${android_plugin_dir_path}/*qtforandroid*${CMAKE_ANDROID_ARCH_ABI}.so")
file(GLOB plugin_dir_files LIST_DIRECTORIES FALSE "${glob_expression}")
if (NOT plugin_dir_files)
message(SEND_ERROR
"Detected Qt installation does not contain qtforandroid_${CMAKE_ANDROID_ARCH_ABI}.so in the following dir:
${android_plugin_dir_path}
This is most likely due to the installation not being a Qt for Android build.
Please recheck your build configuration.")
return()
else()
list(GET plugin_dir_files 0 android_platform_plugin_path)
message(STATUS "Found android platform plugin at: ${android_platform_plugin_path}")
endif()
set(qt_android_install_dir "${qt_path}")
else()
# Building from source, use the same install prefix.
set(qt_android_install_dir "${CMAKE_INSTALL_PREFIX}")
endif()
file(TO_NATIVE_PATH "${qt_android_install_dir}" qt_android_install_dir_native)
string(APPEND file_contents
" \"qt\": \"${qt_android_install_dir_native}\",\n")
# Android SDK path
file(TO_NATIVE_PATH "${ANDROID_SDK_ROOT}" android_sdk_root_native)
string(APPEND file_contents
" \"sdk\": \"${android_sdk_root_native}\",\n")
# Android SDK Build Tools Revision
string(APPEND file_contents
" \"sdkBuildToolsRevision\": \"${QT_ANDROID_SDK_BUILD_TOOLS_VERSION}\",\n")
# Android NDK
file(TO_NATIVE_PATH "${ANDROID_NDK}" android_ndk_root_native)
string(APPEND file_contents
" \"ndk\": \"${android_ndk_root_native}\",\n")
# Setup LLVM toolchain
string(APPEND file_contents
" \"toolchain-prefix\": \"llvm\",\n")
string(APPEND file_contents
" \"tool-prefix\": \"llvm\",\n")
string(APPEND file_contents
" \"useLLVM\": true,\n")
# NDK Toolchain Version
string(APPEND file_contents
" \"toolchain-version\": \"${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION}\",\n")
# NDK Host
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()
# Architecture
string(APPEND file_contents
" \"architectures\": { \"${CMAKE_ANDROID_ARCH_ABI}\" : \"${arch_value}\" },\n")
# deployment dependencies
get_target_property(android_deployment_dependencies
${target} QT_ANDROID_DEPLOYMENT_DEPENDENCIES)
if (android_deployment_dependencies)
list(JOIN android_deployment_dependencies "," android_deployment_dependencies)
string(APPEND file_contents
" \"deployment-dependencies\": \"${android_deployment_dependencies}\",\n")
endif()
# Extra plugins
get_target_property(android_extra_plugins
${target} QT_ANDROID_EXTRA_PLUGINS)
if (android_extra_plugins)
list(JOIN android_extra_plugins "," android_extra_plugins)
string(APPEND file_contents
" \"android-extra-plugins\": \"${android_extra_plugins}\",\n")
endif()
# Extra libs
get_target_property(android_extra_libs
${target} QT_ANDROID_EXTRA_LIBS)
if (android_extra_libs)
list(JOIN android_extra_libs "," android_extra_libs)
string(APPEND file_contents
" \"android-extra-libs\": \"${android_extra_libs}\",\n")
endif()
# package source dir
get_target_property(android_package_source_dir
${target} QT_ANDROID_PACKAGE_SOURCE_DIR)
if (android_package_source_dir)
file(TO_NATIVE_PATH "${android_package_source_dir}" android_package_source_dir_native)
string(APPEND file_contents
" \"android-package-source-directory\": \"${android_package_source_dir_native}\",\n")
endif()
#TODO: ANDROID_VERSION_NAME, doesn't seem to be used?
#TODO: ANDROID_VERSION_CODE, doesn't seem to be used?
get_target_property(qml_import_path ${target} QT_QML_IMPORT_PATH)
if (qml_import_path)
file(TO_NATIVE_PATH "${qml_import_path}" qml_import_path_native)
string(APPEND file_contents
" \"qml-import-path\": \"${qml_import_path_native}\",\n")
endif()
get_target_property(qml_root_path ${target} QT_QML_ROOT_PATH)
if(NOT qml_root_path)
set(qml_root_path "${target_source_dir}")
endif()
file(TO_NATIVE_PATH "${qml_root_path}" qml_root_path_native)
string(APPEND file_contents
" \"qml-root-path\": \"${qml_root_path_native}\",\n")
# App binary
string(APPEND file_contents
" \"application-binary\": \"${target_output_name}\",\n")
# Override qmlimportscanner binary path
set(qml_importscanner_binary_path "${QT_HOST_PATH}/bin/qmlimportscanner")
if (WIN32)
string(APPEND qml_importscanner_binary_path ".exe")
endif()
file(TO_NATIVE_PATH "${qml_importscanner_binary_path}" qml_importscanner_binary_path_native)
string(APPEND file_contents
" \"qml-importscanner-binary\" : \"${qml_importscanner_binary_path_native}\",\n")
# Last item in json file
# base location of stdlibc++, will be suffixed by androiddeploy qt
# Sysroot is set by Android toolchain file and is composed of ANDROID_TOOLCHAIN_ROOT.
set(android_ndk_stdlib_base_path "${CMAKE_SYSROOT}/usr/lib/")
string(APPEND file_contents
" \"stdcpp-path\": \"${android_ndk_stdlib_base_path}\"\n")
# content end
string(APPEND file_contents "}\n")
file(WRITE ${deploy_file} ${file_contents})
set_target_properties(${target}
PROPERTIES
QT_ANDROID_DEPLOYMENT_SETTINGS_FILE ${deploy_file}
)
endfunction()
function(qt_android_apply_arch_suffix target)
get_target_property(target_type ${target} TYPE)
if (target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY")
set_property(TARGET "${target}" PROPERTY SUFFIX "_${CMAKE_ANDROID_ARCH_ABI}.so")
elseif (target_type STREQUAL "STATIC_LIBRARY")
set_property(TARGET "${target}" PROPERTY SUFFIX "_${CMAKE_ANDROID_ARCH_ABI}.a")
endif()
endfunction()
# Add custom target to package the APK
function(qt_android_add_apk_target target)
get_target_property(deployment_file ${target} QT_ANDROID_DEPLOYMENT_SETTINGS_FILE)
if (NOT deployment_file)
message(FATAL_ERROR "Target ${target} is not a valid android executable target\n")
endif()
# Create a top-level "apk" target for convenience, so that users can call 'ninja apk'.
# It will trigger building all the target specific apk build targets that are added via this
# function.
# Allow opt-out.
if(NOT QT_NO_GLOBAL_APK_TARGET)
if(NOT TARGET apk)
add_custom_target(apk
DEPENDS ${target}_prepare_apk_dir
COMMENT "Building all apks"
)
endif()
set(should_add_to_global_apk TRUE)
endif()
set(deployment_tool "${QT_HOST_PATH}/bin/androiddeployqt")
set(apk_dir "$<TARGET_PROPERTY:${target},BINARY_DIR>/android-build")
add_custom_target(${target}_prepare_apk_dir
DEPENDS ${target}
COMMAND ${CMAKE_COMMAND}
-E copy $<TARGET_FILE:${target}>
"${apk_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
COMMENT "Copying ${target} binarty to apk folder"
)
add_custom_target(${target}_make_apk
DEPENDS ${target}_prepare_apk_dir
COMMAND ${deployment_tool}
--input ${deployment_file}
--output ${apk_dir}
COMMENT "Creating APK for ${target}"
)
if(should_add_to_global_apk)
add_dependencies(apk "${target}_make_apk")
endif()
endfunction()
# Add a test for Android which will be run by the android test runner tool # Add a test for Android which will be run by the android test runner tool
function(qt_android_add_test target) function(qt_android_add_test target)

View File

@ -16,6 +16,10 @@ endif()
file(RELATIVE_PATH QT_INVERSE_CONFIG_INSTALL_DIR file(RELATIVE_PATH QT_INVERSE_CONFIG_INSTALL_DIR
"${_clean_prefix}" "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}") "${_clean_prefix}" "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}")
if(ANDROID)
set(corelib_extra_cmake_files
"${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}AndroidMacros.cmake")
endif()
# special case end # special case end
##################################################################### #####################################################################
@ -257,7 +261,9 @@ qt_add_module(Core
Qt::Platform # special case: Qt::Platform # special case:
# special case begin # special case begin
# Generated in QtBaseGlobalTargets # Generated in QtBaseGlobalTargets
EXTRA_CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CTestMacros.cmake" EXTRA_CMAKE_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/Qt6CTestMacros.cmake"
${corelib_extra_cmake_files}
# special case end # special case end
) )

View File

@ -0,0 +1,256 @@
# Generate deployment tool json
function(qt6_android_generate_deployment_settings target)
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
if (NOT TARGET ${target})
message(SEND_ERROR "${target} is not a cmake target")
return()
endif()
get_target_property(target_type ${target} TYPE)
if (NOT "${target_type}" STREQUAL "MODULE_LIBRARY")
message(SEND_ERROR "QT_ANDROID_GENERATE_DEPLOYMENT_SETTINGS only works on Module targets")
return()
endif()
get_target_property(target_source_dir ${target} SOURCE_DIR)
get_target_property(target_binary_dir ${target} BINARY_DIR)
get_target_property(target_output_name ${target} OUTPUT_NAME)
if (NOT target_output_name)
set(target_output_name ${target})
endif()
set(deploy_file "${target_binary_dir}/android-lib${target_output_name}.so-deployment-settings.json")
set(file_contents "{\n")
# content begin
string(APPEND file_contents
" \"description\": \"This file is generated by cmake to be read by androiddeployqt and should not be modified by hand.\",\n")
# Host Qt Android install path
if (NOT QT_BUILDING_QT OR QT_STANDALONE_TEST_PATH)
set(qt_path "${QT6_INSTALL_PREFIX}")
set(android_plugin_dir_path "${qt_path}/${QT6_INSTALL_PLUGINS}/platforms")
set(glob_expression "${android_plugin_dir_path}/*qtforandroid*${CMAKE_ANDROID_ARCH_ABI}.so")
file(GLOB plugin_dir_files LIST_DIRECTORIES FALSE "${glob_expression}")
if (NOT plugin_dir_files)
message(SEND_ERROR
"Detected Qt installation does not contain qtforandroid_${CMAKE_ANDROID_ARCH_ABI}.so in the following dir:
${android_plugin_dir_path}
This is most likely due to the installation not being a Qt for Android build.
Please recheck your build configuration.")
return()
else()
list(GET plugin_dir_files 0 android_platform_plugin_path)
message(STATUS "Found android platform plugin at: ${android_platform_plugin_path}")
endif()
set(qt_android_install_dir "${qt_path}")
else()
# Building from source, use the same install prefix.
set(qt_android_install_dir "${CMAKE_INSTALL_PREFIX}")
endif()
file(TO_NATIVE_PATH "${qt_android_install_dir}" qt_android_install_dir_native)
string(APPEND file_contents
" \"qt\": \"${qt_android_install_dir_native}\",\n")
# Android SDK path
file(TO_NATIVE_PATH "${ANDROID_SDK_ROOT}" android_sdk_root_native)
string(APPEND file_contents
" \"sdk\": \"${android_sdk_root_native}\",\n")
# Android SDK Build Tools Revision
string(APPEND file_contents
" \"sdkBuildToolsRevision\": \"${QT_ANDROID_SDK_BUILD_TOOLS_VERSION}\",\n")
# Android NDK
file(TO_NATIVE_PATH "${ANDROID_NDK}" android_ndk_root_native)
string(APPEND file_contents
" \"ndk\": \"${android_ndk_root_native}\",\n")
# Setup LLVM toolchain
string(APPEND file_contents
" \"toolchain-prefix\": \"llvm\",\n")
string(APPEND file_contents
" \"tool-prefix\": \"llvm\",\n")
string(APPEND file_contents
" \"useLLVM\": true,\n")
# NDK Toolchain Version
string(APPEND file_contents
" \"toolchain-version\": \"${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION}\",\n")
# NDK Host
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()
# Architecture
string(APPEND file_contents
" \"architectures\": { \"${CMAKE_ANDROID_ARCH_ABI}\" : \"${arch_value}\" },\n")
# deployment dependencies
get_target_property(android_deployment_dependencies
${target} QT_ANDROID_DEPLOYMENT_DEPENDENCIES)
if (android_deployment_dependencies)
list(JOIN android_deployment_dependencies "," android_deployment_dependencies)
string(APPEND file_contents
" \"deployment-dependencies\": \"${android_deployment_dependencies}\",\n")
endif()
# Extra plugins
get_target_property(android_extra_plugins
${target} QT_ANDROID_EXTRA_PLUGINS)
if (android_extra_plugins)
list(JOIN android_extra_plugins "," android_extra_plugins)
string(APPEND file_contents
" \"android-extra-plugins\": \"${android_extra_plugins}\",\n")
endif()
# Extra libs
get_target_property(android_extra_libs
${target} QT_ANDROID_EXTRA_LIBS)
if (android_extra_libs)
list(JOIN android_extra_libs "," android_extra_libs)
string(APPEND file_contents
" \"android-extra-libs\": \"${android_extra_libs}\",\n")
endif()
# package source dir
get_target_property(android_package_source_dir
${target} QT_ANDROID_PACKAGE_SOURCE_DIR)
if (android_package_source_dir)
file(TO_NATIVE_PATH "${android_package_source_dir}" android_package_source_dir_native)
string(APPEND file_contents
" \"android-package-source-directory\": \"${android_package_source_dir_native}\",\n")
endif()
#TODO: ANDROID_VERSION_NAME, doesn't seem to be used?
#TODO: ANDROID_VERSION_CODE, doesn't seem to be used?
get_target_property(qml_import_path ${target} QT_QML_IMPORT_PATH)
if (qml_import_path)
file(TO_NATIVE_PATH "${qml_import_path}" qml_import_path_native)
string(APPEND file_contents
" \"qml-import-path\": \"${qml_import_path_native}\",\n")
endif()
get_target_property(qml_root_path ${target} QT_QML_ROOT_PATH)
if(NOT qml_root_path)
set(qml_root_path "${target_source_dir}")
endif()
file(TO_NATIVE_PATH "${qml_root_path}" qml_root_path_native)
string(APPEND file_contents
" \"qml-root-path\": \"${qml_root_path_native}\",\n")
# App binary
string(APPEND file_contents
" \"application-binary\": \"${target_output_name}\",\n")
# Override qmlimportscanner binary path
set(qml_importscanner_binary_path "${QT_HOST_PATH}/bin/qmlimportscanner")
if (WIN32)
string(APPEND qml_importscanner_binary_path ".exe")
endif()
file(TO_NATIVE_PATH "${qml_importscanner_binary_path}" qml_importscanner_binary_path_native)
string(APPEND file_contents
" \"qml-importscanner-binary\" : \"${qml_importscanner_binary_path_native}\",\n")
# Last item in json file
# base location of stdlibc++, will be suffixed by androiddeploy qt
# Sysroot is set by Android toolchain file and is composed of ANDROID_TOOLCHAIN_ROOT.
set(android_ndk_stdlib_base_path "${CMAKE_SYSROOT}/usr/lib/")
string(APPEND file_contents
" \"stdcpp-path\": \"${android_ndk_stdlib_base_path}\"\n")
# content end
string(APPEND file_contents "}\n")
file(WRITE ${deploy_file} ${file_contents})
set_target_properties(${target}
PROPERTIES
QT_ANDROID_DEPLOYMENT_SETTINGS_FILE ${deploy_file}
)
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_android_generate_deployment_settings)
qt6_android_generate_deployment_settings(${ARGV})
endfunction()
endif()
function(qt6_android_apply_arch_suffix target)
get_target_property(target_type ${target} TYPE)
if (target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY")
set_property(TARGET "${target}" PROPERTY SUFFIX "_${CMAKE_ANDROID_ARCH_ABI}.so")
elseif (target_type STREQUAL "STATIC_LIBRARY")
set_property(TARGET "${target}" PROPERTY SUFFIX "_${CMAKE_ANDROID_ARCH_ABI}.a")
endif()
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_android_apply_arch_suffix)
qt6_android_apply_arch_suffix(${ARGV})
endfunction()
endif()
# Add custom target to package the APK
function(qt6_android_add_apk_target target)
get_target_property(deployment_file ${target} QT_ANDROID_DEPLOYMENT_SETTINGS_FILE)
if (NOT deployment_file)
message(FATAL_ERROR "Target ${target} is not a valid android executable target\n")
endif()
# Create a top-level "apk" target for convenience, so that users can call 'ninja apk'.
# It will trigger building all the target specific apk build targets that are added via this
# function.
# Allow opt-out.
if(NOT QT_NO_GLOBAL_APK_TARGET)
if(NOT TARGET apk)
add_custom_target(apk
DEPENDS ${target}_prepare_apk_dir
COMMENT "Building all apks"
)
endif()
set(should_add_to_global_apk TRUE)
endif()
set(deployment_tool "${QT_HOST_PATH}/bin/androiddeployqt")
set(apk_dir "$<TARGET_PROPERTY:${target},BINARY_DIR>/android-build")
add_custom_target(${target}_prepare_apk_dir
DEPENDS ${target}
COMMAND ${CMAKE_COMMAND}
-E copy $<TARGET_FILE:${target}>
"${apk_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
COMMENT "Copying ${target} binarty to apk folder"
)
add_custom_target(${target}_make_apk
DEPENDS ${target}_prepare_apk_dir
COMMAND ${deployment_tool}
--input ${deployment_file}
--output ${apk_dir}
COMMENT "Creating APK for ${target}"
)
if(should_add_to_global_apk)
add_dependencies(apk "${target}_make_apk")
endif()
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_android_add_apk_target)
qt6_android_add_apk_target(${ARGV})
endfunction()
endif()

View File

@ -46,3 +46,7 @@ set(QT@PROJECT_VERSION_MAJOR@_IS_SHARED_LIBS_BUILD "@BUILD_SHARED_LIBS@")
get_filename_component(_Qt6CoreConfigDir ${CMAKE_CURRENT_LIST_FILE} PATH) get_filename_component(_Qt6CoreConfigDir ${CMAKE_CURRENT_LIST_FILE} PATH)
set(_Qt6CTestMacros "${_Qt6CoreConfigDir}/Qt6CTestMacros.cmake") set(_Qt6CTestMacros "${_Qt6CoreConfigDir}/Qt6CTestMacros.cmake")
if(ANDROID_PLATFORM)
include("${CMAKE_CURRENT_LIST_DIR}/@QT_CMAKE_EXPORT_NAMESPACE@AndroidMacros.cmake")
endif()