diff options
Diffstat (limited to 'src/corelib/Qt6AndroidGradleHelpers.cmake')
-rw-r--r-- | src/corelib/Qt6AndroidGradleHelpers.cmake | 424 |
1 files changed, 375 insertions, 49 deletions
diff --git a/src/corelib/Qt6AndroidGradleHelpers.cmake b/src/corelib/Qt6AndroidGradleHelpers.cmake index 889438ffaad..09184c93b69 100644 --- a/src/corelib/Qt6AndroidGradleHelpers.cmake +++ b/src/corelib/Qt6AndroidGradleHelpers.cmake @@ -1,12 +1,61 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +# Returns the path to the template file from either user defined template directory, or +# Qt default template directory. +function(_qt_internal_android_get_template_path out_var target template_name) + if(template_name STREQUAL "") + message(FATAL_ERROR "Template name is empty." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + + _qt_internal_android_template_dir(template_directory) + get_filename_component(template_directory "${template_directory}" ABSOLUTE) + + # The paths are ordered according to their priority, from highest to lowest. + set(possible_paths + "${template_directory}/${template_name}.in" + ) + + get_target_property(android_target_type ${target} _qt_android_target_type) + if(android_target_type STREQUAL "APPLICATION") + _qt_internal_android_get_package_source_dir(user_template_directory ${target}) + get_filename_component(user_template_directory "${user_template_directory}" ABSOLUTE) + + # Add user template with the higher priority + list(PREPEND possible_paths "${user_template_directory}/${template_name}.in") + endif() + + set(template_path "") + foreach(possible_path IN LISTS possible_paths) + if(EXISTS "${possible_path}") + set(template_path "${possible_path}") + break() + endif() + endforeach() + + if(template_path STREQUAL "") + message(FATAL_ERROR "'${template_name}' is not found." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + + set(${out_var} "${template_path}" PARENT_SCOPE) +endfunction() + # Generates the settings.gradle file for the target. Writes the result to the target android build # directory. function(_qt_internal_android_generate_bundle_settings_gradle target) set(settings_gradle_filename "settings.gradle") - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${settings_gradle_filename}.in") + _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + set(settings_gradle_file "${android_build_dir}/${settings_gradle_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${settings_gradle_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} "${settings_gradle_filename}") set(android_app_name "$<TARGET_PROPERTY:${target},QT_ANDROID_APP_NAME>") string(JOIN "" ROOT_PROJECT_NAME @@ -16,16 +65,116 @@ function(_qt_internal_android_generate_bundle_settings_gradle target) ">" ) - _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) - set(settings_gradle_file "${android_build_dir}/${settings_gradle_filename}") + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + set(include_prefix "include(\":") + set(include_suffix "\")") + set(include_glue "${include_suffix}\n${include_prefix}") + string(JOIN "" SUBPROJECTS + "$<$<BOOL:${target_dynamic_features}>:" + "${include_prefix}" + "$<JOIN:${target_dynamic_features},${include_glue}>" + "${include_suffix}" + ">" + ) _qt_internal_configure_file(GENERATE OUTPUT ${settings_gradle_file} INPUT "${template_file}") + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files + "${settings_gradle_file}") +endfunction() + +# Generates the source sets for the target. +function(_qt_internal_android_get_gradle_source_sets out_var target) + set(known_types java aidl res resources renderscript assets jniLibs) + set(source_set "") + set(indent " ") + foreach(type IN LISTS known_types) + set(source_dirs + "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_android_gradle_${type}_source_dirs>>") + string(JOIN "" source_set + "${source_set}" + "$<$<BOOL:${source_dirs}>:" + "${indent}${type}.srcDirs = ['$<JOIN:${source_dirs},'$<COMMA> '>']\n" + ">" + ) + endforeach() + + set(manifest + "$<TARGET_PROPERTY:${target},_qt_android_manifest>") + string(JOIN "" source_set + "${source_set}" + "$<$<BOOL:${manifest}>:" + "${indent}manifest.srcFile '${manifest}'\n" + ">" + ) + set(${out_var} "${source_set}" PARENT_SCOPE) +endfunction() + +# Generates the gradle dependency list for the target. +function(_qt_internal_android_get_gradle_dependencies out_var target) + # Use dependencies from file tree by default + set(known_dependencies + "implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])") + foreach(dep_type implementation api) + string(JOIN "\n " dep_prefix + "\n //noinspection GradleDependency" + "${dep_type} " + ) + set(dep_postfix "") + set(dep_property "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_android_gradle_${dep_type}_dependencies>>") + string(JOIN "" known_dependencies + "${known_dependencies}" + "$<$<BOOL:${dep_property}>:" + "${dep_prefix}$<JOIN:${dep_property},${dep_postfix}${dep_prefix}>${dep_postfix}" + ">" + ) + endforeach() + set(${out_var} "${known_dependencies}" PARENT_SCOPE) +endfunction() + +# Sets the default values of the gradle properties for the Android executable target. +function(_qt_internal_set_android_application_gradle_defaults target) + _qt_internal_android_java_dir(android_java_dir) + + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + string(JOIN "" implementation_dependencies + "$<$<BOOL:${target_dynamic_features}>:'com.google.android.play:feature-delivery:2.1.0'>" + ) + # TODO: make androidx.core:core version configurable. + # Currently, it is hardcoded to 1.16.0. + list(APPEND implementation_dependencies "'androidx.core:core:1.16.0'") + + set_target_properties(${target} PROPERTIES + _qt_android_gradle_java_source_dirs "${android_java_dir}/src;src;java" + _qt_android_gradle_aidl_source_dirs "${android_java_dir}/src;src;aidl" + _qt_android_gradle_res_source_dirs "${android_java_dir}/res;res" + _qt_android_gradle_resources_source_dirs "resources" + _qt_android_gradle_renderscript_source_dirs "src" + _qt_android_gradle_assets_source_dirs "assets" + _qt_android_gradle_jniLibs_source_dirs "libs" + _qt_android_manifest "AndroidManifest.xml" + _qt_android_gradle_implementation_dependencies "${implementation_dependencies}" + ) endfunction() # Generates the build.gradle file for the target. Writes the result to the target app deployment # directory. function(_qt_internal_android_generate_target_build_gradle target) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") + + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(build_gradle_filename "build.gradle") + set(out_file "${arg_DEPLOYMENT_DIR}/${build_gradle_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + # TODO: The current build.gradle.in templates hardcodes couple values that needs to be # configurable in the future. For example the buildscript dependencies, or the use of # androidx.core:core:1.13.1 and the dependency for all user applications. @@ -42,23 +191,8 @@ function(_qt_internal_android_generate_target_build_gradle target) "Please check your Android SDK installation.") endif() - _qt_internal_android_java_dir(android_java_dir) - string(JOIN "\n " SOURCE_SETS - "manifest.srcFile 'AndroidManifest.xml'" - "java.srcDirs = ['${android_java_dir}/src', 'src', 'java']" - "aidl.srcDirs = ['${android_java_dir}/src', 'src', 'aidl']" - "res.srcDirs = ['${android_java_dir}/res', 'res']" - "resources.srcDirs = ['resources']" - "renderscript.srcDirs = ['src']" - "assets.srcDirs = ['assets']" - "jniLibs.srcDirs = ['libs']" - ) - - string(JOIN "\n " GRADLE_DEPENDENCIES - "implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])" - "//noinspection GradleDependency" - "implementation 'androidx.core:core:1.13.1'" - ) + _qt_internal_android_get_gradle_source_sets(SOURCE_SETS ${target}) + _qt_internal_android_get_gradle_dependencies(GRADLE_DEPENDENCIES ${target}) _qt_internal_android_get_gradle_property(min_sdk_version ${target} QT_ANDROID_MIN_SDK_VERSION "28") @@ -76,21 +210,36 @@ function(_qt_internal_android_generate_target_build_gradle target) "ndk.abiFilters = ['${target_abi_list}']" ) - set(ANDROID_DEPLOYMENT_EXTRAS "") + set(target_dynamic_features "$<TARGET_PROPERTY:${target},_qt_android_dynamic_features>") + set(include_prefix "\":") + set(include_suffix "\"") + set(include_glue "${include_suffix}$<COMMA>${include_prefix}") + string(APPEND ANDROID_DEPLOYMENT_EXTRAS + "$<$<BOOL:${target_dynamic_features}>:dynamicFeatures = [" + "${include_prefix}" + "$<JOIN:${target_dynamic_features},${include_glue}>" + "${include_suffix}]" + ">" + ) get_target_property(android_target_type ${target} _qt_android_target_type) if(android_target_type STREQUAL "APPLICATION") set(GRADLE_PLUGIN_TYPE "com.android.application") + set(template_subdir "app") + elseif(android_target_type STREQUAL "DYNAMIC_FEATURE") + set(GRADLE_PLUGIN_TYPE "com.android.dynamic-feature") + set(template_subdir "dynamic_feature") else() message(FATAL_ERROR "Unsupported target type for android bundle deployment ${target}") endif() - _qt_internal_android_get_target_deployment_dir(target_deployment_dir ${target}) - set(build_gradle_filename "build.gradle") - set(build_gradle_file "${target_deployment_dir}/${build_gradle_filename}") - _qt_internal_android_template_dir(template_directory) - _qt_internal_configure_file(GENERATE OUTPUT "${build_gradle_file}" - INPUT "${template_directory}/${build_gradle_filename}.in") + _qt_internal_android_get_template_path(template_file ${target} + "${template_subdir}/${build_gradle_filename}") + _qt_internal_configure_file(GENERATE + OUTPUT "${out_file}" + INPUT "${template_file}" + ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") endfunction() # Prepares the artifacts for the gradle build of the target. @@ -99,21 +248,17 @@ function(_qt_internal_android_prepare_gradle_build target) _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) _qt_internal_android_copy_gradle_files(${target} "${android_build_dir}") + _qt_internal_android_copy_target_package_sources(${target}) _qt_internal_android_generate_bundle_gradle_properties(${target}) _qt_internal_android_generate_bundle_settings_gradle(${target}) _qt_internal_android_generate_bundle_local_properties(${target}) - _qt_internal_android_generate_target_build_gradle(${target}) - _qt_internal_android_generate_target_gradle_properties(${target}) - - set(gradle_scripts - "${android_build_dir}/gradle.properties" - "${android_build_dir}/local.properties" - "${android_build_dir}/settings.gradle" - "${deployment_dir}/build.gradle" - "${deployment_dir}/gradle.properties" - ) - set_target_properties(${target} PROPERTIES _qt_android_deployment_files "${gradle_scripts}") + _qt_internal_android_generate_target_build_gradle(${target} DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_generate_target_gradle_properties(${target} + DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_generate_target_android_manifest(${target} + DEPLOYMENT_DIR "${deployment_dir}") + _qt_internal_android_add_gradle_build(${target} apk) _qt_internal_android_add_gradle_build(${target} aab) @@ -146,6 +291,15 @@ function(_qt_internal_android_add_gradle_build target type) set(package_build_file_path "${package_build_dir}/${deployment_type_suffix}/app-${deployment_type_suffix}.${type}") + set(extra_deps "") + if(TARGET ${target}_copy_feature_names) + list(APPEND extra_deps ${target}_copy_feature_names) + endif() + + if(TARGET ${target}_deploy_dynamic_features) + list(APPEND extra_deps ${target}_deploy_dynamic_features) + endif() + set(gradle_scripts "$<TARGET_PROPERTY:${target},_qt_android_deployment_files>") add_custom_command(OUTPUT "${package_file_path}" BYPRODUCTS "${package_build_file_path}" @@ -159,6 +313,7 @@ function(_qt_internal_android_add_gradle_build target type) ${gradle_scripts} ${target}_copy_gradle_files ${target}_android_deploy_aux + ${extra_deps} WORKING_DIRECTORY "${android_build_dir}" VERBATIM @@ -235,13 +390,120 @@ endfunction() # Generates gradle.properties for the specific target. Usually contains the # target build type(executable, dynamic feature, library). function(_qt_internal_android_generate_target_gradle_properties target) - set(gradle_properties "gradle.properties") - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${gradle_properties}.in") - _qt_internal_android_get_target_deployment_dir(deployment_dir ${target}) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") - _qt_internal_configure_file(CONFIGURE OUTPUT "${deployment_dir}/${gradle_properties}" - INPUT "${template_file}") + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(gradle_properties_file_name "gradle.properties") + set(out_file "${arg_DEPLOYMENT_DIR}/${gradle_properties_file_name}") + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} + "app/${gradle_properties_file_name}") + _qt_internal_configure_file(CONFIGURE + OUTPUT "${out_file}" + INPUT "${template_file}" + ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") +endfunction() + +# Constucts generator expression that returns either target property or the default value +function(_qt_internal_android_get_manifest_property out_var target property default) + set(target_property "$<TARGET_PROPERTY:${target},${property}>") + string(JOIN "" out_genex + "$<IF:$<BOOL:${target_property}>," + "${target_property}," + "${default}" + ">" + ) + + set(${out_var} "${out_genex}" PARENT_SCOPE) +endfunction() + +# Generates the target AndroidManifest.xml +function(_qt_internal_android_generate_target_android_manifest target) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "DEPLOYMENT_DIR" "") + + if(NOT arg_DEPLOYMENT_DIR) + message(FATAL_ERROR "DEPLOYMENT_DIR is not specified.") + endif() + + set(android_manifest_filename "AndroidManifest.xml") + set(out_file "${arg_DEPLOYMENT_DIR}/${android_manifest_filename}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} + "app/${android_manifest_filename}") + set(temporary_file "${out_file}.tmp") + + # The file cannot be generated at cmake configure time, because androiddeployqt + # will override it at build time. We use this trick with temporary file to override + # it after the aux run of androiddeployqt. + add_custom_command(OUTPUT "${out_file}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${temporary_file}" + "${out_file}" + DEPENDS + "${template_file}" + "${temporary_file}" + ${target}_android_deploy_aux + ) + + _qt_internal_android_get_manifest_property(APP_PACKAGE_NAME ${target} + QT_ANDROID_PACKAGE_NAME "org.qtproject.example.$<MAKE_C_IDENTIFIER:${target}>") + _qt_internal_android_get_manifest_property(APP_NAME ${target} + QT_ANDROID_APP_NAME "${target}") + _qt_internal_android_get_manifest_property(APP_VERSION_CODE ${target} + QT_ANDROID_VERSION_CODE "1") + _qt_internal_android_get_manifest_property(APP_VERSION_NAME ${target} + QT_ANDROID_VERSION_NAME "1") + _qt_internal_android_get_manifest_property(APP_LIB_NAME ${target} OUTPUT_NAME "${target}") + + # For application icon we substitute the whole attribute definition, but not only value + # otherwise it leads to the Manifest processing issue. + set(target_property "$<TARGET_PROPERTY:${target},QT_ANDROID_APP_ICON>") + string(JOIN "" APP_ICON + "$<$<BOOL:${target_property}>:" + "android:icon=\"${target_property}\"" + ">" + ) + + file(READ "${template_file}" manifest_content) + string(REPLACE ">" "$<ANGLE-R>" manifest_content "${manifest_content}") + string(REPLACE ";" "$<SEMICOLON>" manifest_content "${manifest_content}") + string(REPLACE "," "$<COMMA>" manifest_content "${manifest_content}") + + _qt_internal_android_convert_permissions(APP_PERMISSIONS ${target} XML) + + set(feature_prefix "\n <uses-feature android:name=\"") + set(feature_suffix " \" android:required=\"false\" /$<ANGLE-R>") + set(feature_property "$<TARGET_PROPERTY:${target},QT_ANDROID_FEATURES>") + string(JOIN "" APP_FEATURES + "$<$<BOOL:${feature_property}>:" + "${feature_prefix}" + "$<JOIN:${feature_property},${feature_suffix},${feature_prefix}>" + "${feature_suffix}" + ">" + ) + + set(APP_ARGUMENTS "${QT_ANDROID_APPLICATION_ARGUMENTS}") + + _qt_internal_configure_file(GENERATE OUTPUT "${temporary_file}" + CONTENT "${manifest_content}") + + set_property(TARGET ${target} APPEND PROPERTY + _qt_android_deployment_files "${out_file}" "${temporary_file}") endfunction() # Generates the top-level gradle.properties in the android-build directory @@ -252,24 +514,88 @@ function(_qt_internal_android_generate_bundle_gradle_properties target) set(gradle_properties_file_name "gradle.properties") _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) - _qt_internal_android_template_dir(template_directory) - set(template_file "${template_directory}/${gradle_properties_file_name}.in") + set(out_file "${android_build_dir}/${gradle_properties_file_name}") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() + + _qt_internal_android_get_template_path(template_file ${target} "${gradle_properties_file_name}") _qt_internal_configure_file(CONFIGURE - OUTPUT "${android_build_dir}/${gradle_properties_file_name}" + OUTPUT "${out_file}" INPUT "${template_file}" ) + set_property(TARGET ${target} APPEND PROPERTY _qt_android_deployment_files "${out_file}") endfunction() # Generates the local.properties for gradle builds. Contains the path to the # Android SDK root. function(_qt_internal_android_generate_bundle_local_properties target) _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + set(out_file "${android_build_dir}/local.properties") + + # Skip generating the file if it's already provided by user. + get_target_property(deployment_files ${target} _qt_android_deployment_files) + if("${out_file}" IN_LIST deployment_files) + return() + endif() file(TO_CMAKE_PATH "${ANDROID_SDK_ROOT}" ANDROID_SDK_ROOT_NATIVE) - _qt_internal_configure_file(CONFIGURE OUTPUT "${android_build_dir}/local.properties" + _qt_internal_configure_file(CONFIGURE OUTPUT "${out_file}" CONTENT "sdk.dir=${ANDROID_SDK_ROOT_NATIVE}\n") endfunction() +# Copies the customized Android package sources to the Android build directory +function(_qt_internal_android_copy_target_package_sources target) + _qt_internal_android_get_package_source_dir(package_source_dir ${target}) + + if(NOT package_source_dir) + return() + endif() + get_filename_component(package_source_dir "${package_source_dir}" ABSOLUTE) + + # Collect deployment files from use-defined package source directory + file(GLOB_RECURSE package_files + LIST_DIRECTORIES false + RELATIVE "${package_source_dir}" + "${package_source_dir}/*" + ) + + # Do not copy files that we treat as CMake templates, having '.in' extention. + # + # TODO: If it ever will be an issue we may exclude only templates that are + # known by our build system. + list(FILTER package_files EXCLUDE REGEX ".+\\.in$") + + _qt_internal_android_get_target_android_build_dir(android_build_dir ${target}) + list(TRANSFORM package_files PREPEND "${android_build_dir}/" OUTPUT_VARIABLE out_package_files) + list(TRANSFORM package_files PREPEND "${package_source_dir}/" OUTPUT_VARIABLE in_package_files) + + if(in_package_files) + # TODO: Add cmake < 3.26 support + if(CMAKE_VERSION VERSION_LESS 3.26) + message(FATAL_ERROR "The use of QT_ANDROID_PACKAGE_SOURCE_DIR property with + the QT_USE_ANDROID_MODERN_BUNDLE option enabled requires CMake version >= 3.26.") + endif() + set(copy_commands COMMAND "${CMAKE_COMMAND}" -E copy_directory_if_different + "${package_source_dir}" "${android_build_dir}") + else() + # We actually have nothing to deploy. + return() + endif() + + add_custom_command(OUTPUT ${out_package_files} + ${copy_commands} + DEPENDS + ${in_package_files} + VERBATIM + ) + + set_target_properties(${target} PROPERTIES _qt_android_deployment_files "${out_package_files}") +endfunction() + # Copies gradle scripts to a build directory. function(_qt_internal_android_copy_gradle_files target output_directory) _qt_internal_android_gradlew_name(gradlew_file_name) |