# Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause # Generate deployment tool json # Locate newest Android sdk build tools revision function(_qt_internal_android_get_sdk_build_tools_revision out_var) if (NOT QT_ANDROID_SDK_BUILD_TOOLS_REVISION) file(GLOB android_build_tools LIST_DIRECTORIES true RELATIVE "${ANDROID_SDK_ROOT}/build-tools" "${ANDROID_SDK_ROOT}/build-tools/*") if (NOT android_build_tools) message(FATAL_ERROR "Could not locate Android SDK build tools under \"${ANDROID_SDK_ROOT}/build-tools\"") endif() list(SORT android_build_tools) list(REVERSE android_build_tools) list(GET android_build_tools 0 android_build_tools_latest) endif() set(${out_var} "${android_build_tools_latest}" PARENT_SCOPE) endfunction() # The function appends to the 'out_var' a 'json_property' that contains the 'tool' path. If 'tool' # target or its IMPORTED_LOCATION are not found the function displays warning, but is not failing # at the project configuring phase. function(_qt_internal_add_tool_to_android_deployment_settings out_var tool json_property target) unset(tool_binary_path) __qt_internal_get_tool_imported_location(tool_binary_path ${tool}) if("${tool_binary_path}" STREQUAL "") # Fallback search for the tool in host bin and host libexec directories find_program(tool_binary_path NAMES ${tool} ${tool}.exe PATHS "${QT_HOST_PATH}/${QT6_HOST_INFO_BINDIR}" "${QT_HOST_PATH}/${QT6_HOST_INFO_LIBEXECDIR}" NO_DEFAULT_PATH ) if(NOT tool_binary_path) message(WARNING "Unable to locate ${tool}. Android package deployment of ${target}" " target can be incomplete. Make sure the host Qt has ${tool} installed.") return() endif() endif() file(TO_CMAKE_PATH "${tool_binary_path}" tool_binary_path) string(APPEND ${out_var} " \"${json_property}\" : \"${tool_binary_path}\",\n") set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() # Generate the deployment settings json file for a cmake target. function(qt6_android_generate_deployment_settings target) # Information extracted from mkspecs/features/android/android_deployment_settings.prf if (NOT TARGET ${target}) message(FATAL_ERROR "${target} is not a cmake target") endif() # When parsing JSON file format backslashes and follow up symbols are regarded as special # characters. This puts Windows path format into a trouble. # _qt_internal_android_format_deployment_paths converts sensitive paths to the CMake format # that is supported by JSON as well. The function should be called as many times as # qt6_android_generate_deployment_settings, because users may change properties that contain # paths in between the calls. _qt_internal_android_format_deployment_paths(${target}) # Avoid calling the function body twice because of 'file(GENERATE'. get_target_property(is_called ${target} _qt_is_android_generate_deployment_settings_called) if(is_called) return() endif() set_target_properties(${target} PROPERTIES _qt_is_android_generate_deployment_settings_called TRUE ) get_target_property(android_executable_finalizer_called ${target} _qt_android_executable_finalizer_called) if(android_executable_finalizer_called) # Don't show deprecation when called by our own function implementations. else() message(DEPRECATION "Calling qt_android_generate_deployment_settings directly is deprecated since Qt 6.5. " "Use qt_add_executable instead.") 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() # QtCreator requires the file name of deployment settings has no config related suffixes # to run androiddeployqt correctly. If we use multi-config generator for the first config # in a list avoid adding any configuration-specific suffixes. get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) if(is_multi_config) list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) set(config_suffix "$<$>:-$>") endif() set(deploy_file "${target_binary_dir}/android-${target}-deployment-settings${config_suffix}.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:\n" "${android_plugin_dir_path}\n" "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() endif() _qt_internal_collect_qt_for_android_paths(file_contents) # Android SDK path file(TO_CMAKE_PATH "${ANDROID_SDK_ROOT}" android_sdk_root_native) string(APPEND file_contents " \"sdk\": \"${android_sdk_root_native}\",\n") # Android SDK Build Tools Revision _qt_internal_android_get_sdk_build_tools_revision(android_sdk_build_tools) set(android_sdk_build_tools_genex "") string(APPEND android_sdk_build_tools_genex "$>," "$," "${android_sdk_build_tools}" ">" ) string(APPEND file_contents " \"sdkBuildToolsRevision\": \"${android_sdk_build_tools_genex}\",\n") # Android NDK file(TO_CMAKE_PATH "${CMAKE_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") get_target_property(qt_android_abis ${target} _qt_android_abis) if(NOT qt_android_abis) set(qt_android_abis "") 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\": { ${architecture_records} },\n") # deployment dependencies _qt_internal_add_android_deployment_multi_value_property(file_contents "deployment-dependencies" ${target} "QT_ANDROID_DEPLOYMENT_DEPENDENCIES" ) # Extra plugins _qt_internal_add_android_deployment_multi_value_property(file_contents "android-extra-plugins" ${target} "_qt_android_native_extra_plugins" ) # Extra libs _qt_internal_add_android_deployment_multi_value_property(file_contents "android-extra-libs" ${target} "_qt_android_native_extra_libs" ) # Alternative path to Qt libraries on target device _qt_internal_add_android_deployment_property(file_contents "android-system-libs-prefix" ${target} "QT_ANDROID_SYSTEM_LIBS_PREFIX") # package source dir _qt_internal_add_android_deployment_property(file_contents "android-package-source-directory" ${target} "_qt_android_native_package_source_dir") # version code _qt_internal_add_android_deployment_property(file_contents "android-version-code" ${target} "QT_ANDROID_VERSION_CODE") # version name _qt_internal_add_android_deployment_property(file_contents "android-version-name" ${target} "QT_ANDROID_VERSION_NAME") # minimum SDK version _qt_internal_add_android_deployment_property(file_contents "android-min-sdk-version" ${target} "QT_ANDROID_MIN_SDK_VERSION") # target SDK version _qt_internal_add_android_deployment_property(file_contents "android-target-sdk-version" ${target} "QT_ANDROID_TARGET_SDK_VERSION") # should Qt shared libs be excluded from deployment _qt_internal_add_android_deployment_property(file_contents "android-no-deploy-qt-libs" ${target} "QT_ANDROID_NO_DEPLOY_QT_LIBS") __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets) __qt_internal_collect_plugin_library_files("${target}" "${plugin_targets}" plugin_targets) string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n") # App binary string(APPEND file_contents " \"application-binary\": \"${target_output_name}\",\n") # App command-line arguments if (QT_ANDROID_APPLICATION_ARGUMENTS) string(APPEND file_contents " \"android-application-arguments\": \"${QT_ANDROID_APPLICATION_ARGUMENTS}\",\n") endif() if(COMMAND _qt_internal_generate_android_qml_deployment_settings) _qt_internal_generate_android_qml_deployment_settings(file_contents ${target}) else() string(APPEND file_contents " \"qml-skip-import-scanning\": true,\n") endif() # Override rcc binary path _qt_internal_add_tool_to_android_deployment_settings(file_contents rcc "rcc-binary" "${target}") # Extra prefix paths foreach(prefix IN LISTS CMAKE_FIND_ROOT_PATH) if (NOT "${prefix}" STREQUAL "${qt_android_install_dir_native}" AND NOT "${prefix}" STREQUAL "${android_ndk_root_native}") file(TO_CMAKE_PATH "${prefix}" prefix) list(APPEND extra_prefix_list "\"${prefix}\"") endif() endforeach() string (REPLACE ";" "," extra_prefix_list "${extra_prefix_list}") string(APPEND file_contents " \"extraPrefixDirs\" : [ ${extra_prefix_list} ],\n") # Create an empty target for the cases when we need to generate deployment setting but # qt_finalize_project is never called. if(NOT TARGET _qt_internal_apk_dependencies AND NOT QT_NO_COLLECT_BUILD_TREE_APK_DEPS) add_custom_target(_qt_internal_apk_dependencies) endif() # Extra library paths that could be used as a dependency lookup path by androiddeployqt. # # Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder # when looking for dependencies. # TODO: add a public target property accessible from user space _qt_internal_add_android_deployment_list_property(file_contents "extraLibraryDirs" ${target} "_qt_android_extra_library_dirs" _qt_internal_apk_dependencies "_qt_android_extra_library_dirs" ) if(QT_FEATURE_zstd) set(is_zstd_enabled "true") else() set(is_zstd_enabled "false") endif() string(APPEND file_contents " \"zstdCompression\": ${is_zstd_enabled},\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(GENERATE OUTPUT ${deploy_file} CONTENT "${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(called_from_qt_impl ${target} _qt_android_apply_arch_suffix_called_from_qt_impl) if(called_from_qt_impl) # Don't show deprecation when called by our own function implementations. else() message(DEPRECATION "Calling qt_android_apply_arch_suffix directly is deprecated since Qt 6.5. " "Use qt_add_executable or qt_add_library instead.") endif() 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) # Avoid calling qt6_android_add_apk_target twice get_property(apk_targets GLOBAL PROPERTY _qt_apk_targets) if("${target}" IN_LIST apk_targets) return() endif() get_target_property(android_executable_finalizer_called ${target} _qt_android_executable_finalizer_called) if(android_executable_finalizer_called) # Don't show deprecation when called by our own function implementations. else() message(DEPRECATION "Calling qt_android_add_apk_target directly is deprecated since Qt 6.5. " "Use qt_add_executable instead.") endif() 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() # Use genex to get path to the deployment settings, the above check only to confirm that # qt6_android_add_apk_target is called on an android executable target. string(JOIN "" deployment_file "$" ">" ) # Make global apk and aab targets depend on the current apk target. if(TARGET aab) add_dependencies(aab ${target}_make_aab) endif() if(TARGET apk) add_dependencies(apk ${target}_make_apk) _qt_internal_create_global_apk_all_target_if_needed() endif() set(deployment_tool "${QT_HOST_PATH}/${QT6_HOST_INFO_BINDIR}/androiddeployqt") # No need to use genex for the BINARY_DIR since it's read-only. get_target_property(target_binary_dir ${target} BINARY_DIR) if($CACHE{QT_USE_TARGET_ANDROID_BUILD_DIR}) set(apk_final_dir "${target_binary_dir}/android-build-${target}") else() if(QT_USE_TARGET_ANDROID_BUILD_DIR) message(WARNING "QT_USE_TARGET_ANDROID_BUILD_DIR needs to be set in CACHE") endif() get_property(known_android_build GLOBAL PROPERTY _qt_internal_known_android_build_dir) get_property(already_warned GLOBAL PROPERTY _qt_internal_already_warned_android_build_dir) set(apk_final_dir "${target_binary_dir}/android-build") if(NOT QT_SKIP_ANDROID_BUILD_DIR_CHECK AND "${apk_final_dir}" IN_LIST known_android_build AND NOT "${apk_final_dir}" IN_LIST already_warned) message(WARNING "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt contains multiple" " Qt Android executable targets. This can lead to mixing of deployment artifacts" " of targets defined there. Setting QT_USE_TARGET_ANDROID_BUILD_DIR=TRUE" " allows building multiple executable targets within a single CMakeLists.txt." " Note: This option is not supported by Qt Creator versions older than 13." " Set QT_SKIP_ANDROID_BUILD_DIR_CHECK=TRUE to suppress this warning." ) set_property(GLOBAL APPEND PROPERTY _qt_internal_already_warned_android_build_dir "${apk_final_dir}") else() set_property(GLOBAL APPEND PROPERTY _qt_internal_known_android_build_dir "${apk_final_dir}") endif() endif() set(apk_file_name "${target}.apk") set(dep_file_name "${target}.d") set(apk_final_file_path "${apk_final_dir}/${apk_file_name}") set(dep_file_path "${apk_final_dir}/${dep_file_name}") set(target_file_copy_relative_path "libs/${CMAKE_ANDROID_ARCH_ABI}/$") set(extra_deps "") if(QT_ENABLE_VERBOSE_DEPLOYMENT) set(uses_terminal USES_TERMINAL) endif() # Plugins still might be added after creating the deployment targets. if(NOT TARGET qt_internal_plugins) add_custom_target(qt_internal_plugins) endif() # Before running androiddeployqt, we need to make sure all plugins are built. list(APPEND extra_deps qt_internal_plugins) # 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. _qt_internal_copy_file_if_different_command(copy_command "$" "${apk_final_dir}/${target_file_copy_relative_path}" ) add_custom_target(${target}_prepare_apk_dir ALL DEPENDS ${target} ${extra_deps} COMMAND ${copy_command} COMMENT "Copying ${target} binary to apk folder" ${uses_terminal} ) set(sign_apk "") if(QT_ANDROID_SIGN_APK) set(sign_apk "--sign") endif() set(sign_aab "") if(QT_ANDROID_SIGN_AAB) set(sign_aab "--sign") endif() set(extra_args "") if(QT_INTERNAL_NO_ANDROID_RCC_BUNDLE_CLEANUP) list(APPEND extra_args "--no-rcc-bundle-cleanup") endif() if(QT_ENABLE_VERBOSE_DEPLOYMENT) list(APPEND extra_args "--verbose") endif() if(QT_ANDROID_DEPLOY_RELEASE) message(WARNING "QT_ANDROID_DEPLOY_RELEASE is not a valid Qt variable." " Please set QT_ANDROID_DEPLOYMENT_TYPE to RELEASE instead.") endif() # Setting QT_ANDROID_DEPLOYMENT_TYPE to a value other than Release disables # release package signing regardless of the build type. if(QT_ANDROID_DEPLOYMENT_TYPE) string(TOUPPER "${QT_ANDROID_DEPLOYMENT_TYPE}" deployment_type_upper) if("${deployment_type_upper}" STREQUAL "RELEASE") list(APPEND extra_args "--release") endif() elseif(NOT QT_BUILD_TESTS) # Workaround for tests: do not set automatically --release flag if QT_BUILD_TESTS is set. # Release package need to be signed. Signing is currently not supported by CI. # What is more, also androidtestrunner is not working on release APKs, # For example running "adb shell run-as" on release APK will finish with the error: # run-as: Package '[PACKAGE-NAME]' is not debuggable list(APPEND extra_args $<$,$,$>:--release>) endif() _qt_internal_check_depfile_support(has_depfile_support) if(has_depfile_support) cmake_policy(PUSH) if(POLICY CMP0116) # Without explicitly setting this policy to NEW, we get a warning # even though we ensure there's actually no problem here. # See https://gitlab.kitware.com/cmake/cmake/-/issues/21959 cmake_policy(SET CMP0116 NEW) set(relative_to_dir ${CMAKE_CURRENT_BINARY_DIR}) else() set(relative_to_dir ${CMAKE_BINARY_DIR}) endif() # Add custom command that creates the apk and triggers rebuild if files listed in # ${dep_file_path} are changed. add_custom_command(OUTPUT "${apk_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 "${apk_final_file_path}" --depfile "${dep_file_path}" --builddir "${relative_to_dir}" ${extra_args} ${sign_apk} COMMENT "Creating APK 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}") else() add_custom_target(${target}_make_apk DEPENDS ${target}_prepare_apk_dir COMMAND ${deployment_tool} --input ${deployment_file} --output ${apk_final_dir} --apk ${apk_final_file_path} ${extra_args} ${sign_apk} COMMENT "Creating APK for ${target}" VERBATIM ${uses_terminal} ) endif() # Add target triggering AAB creation. Since the _make_aab target is not added to the ALL # set, we may avoid dependency check for it and admit that the target is "always out # of date". add_custom_target(${target}_make_aab DEPENDS ${target}_prepare_apk_dir COMMAND ${deployment_tool} --input ${deployment_file} --output ${apk_final_dir} --apk ${apk_final_file_path} --aab ${sign_aab} ${extra_args} COMMENT "Creating AAB for ${target}" ${uses_terminal} ) if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT) # When building per-ABI external projects we only need to copy ABI-specific libraries and # resources to the "main" ABI android build folder. if("${QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR}" STREQUAL "") message(FATAL_ERROR "QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR is not set when building" " ABI specific external project. This should not happen and might mean an issue" " in Qt. Please report a bug with CMake traces attached.") endif() # Assume that external project mirrors build structure of the top-level ABI project and # replace the build root when specifying the output directory of androiddeployqt. file(RELATIVE_PATH androiddeployqt_output_path "${CMAKE_BINARY_DIR}" "${apk_final_dir}") set(androiddeployqt_output_path "${QT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR}/${androiddeployqt_output_path}") _qt_internal_copy_file_if_different_command(copy_command "$" "${androiddeployqt_output_path}/${target_file_copy_relative_path}" ) if(has_depfile_support) set(deploy_android_deps_dir "${apk_final_dir}/${target}_deploy_android") set(timestamp_file "${deploy_android_deps_dir}/timestamp") set(dep_file "${deploy_android_deps_dir}/${target}.d") add_custom_command(OUTPUT "${timestamp_file}" DEPENDS ${target} ${extra_deps} COMMAND ${CMAKE_COMMAND} -E make_directory "${deploy_android_deps_dir}" COMMAND ${CMAKE_COMMAND} -E touch "${timestamp_file}" COMMAND ${copy_command} COMMAND ${deployment_tool} --input ${deployment_file} --output ${androiddeployqt_output_path} --copy-dependencies-only ${extra_args} --depfile "${dep_file}" --builddir "${CMAKE_BINARY_DIR}" COMMENT "Resolving ${CMAKE_ANDROID_ARCH_ABI} dependencies for the ${target} APK" DEPFILE "${dep_file}" VERBATIM ${uses_terminal} ) add_custom_target(qt_internal_${target}_copy_apk_dependencies DEPENDS "${timestamp_file}") else() add_custom_target(qt_internal_${target}_copy_apk_dependencies DEPENDS ${target} ${extra_deps} COMMAND ${copy_command} COMMAND ${deployment_tool} --input ${deployment_file} --output ${androiddeployqt_output_path} --copy-dependencies-only ${extra_args} COMMENT "Resolving ${CMAKE_ANDROID_ARCH_ABI} dependencies for the ${target} APK" ${uses_terminal} ) endif() endif() set_property(GLOBAL APPEND PROPERTY _qt_apk_targets ${target}) _qt_internal_collect_apk_dependencies_defer() _qt_internal_collect_apk_imported_dependencies_defer("${target}") endfunction() function(_qt_internal_create_global_android_targets) macro(_qt_internal_create_global_android_targets_impl target) string(TOUPPER "${target}" target_upper) if(NOT QT_NO_GLOBAL_${target_upper}_TARGET) if(NOT TARGET ${target}) add_custom_target(${target} COMMENT "Building all apks") endif() endif() endmacro() # Create a top-level "apk" target for convenience, so that users can call 'ninja apk'. # 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(apk) # Create a top-level "aab" target for convenience, so that users can call 'ninja aab'. # 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) endfunction() # The function collects all known non-imported shared libraries that are created in the build tree. # It uses the CMake DEFER CALL feature if the CMAKE_VERSION is greater # than or equal to 3.19. # Note: Users that use cmake version less that 3.19 need to call qt_finalize_project # in the end of a project's top-level CMakeLists.txt. function(_qt_internal_collect_apk_dependencies_defer) # User opted-out the functionality if(QT_NO_COLLECT_BUILD_TREE_APK_DEPS) return() endif() get_property(is_called GLOBAL PROPERTY _qt_is_collect_apk_dependencies_defer_called) if(is_called) # Already scheduled return() endif() set_property(GLOBAL PROPERTY _qt_is_collect_apk_dependencies_defer_called TRUE) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") cmake_language(EVAL CODE "cmake_language(DEFER DIRECTORY \"${CMAKE_SOURCE_DIR}\" CALL _qt_internal_collect_apk_dependencies)") else() # User don't want to see the warning if(NOT QT_NO_WARN_BUILD_TREE_APK_DEPS) message(WARNING "The CMake version you use is less than 3.19. APK dependencies, that are a" " part of the project tree, might not be collected correctly." " Please call qt_finalize_project in the end of a project's top-level" " CMakeLists.txt file to make sure that all the APK dependencies are" " collected correctly." " You can pass -DQT_NO_WARN_BUILD_TREE_APK_DEPS=ON when configuring the project" " to silence the warning.") endif() endif() endfunction() # The function collects project-built shared libraries that might be dependencies for # the main apk targets. It stores their locations in a global custom target property. function(_qt_internal_collect_apk_dependencies) # User opted-out the functionality if(QT_NO_COLLECT_BUILD_TREE_APK_DEPS) return() endif() get_property(is_called GLOBAL PROPERTY _qt_is_collect_apk_dependencies_called) if(is_called) return() endif() set_property(GLOBAL PROPERTY _qt_is_collect_apk_dependencies_called TRUE) get_property(apk_targets GLOBAL PROPERTY _qt_apk_targets) _qt_internal_collect_buildsystem_targets(libs "${CMAKE_SOURCE_DIR}" INCLUDE SHARED_LIBRARY MODULE_LIBRARY) list(REMOVE_DUPLICATES libs) if(NOT TARGET qt_internal_plugins) add_custom_target(qt_internal_plugins) endif() foreach(lib IN LISTS libs) if(NOT lib IN_LIST apk_targets) list(APPEND extra_library_dirs "$") get_target_property(target_type ${lib} TYPE) # We collect all MODULE_LIBRARY targets since target APK may have implicit dependency # to the plugin that will cause the runtime issue. Plugins that were added using # qt6_add_plugin should be already added to the qt_internal_plugins dependency list, # but it's ok to re-add them. if(target_type STREQUAL "MODULE_LIBRARY") add_dependencies(qt_internal_plugins ${lib}) endif() endif() endforeach() if(NOT TARGET _qt_internal_apk_dependencies) add_custom_target(_qt_internal_apk_dependencies) endif() set_target_properties(_qt_internal_apk_dependencies PROPERTIES _qt_android_extra_library_dirs "${extra_library_dirs}" ) endfunction() # This function collects all imported shared libraries that might be dependencies for # the main apk targets. The actual collection is deferred until the target's directory scope # is processed. # The function requires CMake 3.21 or later. function(_qt_internal_collect_apk_imported_dependencies_defer target) # User opted-out of the functionality. if(QT_NO_COLLECT_IMPORTED_TARGET_APK_DEPS) return() endif() get_target_property(target_source_dir "${target}" SOURCE_DIR) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.21") cmake_language(EVAL CODE "cmake_language(DEFER DIRECTORY \"${target_source_dir}\" CALL _qt_internal_collect_apk_imported_dependencies \"${target}\")") endif() endfunction() # This function collects imported shared libraries that might be dependencies for # the main apk targets. It stores their locations on a custom target property for the given target. # The function requires CMake 3.21 or later. function(_qt_internal_collect_apk_imported_dependencies target) # User opted-out the functionality if(QT_NO_COLLECT_IMPORTED_TARGET_APK_DEPS) return() endif() get_target_property(target_source_dir "${target}" SOURCE_DIR) _qt_internal_collect_imported_shared_libraries_recursive(libs "${target_source_dir}") list(REMOVE_DUPLICATES libs) foreach(lib IN LISTS libs) list(APPEND extra_library_dirs "$") endforeach() set_property(TARGET "${target}" APPEND PROPERTY _qt_android_extra_library_dirs "${extra_library_dirs}" ) endfunction() # This function recursively walks the current directory and its parent directories to collect # imported shared library targets. # The recursion goes upwards instead of downwards because imported targets are usually not global, # and we can't call get_target_property() on a target which is not available in the current # directory or parent scopes. # We also can't cache parent directories because the imported targets in a parent directory # might change in-between collection calls. # The function requires CMake 3.21 or later. function(_qt_internal_collect_imported_shared_libraries_recursive out_var subdir) set(result "") get_directory_property(imported_targets DIRECTORY "${subdir}" IMPORTED_TARGETS) foreach(imported_target IN LISTS imported_targets) get_target_property(target_type "${imported_target}" TYPE) if(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY") # If the target has the _qt_package_version property set, it means it's an # 'official' qt target like a module or plugin, so we don't want to add it # to the list of extra paths to scan for in androiddeployqt, because they are # already handled via the regular 'qt' code path in the androiddeployqt. # Thus this will pick up only non-qt 3rd party targets. get_target_property(qt_package_version "${imported_target}" _qt_package_version) if(NOT qt_package_version) list(APPEND result "${imported_target}") endif() endif() endforeach() get_directory_property(parent_dir DIRECTORY "${subdir}" PARENT_DIRECTORY) if(parent_dir) _qt_internal_collect_imported_shared_libraries_recursive(result_inner "${parent_dir}") endif() list(APPEND result ${result_inner}) set(${out_var} "${result}" PARENT_SCOPE) endfunction() # This function allows deciding whether apks should be built as part of the ALL target at first # add_executable call point, rather than when the 'apk' target is created as part of the # find_package(Core) call. # # It does so by creating a custom 'apk_all' target as an implementation detail. # # This is needed to ensure that the decision is made only when the value of QT_BUILDING_QT is # available, which is defined in qt_repo_build() -> include(QtSetup), which is included after the # execution of _qt_internal_create_global_apk_target. function(_qt_internal_create_global_apk_all_target_if_needed) if(TARGET apk AND NOT TARGET apk_all) # Some Qt tests helper executables have their apk build process failing. # qt_internal_add_executables that are excluded from ALL should also not have apks built # for them. # Don't build apks by default when doing a Qt build. set(skip_add_to_all FALSE) if(QT_BUILDING_QT) set(skip_add_to_all TRUE) endif() option(QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL "Skip building apks as part of the default 'ALL' target" ${skip_add_to_all}) set(part_of_all "ALL") if(QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL) set(part_of_all "") endif() add_custom_target(apk_all ${part_of_all}) add_dependencies(apk_all apk) endif() endfunction() # The function converts the target property to a json record and appends it to the output # variable. function(_qt_internal_add_android_deployment_property out_var json_key target property) set(property_genex "$>") string(APPEND ${out_var} "$<$:" " \"${json_key}\": \"${property_genex}\"\,\n" ">" ) set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() # The function converts the list properties of the targets to a json list record and appends it # to the output variable. # _qt_internal_add_android_deployment_list_property(out_var json_key [ ]...) # The generated JSON object is the normal JSON array, e.g.: # "qml-root-path": ["qml/root/path1","qml/root/path2"], function(_qt_internal_add_android_deployment_list_property out_var json_key) list(LENGTH ARGN argn_count) math(EXPR is_odd "${argn_count} % 2") if(is_odd) message(FATAL_ERROR "Invalid argument count") endif() set(skip_next FALSE) set(property_genex "") math(EXPR last_index "${argn_count} - 1") foreach(idx RANGE ${last_index}) if(skip_next) set(skip_next FALSE) continue() endif() set(skip_next TRUE) math(EXPR property_idx "${idx} + 1") list(GET ARGN ${idx} target) list(GET ARGN ${property_idx} property) # Add comma if we have at least one element from the previous iteration if(property_genex) set(add_comma_genex "$<$:$>" ) endif() set(property_genex "$>" ) set(add_quote_genex "$<$:\">" ) # Add comma only if next property genex contains non-empty value. set(add_comma_genex "$<$:${add_comma_genex}>") string(JOIN "" list_join_genex "${list_join_genex}" "${add_comma_genex}${add_quote_genex}" "$\"" ">" "${add_quote_genex}" ) endforeach() string(APPEND ${out_var} " \"${json_key}\" : [ ${list_join_genex} ],\n") set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() # The function converts the target list property to a json multi-value string record and appends it # to the output variable. # The generated JSON object is a simple string with the list property items separated by commas, # e.g: # "android-extra-plugins": "plugin1,plugin2", function(_qt_internal_add_android_deployment_multi_value_property out_var json_key target property) set(property_genex "$>" ) string(JOIN "" list_join_genex "$" ">" ) string(APPEND ${out_var} "$<$:" " \"${json_key}\" : \"${list_join_genex}\",\n" ">" ) set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() # The function converts paths to the CMake format to make them acceptable for JSON. # It doesn't overwrite public properties, but instead writes formatted values to internal # properties. function(_qt_internal_android_format_deployment_paths target) if(QT_BUILD_STANDALONE_TESTS OR QT_BUILDING_QT OR QT_INTERNAL_IS_STANDALONE_TEST) set(android_deployment_paths_policy NEW) else() set(policy_path_properties QT_QML_IMPORT_PATH QT_QML_ROOT_PATH QT_ANDROID_PACKAGE_SOURCE_DIR QT_ANDROID_EXTRA_PLUGINS QT_ANDROID_EXTRA_LIBS ) # Check if any of paths contains the value and stop the evaluation if all properties are # empty or -NOTFOUND set(has_android_paths FALSE) foreach(prop_name IN LISTS policy_path_properties) get_target_property(prop_value ${target} ${prop_name}) if(prop_value) set(has_android_paths TRUE) break() endif() endforeach() if(has_android_paths) __qt_internal_setup_policy(QTP0002 "6.6.0" "Target properties that specify android-specific paths may contain generator\ expressions but they must evaluate to valid JSON strings.\ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0002.html for policy details." ) qt6_policy(GET QTP0002 android_deployment_paths_policy) endif() endif() if(android_deployment_paths_policy STREQUAL "NEW") # When building standalone tests or Qt itself we obligate developers to not use # windows paths when setting QT_* properties below, so their values are used as is when # generating deployment settings. string(JOIN "" qml_root_path_genex "$>" "$<" "$>>," "$>>" ">:;" ">" "$>" ) set_target_properties(${target} PROPERTIES _qt_native_qml_import_paths "$>" _qt_android_native_qml_root_paths "${qml_root_path_genex}" _qt_android_native_package_source_dir "$>" _qt_android_native_extra_plugins "$>" _qt_android_native_extra_libs "$>" ) else() # User projects still may use windows paths inside the QT_* properties below, with # obligation to run the finalizer code. _qt_internal_android_format_deployment_path_property(${target} QT_QML_IMPORT_PATH _qt_native_qml_import_paths) _qt_internal_android_format_deployment_path_property(${target} QT_QML_ROOT_PATH _qt_android_native_qml_root_paths) _qt_internal_android_format_deployment_path_property(${target} _qt_internal_qml_root_path _qt_android_native_qml_root_paths APPEND) _qt_internal_android_format_deployment_path_property(${target} QT_ANDROID_PACKAGE_SOURCE_DIR _qt_android_native_package_source_dir) _qt_internal_android_format_deployment_path_property(${target} QT_ANDROID_EXTRA_PLUGINS _qt_android_native_extra_plugins) _qt_internal_android_format_deployment_path_property(${target} QT_ANDROID_EXTRA_LIBS _qt_android_native_extra_libs) endif() endfunction() # The function converts the value of target property to JSON compatible path and writes the # result to out_property. Property might be either single value, semicolon separated list or system # path spec. # The APPEND argument controls the property is set. The argument should be added after all # the required arguments. function(_qt_internal_android_format_deployment_path_property target property out_property) set(should_append "") if(ARGC EQUAL 4) if("${ARGV3}" STREQUAL "APPEND") set(should_append APPEND) else() message(FATAL_ERROR "Unexpected argument ${ARGV3}") endif() elseif(ARGC GREATER 4) message(FATAL_ERROR "Unexpected arguments ${ARGN}") endif() get_target_property(_paths ${target} ${property}) if(_paths) set(native_paths "") foreach(_path IN LISTS _paths) file(TO_CMAKE_PATH "${_path}" _path) list(APPEND native_paths "${_path}") endforeach() set_property(TARGET ${target} ${should_append} PROPERTY ${out_property} "${native_paths}") endif() endfunction() if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_android_add_apk_target) 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_prefix_path out_path abi) if(CMAKE_ANDROID_ARCH_ABI STREQUAL abi) # Required to build unit tests in developer build if(QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX) set(${out_path} "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}") else() set(${out_path} "${QT6_INSTALL_PREFIX}") endif() elseif(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() function(_qt_internal_get_android_abi_cmake_dir_path out_path abi) if(DEFINED QT_ANDROID_PATH_CMAKE_DIR_${abi}) set(cmake_dir "${QT_ANDROID_PATH_CMAKE_DIR_${abi}}") else() _qt_internal_get_android_abi_prefix_path(prefix_path ${abi}) if((PROJECT_NAME STREQUAL "QtBase" OR QT_SUPERBUILD) AND QT_BUILDING_QT AND NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_INTERNAL_IS_STANDALONE_TEST) set(cmake_dir "${QT_CONFIG_BUILD_DIR}") else() string(TOUPPER "${QT_CMAKE_EXPORT_NAMESPACE}" export_namespace_upper) set(cmake_dir "${prefix_path}/${${export_namespace_upper}_INSTALL_LIBS}/cmake") endif() endif() set(${out_path} "${cmake_dir}" PARENT_SCOPE) endfunction() function(_qt_internal_get_android_abi_toolchain_path out_path abi) set(toolchain_path "${QT_CMAKE_EXPORT_NAMESPACE}/qt.toolchain.cmake") _qt_internal_get_android_abi_cmake_dir_path(cmake_dir ${abi}) get_filename_component(toolchain_path "${cmake_dir}/${toolchain_path}" ABSOLUTE) set(${out_path} "${toolchain_path}" PARENT_SCOPE) endfunction() function(_qt_internal_get_android_abi_subdir_path out_path subdir abi) set(install_paths_path "${QT_CMAKE_EXPORT_NAMESPACE}/QtInstallPaths.cmake") _qt_internal_get_android_abi_cmake_dir_path(cmake_dir ${abi}) include("${cmake_dir}/${install_paths_path}") set(${out_path} "${${subdir}}" PARENT_SCOPE) endfunction() function(_qt_internal_collect_qt_for_android_paths out_var) get_target_property(qt_android_abis ${target} _qt_android_abis) if(NOT qt_android_abis) set(qt_android_abis "") endif() set(custom_qt_paths data libexecs libs plugins qml) foreach(type IN ITEMS prefix ${custom_qt_paths}) set(${type}_records "") endforeach() foreach(abi IN LISTS qt_android_abis CMAKE_ANDROID_ARCH_ABI) _qt_internal_get_android_abi_prefix_path(qt_abi_prefix_path ${abi}) file(TO_CMAKE_PATH "${qt_abi_prefix_path}" qt_abi_prefix_path) get_filename_component(qt_abi_prefix_path "${qt_abi_prefix_path}" ABSOLUTE) list(APPEND prefix_records " \"${abi}\": \"${qt_abi_prefix_path}\"") foreach(type IN ITEMS ${custom_qt_paths}) string(TOUPPER "${type}" upper_case_type) _qt_internal_get_android_abi_subdir_path(qt_abi_path QT6_INSTALL_${upper_case_type} ${abi}) list(APPEND ${type}_records " \"${abi}\": \"${qt_abi_path}\"") endforeach() endforeach() foreach(type IN ITEMS prefix ${custom_qt_paths}) list(JOIN ${type}_records ",\n" ${type}_records_string) set(${type}_records_string "{\n${${type}_records_string}\n }") endforeach() string(APPEND ${out_var} " \"qt\": ${prefix_records_string},\n") string(APPEND ${out_var} " \"qtDataDirectory\": ${data_records_string},\n") string(APPEND ${out_var} " \"qtLibExecsDirectory\": ${libexecs_records_string},\n") string(APPEND ${out_var} " \"qtLibsDirectory\": ${libs_records_string},\n") string(APPEND ${out_var} " \"qtPluginsDirectory\": ${plugins_records_string},\n") string(APPEND ${out_var} " \"qtQmlDirectory\": ${qml_records_string},\n") set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() # The function collects list of existing Qt for Android using # _qt_internal_get_android_abi_prefix_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_toolchain_path(qt_abi_toolchain_path ${abi}) # It's expected that Qt for Android contains ABI specific toolchain file. if(EXISTS "${qt_abi_toolchain_path}" 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() # Returns a path to the timestamp file for the specific step of the multi-ABI Android project function(_qt_internal_get_android_abi_step_stampfile out project abi step) get_target_property(build_dir ${project} _qt_android_build_directory) get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(is_multi) set(${out} "${build_dir}/$/${project}_${step}_stamp" PARENT_SCOPE) else() set(${out} "${build_dir}/${project}_${step}_stamp" PARENT_SCOPE) endif() endfunction() # Creates the multi-ABI Android projects and assigns the JOB_POOL to them if it's possible function(_qt_internal_add_android_abi_project project abi) add_custom_target(${project}) set(build_dir "${CMAKE_BINARY_DIR}/android_abi_builds/${abi}") set_target_properties(${project} PROPERTIES _qt_android_build_directory "${build_dir}" ) file(MAKE_DIRECTORY "${build_dir}") if(CMAKE_GENERATOR MATCHES "^Ninja") set_property(GLOBAL APPEND PROPERTY JOB_POOLS _qt_android_${project}_pool=1) endif() endfunction() # Adds the custom build step to the multi-ABI Android project function(_qt_internal_add_android_abi_step project abi step) cmake_parse_arguments(arg "" "" "COMMAND;DEPENDS" ${ARGV}) if(NOT arg_COMMAND) message(FATAL_ERROR "COMMAND is not set for ${project} step ${step} Android ABI ${abi}.") endif() set(dep_stamps "") foreach(dep ${arg_DEPENDS}) _qt_internal_get_android_abi_step_stampfile(stamp ${project} ${abi} ${dep}) list(APPEND dep_stamps "${stamp}") endforeach() get_target_property(build_dir ${project} _qt_android_build_directory) if(CMAKE_GENERATOR MATCHES "^Ninja") set(add_to_pool JOB_POOL _qt_android_${project}_pool) else() set(add_to_pool "") endif() _qt_internal_get_android_abi_step_stampfile(stamp ${project} ${abi} ${step}) add_custom_command(OUTPUT "${stamp}" COMMAND ${arg_COMMAND} COMMAND "${CMAKE_COMMAND}" -E touch "${stamp}" ${add_to_pool} DEPENDS ${dep_stamps} WORKING_DIRECTORY "${build_dir}" VERBATIM ) add_custom_target("${project}_${step}" DEPENDS "${stamp}") get_target_property(known_steps ${project} _qt_android_abi_steps) if(NOT CMAKE_GENERATOR MATCHES "^Ninja") if(NOT QT_NO_WARN_ANDROID_MULTI_ABI_GENERATOR) get_property(is_warned GLOBAL PROPERTY _qt_internal_warn_android_multi_abi_generator) if(NOT is_warned) set_property(GLOBAL PROPERTY _qt_internal_warn_android_multi_abi_generator TRUE) message(WARNING "Building Multi-ABI Qt projects with the '${CMAKE_GENERATOR}'" " generator has limitations. All targets from non-main ABI will be built" " unconditionally. Please use the 'Ninja' or 'Ninja Multi-config' generators" " with ninja build instead. Set QT_NO_WARN_ANDROID_MULTI_ABI_GENERATOR to" " 'TRUE' to suppress this warning." ) endif() endif() if(known_steps) list(GET known_steps 0 first) add_dependencies(${first} ${project}_${step}) endif() endif() list(PREPEND known_steps ${project}_${step}) set_target_properties(${project} PROPERTIES _qt_android_abi_steps "${known_steps}") endfunction() # The function configures external projects for ABIs that target packages need to build with. # Each target adds build step to the external project that is linked to the # qt_internal_android_${abi}-${target}_build target in the primary ABI build tree. function(_qt_internal_configure_android_multiabi_target target) # Functionality is only applicable for the primary ABI if(QT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT) return() endif() get_target_property(target_abis ${target} QT_ANDROID_ABIS) if(target_abis) # Use target-specific Qt for Android ABIs. set(android_abis ${target_abis}) elseif(QT_ANDROID_BUILD_ALL_ABIS) # Use autodetected Qt for Android ABIs. set(android_abis ${QT_DEFAULT_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" " - Set the ANDROID_ABIS property for ${target}\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() get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) if(is_multi_config) list(JOIN CMAKE_CONFIGURATION_TYPES "$" escaped_configuration_types) set(config_arg "-DCMAKE_CONFIGURATION_TYPES=${escaped_configuration_types}") else() set(config_arg "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") endif() unset(extra_cmake_args) # The flag is needed when building qt standalone tests only to avoid building # qt repo itself if(QT_BUILD_STANDALONE_TESTS) list(APPEND extra_cmake_args "-DQT_BUILD_STANDALONE_TESTS=ON") endif() if(NOT QT_ADDITIONAL_PACKAGES_PREFIX_PATH STREQUAL "") list(JOIN QT_ADDITIONAL_PACKAGES_PREFIX_PATH "$" escaped_packages_prefix_path) list(APPEND extra_cmake_args "-DQT_ADDITIONAL_PACKAGES_PREFIX_PATH=${escaped_packages_prefix_path}") endif() if(NOT QT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH STREQUAL "") list(JOIN QT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH "$" escaped_host_packages_prefix_path) list(APPEND extra_cmake_args "-DQT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH=${escaped_host_packages_prefix_path}") endif() if(ANDROID_SDK_ROOT) list(APPEND extra_cmake_args "-DANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}") endif() # ANDROID_NDK_ROOT is invented by Qt and is what the qt toolchain file expects if(ANDROID_NDK_ROOT) list(APPEND extra_cmake_args "-DANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}") # ANDROID_NDK is passed by Qt Creator and is also present in the android toolchain file. elseif(ANDROID_NDK) list(APPEND extra_cmake_args "-DANDROID_NDK_ROOT=${ANDROID_NDK}") endif() if(DEFINED QT_NO_PACKAGE_VERSION_CHECK) list(APPEND extra_cmake_args "-DQT_NO_PACKAGE_VERSION_CHECK=${QT_NO_PACKAGE_VERSION_CHECK}") endif() if(DEFINED QT_HOST_PATH_CMAKE_DIR) list(APPEND extra_cmake_args "-DQT_HOST_PATH_CMAKE_DIR=${QT_HOST_PATH_CMAKE_DIR}") endif() if(CMAKE_MAKE_PROGRAM) list(APPEND extra_cmake_args "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") endif() if(CMAKE_C_COMPILER_LAUNCHER) list(JOIN CMAKE_C_COMPILER_LAUNCHER "$" compiler_launcher) list(APPEND extra_cmake_args "-DCMAKE_C_COMPILER_LAUNCHER=${compiler_launcher}") endif() if(CMAKE_CXX_COMPILER_LAUNCHER) list(JOIN CMAKE_CXX_COMPILER_LAUNCHER "$" compiler_launcher) list(APPEND extra_cmake_args "-DCMAKE_CXX_COMPILER_LAUNCHER=${compiler_launcher}") endif() unset(user_cmake_args) foreach(var IN LISTS QT_ANDROID_MULTI_ABI_FORWARD_VARS) string(REPLACE ";" "$" var_value "${${var}}") list(APPEND user_cmake_args "-D${var}=${var_value}") endforeach() set(missing_qt_abi_toolchains "") set(previous_copy_apk_dependencies_target ${target}) # Create external projects for each android ABI except the main one. list(REMOVE_ITEM android_abis "${CMAKE_ANDROID_ARCH_ABI}") 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() get_property(abi_external_projects GLOBAL PROPERTY _qt_internal_abi_external_projects) if(NOT abi_external_projects OR NOT "qt_internal_android_${abi}" IN_LIST abi_external_projects) _qt_internal_add_android_abi_project(qt_internal_android_${abi} ${abi}) get_target_property(android_abi_build_dir qt_internal_android_${abi} _qt_android_build_directory) _qt_internal_get_android_abi_toolchain_path(qt_abi_toolchain_path ${abi}) _qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi} configure COMMAND "${CMAKE_COMMAND}" "-G${CMAKE_GENERATOR}" "-DCMAKE_TOOLCHAIN_FILE=${qt_abi_toolchain_path}" "-DQT_HOST_PATH=${QT_HOST_PATH}" "-DQT_IS_ANDROID_MULTI_ABI_EXTERNAL_PROJECT=ON" "-DQT_INTERNAL_ANDROID_MULTI_ABI_BINARY_DIR=${CMAKE_BINARY_DIR}" "${config_arg}" "${extra_cmake_args}" "${user_cmake_args}" "-B" "${android_abi_build_dir}" "-S" "${CMAKE_SOURCE_DIR}" ) set_property(GLOBAL APPEND PROPERTY _qt_internal_abi_external_projects "qt_internal_android_${abi}") endif() get_target_property(android_abi_build_dir qt_internal_android_${abi} _qt_android_build_directory) _qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi} ${target}_build DEPENDS configure COMMAND "${CMAKE_COMMAND}" --build "${android_abi_build_dir}" --config $ --target ${target} ) add_dependencies(${target} "qt_internal_android_${abi}_${target}_build") _qt_internal_add_android_abi_step(qt_internal_android_${abi} ${abi} ${target}_copy_apk_dependencies DEPENDS ${target}_build COMMAND "${CMAKE_COMMAND}" --build "${android_abi_build_dir}" --config $ --target qt_internal_${target}_copy_apk_dependencies ) set(external_project_copy_target "qt_internal_android_${abi}_${target}_copy_apk_dependencies") # Need to build dependency chain between the # qt_internal_android_${abi}-${target}_copy_apk_dependencies targets for all ABI's, to # prevent parallel execution of androiddeployqt processes. We cannot use Ninja job pools # here because it's not possible to define job pool for the step target in ExternalProject. # All tricks with interlayer targets don't work, because we only can bind interlayer target # to the job pool, but its dependencies can still be built in parallel. add_dependencies(${previous_copy_apk_dependencies_target} "${external_project_copy_target}") set(previous_copy_apk_dependencies_target "${external_project_copy_target}") 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}" "\nNote that you also may manually specify the path to the required Qt for" " Android ABI using QT_PATH_ANDROID_ABI_ CMake variable with the value" " of the installation prefix, and QT_ANDROID_PATH_CMAKE_DIR_ with" " the location of the cmake directory for that ABI.\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}") endfunction() # The wrapper function that contains routines that need to be called to produce a valid Android # package for the executable 'target'. The function is added to the finalizer list of the Core # module and is executed implicitly when configuring user projects. function(_qt_internal_android_executable_finalizer target) set_property(TARGET ${target} PROPERTY _qt_android_executable_finalizer_called TRUE) _qt_internal_expose_android_package_source_dir_to_ide(${target}) _qt_internal_configure_android_multiabi_target("${target}") qt6_android_generate_deployment_settings("${target}") qt6_android_add_apk_target("${target}") endfunction() function(_qt_internal_expose_android_package_source_dir_to_ide target) get_target_property(android_package_source_dir ${target} QT_ANDROID_PACKAGE_SOURCE_DIR) if(android_package_source_dir) get_target_property(target_source_dir ${target} SOURCE_DIR) if(NOT IS_ABSOLUTE "${android_package_source_dir}") string(JOIN "/" android_package_source_dir "${target_source_dir}" "${android_package_source_dir}" ) endif() if(EXISTS "${android_package_source_dir}") file(GLOB_RECURSE android_package_sources RELATIVE "${target_source_dir}" "${android_package_source_dir}/*" ) endif() foreach(f IN LISTS android_package_sources) _qt_internal_expose_source_file_to_ide(${target} "${f}") endforeach() endif() endfunction() set(QT_INTERNAL_ANDROID_TARGET_BUILD_DIR_SUPPORT ON CACHE INTERNAL "Indicates that Qt supports per-target Android build directories")