# Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause # # Android specific functions/macros/properties required for building Qt Modules # macro(qt_internal_setup_android_target_properties) define_property(TARGET PROPERTY QT_ANDROID_MODULE_INSTALL_DIR BRIEF_DOCS "Recorded install location for a Qt Module." FULL_DOCS "Recorded install location for a Qt Module. Used by qt_internal_android_dependencies()." ) define_property(TARGET PROPERTY QT_ANDROID_JAR_DEPENDENCIES BRIEF_DOCS "Qt Module Jar dependencies list." FULL_DOCS "Qt Module Jar dependencies list." ) define_property(TARGET PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES BRIEF_DOCS "Qt Module Jars that should be bundled with it during packing." FULL_DOCS "Qt Module Jars that should be bundled with it during packing." ) define_property(TARGET PROPERTY QT_ANDROID_LIB_DEPENDENCIES BRIEF_DOCS "Qt Module C++ libraries that should be bundled with it during packing." FULL_DOCS "Qt Module C++ libraries that should be bundled with it during packing." ) define_property(TARGET PROPERTY QT_ANDROID_LIB_DEPENDENCY_REPLACEMENTS BRIEF_DOCS "Qt Module C++ libraries that can replace libraries declared with the QT_ANDROID_LIB_DEPENDENCIES property." FULL_DOCS "Qt Module C++ libraries that can replace libraries declared with the QT_ANDROID_LIB_DEPENDENCIES property." ) define_property(TARGET PROPERTY QT_ANDROID_BUNDLED_FILES BRIEF_DOCS "Qt Module files that need to be bundled during packing." FULL_DOCS "Qt Module files that need to be bundled during packing." ) define_property(TARGET PROPERTY QT_ANDROID_PERMISSIONS BRIEF_DOCS "Qt Module android permission list." FULL_DOCS "Qt Module android permission list." ) define_property(TARGET PROPERTY QT_ANDROID_FEATURES BRIEF_DOCS "Qt Module android feature list." FULL_DOCS "Qt Module android feature list." ) define_property(TARGET PROPERTY QT_ANDROID_ABIS BRIEF_DOCS "List of ABIs that the target packages are built with." FULL_DOCS "List of ABIs that the target packages are built with." ) endmacro() function(qt_internal_add_android_permission target) _qt_internal_add_android_permission(${ARGV}) endfunction() function(qt_internal_android_dependencies_content target file_content_out) get_target_property(arg_JAR_DEPENDENCIES ${target} QT_ANDROID_JAR_DEPENDENCIES) get_target_property(arg_BUNDLED_JAR_DEPENDENCIES ${target} QT_ANDROID_BUNDLED_JAR_DEPENDENCIES) get_target_property(arg_LIB_DEPENDENCIES ${target} QT_ANDROID_LIB_DEPENDENCIES) get_target_property(arg_LIB_DEPENDENCY_REPLACEMENTS ${target} QT_ANDROID_LIB_DEPENDENCY_REPLACEMENTS) get_target_property(arg_BUNDLED_FILES ${target} QT_ANDROID_BUNDLED_FILES) get_target_property(arg_PERMISSIONS ${target} QT_ANDROID_PERMISSIONS) get_target_property(arg_FEATURES ${target} QT_ANDROID_FEATURES) if ((NOT arg_JAR_DEPENDENCIES) AND (NOT arg_BUNDLED_JAR_DEPENDENCIES) AND (NOT arg_LIB_DEPENDENCIES) AND (NOT arg_LIB_DEPENDENCY_REPLACEMENTS) AND (NOT arg_BUNDLED_FILES) AND (NOT arg_PERMISSIONS) AND (NOT arg_FEATURES)) # None of the values were set, so there's nothing to do return() endif() # mimic qmake's section and string splitting from # mkspecs/feature/qt_android_deps.prf macro(section string delimiter first second) string(FIND ${string} ${delimiter} delimiter_location) if (NOT ${delimiter_location} EQUAL -1) string(SUBSTRING ${string} 0 ${delimiter_location} ${first}) math(EXPR delimiter_location "${delimiter_location} + 1") string(SUBSTRING ${string} ${delimiter_location} -1 ${second}) else() set(${first} ${string}) set(${second} "") endif() endmacro() set(file_contents "") # Jar Dependencies if(arg_JAR_DEPENDENCIES) foreach(jar_dependency IN LISTS arg_JAR_DEPENDENCIES) section(${jar_dependency} ":" jar_file init_class) # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${jar_file} jar_file_unix_path) string(APPEND file_contents "\n") endforeach() endif() # Bundled Jar Dependencies if(arg_BUNDLED_JAR_DEPENDENCIES) foreach(jar_bundle IN LISTS arg_BUNDLED_JAR_DEPENDENCIES) section(${jar_bundle} ":" bundle_file init_class) # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${bundle_file} jar_bundle_unix_path) string(APPEND file_contents "\n") endforeach() endif() # Lib Dependencies if(arg_LIB_DEPENDENCIES) foreach(lib IN LISTS arg_LIB_DEPENDENCIES) string(REPLACE ".so" "_${CMAKE_ANDROID_ARCH_ABI}.so" lib ${lib}) section(${lib} ":" lib_file lib_extends) if (lib_extends) set(lib_extends "extends=\"${lib_extends}\"") endif() # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${lib_file} lib_file_unix_path) string(APPEND file_contents "\n") endforeach() endif() # Lib Dependencies Replacements if(arg_LIB_DEPENDENCY_REPLACEMENTS) foreach(lib IN LISTS arg_LIB_DEPENDENCY_REPLACEMENTS) string(REPLACE ".so" "_${CMAKE_ANDROID_ARCH_ABI}.so" lib ${lib}) section(${lib} ":" lib_file lib_replacement) if (lib_replacement) # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${lib_replacement} lib_replacement_unix_path) set(lib_replacement "replaces=\"${lib_replacement_unix_path}\"") endif() # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${lib_file} lib_file_unix_path) string(APPEND file_contents "\n") endforeach() endif() # Bundled files if(arg_BUNDLED_FILES) foreach(bundled_file IN LISTS arg_BUNDLED_FILES) # Use unix path to allow using files on any host platform. file(TO_CMAKE_PATH ${bundled_file} file_unix_path) string(APPEND file_contents "\n") endforeach() endif() # Android Permissions if(arg_PERMISSIONS) foreach(permission IN LISTS arg_PERMISSIONS) # Check if the permission has also extra attributes in addition to the permission name list(LENGTH permission permission_len) if(permission_len EQUAL 1) string(APPEND file_contents "\n") elseif(permission_len EQUAL 2) list(GET permission 0 name) list(GET permission 1 extras) string(APPEND file_contents "\n") else() message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}") endif() endforeach() endif() # Android Features if(arg_FEATURES) foreach(feature IN LISTS arg_FEATURES) string(APPEND file_contents "\n") endforeach() endif() set(${file_content_out} ${file_contents} PARENT_SCOPE) endfunction() # Generate Qt Module -android-dependencies.xml required by the # androiddeploytoolqt to successfully copy all the plugins and other dependent # items into the APK function(qt_internal_android_dependencies target) get_target_property(target_type "${target}" TYPE) if(target_type STREQUAL "INTERFACE_LIBRARY") return() endif() # Get plugins for the current module get_target_property(module_plugin_types ${target} MODULE_PLUGIN_TYPES) # Get depends for the current module qt_internal_android_dependencies_content(${target} file_contents) # Get plugins from the module's plugin types and get their dependencies foreach(plugin ${QT_KNOWN_PLUGINS}) get_target_property(iter_known_plugin_type ${plugin} QT_PLUGIN_TYPE) foreach(plugin_type ${module_plugin_types}) if (plugin_type STREQUAL iter_known_plugin_type) qt_internal_android_dependencies_content(${plugin} plugin_file_contents) string(APPEND file_contents ${plugin_file_contents}) endif() endforeach() endforeach() if ((NOT module_plugin_types) AND (NOT file_contents)) # None of the values were set, so there's nothing to do return() endif() get_target_property(target_output_name ${target} OUTPUT_NAME) if (NOT target_output_name) set(target_name ${target}) else() set(target_name ${target_output_name}) endif() string(PREPEND file_contents "\n") string(PREPEND file_contents "\n") # Module plugins if(module_plugin_types) foreach(plugin IN LISTS module_plugin_types) string(APPEND file_contents "\n") endforeach() endif() string(APPEND file_contents "\n") string(APPEND file_contents "") qt_path_join(dependency_file "${QT_BUILD_DIR}" "${INSTALL_LIBDIR}" "${target_name}_${CMAKE_ANDROID_ARCH_ABI}-android-dependencies.xml") file(WRITE ${dependency_file} ${file_contents}) get_target_property(target_install_dir ${target} QT_ANDROID_MODULE_INSTALL_DIR) if (NOT target_install_dir) message(SEND_ERROR "qt_internal_android_dependencies: Target ${target} is either not a Qt Module or has no recorded install location") return() endif() # Copy file into install directory, required by the androiddeployqt tool. qt_install(FILES ${dependency_file} DESTINATION ${target_install_dir} COMPONENT Devel) endfunction() function(qt_internal_set_up_build_host_java_docs) if("${ANDROID_SDK_ROOT}" STREQUAL "") message(FATAL_ERROR "QT_HOST_DOCUMENT_JAVA_SOURCES=ON requires setting ANDROID_SDK_ROOT." ) endif() _qt_internal_locate_android_jar() set(QT_ANDROID_JAR "${QT_ANDROID_JAR}" PARENT_SCOPE) set(QT_ANDROID_API_USED_FOR_JAVA "${QT_ANDROID_API_USED_FOR_JAVA}" PARENT_SCOPE) endfunction() # Collect the Java source files that were recorded by qt_internal_add_jar. # If we're not building for Android, qt_internal_add_jar is not called, and we simple collect # all java files under the current directory. function(qt_internal_collect_jar_sources out_var) if(NOT ANDROID) file(GLOB_RECURSE sources LIST_DIRECTORIES FALSE "*.java") set("${out_var}" "${sources}" PARENT_SCOPE) return() endif() set(no_value_options "") set(single_value_options DIRECTORY) set(multi_value_options "") cmake_parse_arguments(PARSE_ARGV 1 arg "${no_value_options}" "${single_value_options}" "${multi_value_options}" ) _qt_internal_validate_all_args_are_parsed(arg) set(directory_arg "") if(DEFINED arg_DIRECTORY) set(directory_arg DIRECTORY ${arg_DIRECTORY}) endif() get_directory_property(result ${directory_arg} _qt_jar_sources) get_directory_property(subdirs ${directory_arg} SUBDIRECTORIES) foreach(subdir IN LISTS subdirs) qt_internal_collect_jar_sources(subdir_result DIRECTORY ${subdir}) if(NOT "${subdir_result}" STREQUAL "") list(APPEND result ${subdir_result}) endif() endforeach() set("${out_var}" "${result}" PARENT_SCOPE) endfunction() function(qt_internal_add_javadoc_target) set(no_value_options "") set(single_value_options MODULE OUTPUT_DIR ) set(multi_value_options SOURCES ) cmake_parse_arguments(PARSE_ARGV 0 arg "${no_value_options}" "${single_value_options}" "${multi_value_options}" ) _qt_internal_validate_all_args_are_parsed(arg) if(TARGET ${arg_MODULE}) get_target_property(skip ${arg_MODULE} _qt_skip_javadoc) if(skip) message(VERBOSE "Skipping generation of Android HTML docs for ${arg_MODULE}.") return() endif() endif() # Collect source directories from source file paths. set(source_dirs "") foreach(source_path IN LISTS arg_SOURCES) get_filename_component(dir_path "${source_path}" DIRECTORY) list(APPEND source_dirs "${dir_path}") endforeach() list(REMOVE_DUPLICATES source_dirs) # Retrieve package names from source dirs. set(package_names "") foreach(source_dir IN LISTS source_dirs) string(REGEX MATCH "/(org/qtproject/qt/android(/.*|$))" package_dir "${source_dir}") if(package_dir STREQUAL "") message(VERBOSE "Java source dir is not a package directory: ${source_dir}") continue() endif() # Store package_dir without leading slash. set(package_dir "${CMAKE_MATCH_1}") # Use dots instead of slashes for the package name. string(REPLACE "/" "." package_name "${package_dir}") list(APPEND package_names "${package_name}") endforeach() # Strip package paths from the source dirs. list(TRANSFORM source_dirs REPLACE "/org/qtproject/qt/android.*" "") list(REMOVE_DUPLICATES source_dirs) # Use the correct separator for the --source-path argument. if(NOT CMAKE_HOST_WIN32) string(REPLACE ";" ":" source_dirs "${source_dirs}") endif() # Use a response file to avoid quoting issues with the space-separated package names. set(javadoc_output_dir "${arg_OUTPUT_DIR}/android") set(response_file "${CMAKE_CURRENT_BINARY_DIR}/doc/.javadocargs") string(REPLACE ";" " " package_names_space_separated "${package_names}") file(CONFIGURE OUTPUT "${response_file}" CONTENT "${package_names_space_separated} --class-path \"${QT_ANDROID_JAR}\" -d \"${javadoc_output_dir}\" --source-path \"${source_dirs}\"" ) set(module ${arg_MODULE}) set(javadoc_target android_html_docs_${module}) add_custom_target(${javadoc_target} ${command_args} COMMAND ${Java_JAVADOC_EXECUTABLE} "@${response_file}" COMMENT "Generating Java documentation" VERBATIM ) add_dependencies(docs_android ${javadoc_target}) if (QT_WILL_INSTALL) install(DIRECTORY "${arg_OUTPUT_DIR}/" DESTINATION "${INSTALL_DOCDIR}/${module}" COMPONENT _install_docs_android_${module} EXCLUDE_FROM_ALL ) add_custom_target(install_docs_android_${module} COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component _install_docs_android_${module} COMMENT "Installing Android html docs for ${module}" ) else() add_custom_target(install_docs_android_${module}) endif() add_dependencies(install_docs_android_${module} ${javadoc_target}) add_dependencies(install_docs_android install_docs_android_${module}) endfunction() function(qt_internal_create_source_jar) set(no_value_options "") set(single_value_options MODULE) set(multi_value_options SOURCES) cmake_parse_arguments(PARSE_ARGV 0 arg "${no_value_options}" "${single_value_options}" "${multi_value_options}" ) _qt_internal_validate_all_args_are_parsed(arg) set(module ${arg_MODULE}) set(jar_target android_source_jar_${module}) set(jar_name ${CMAKE_INSTALL_NAMESPACE}AndroidSources${module}) add_jar(${jar_target} SOURCES ${arg_SOURCES} VERSION ${PROJECT_VERSION} INCLUDE_JARS "${QT_ANDROID_JAR}" OUTPUT_NAME ${jar_name} ) set_target_properties(${jar_target} PROPERTIES EXCLUDE_FROM_ALL ON) add_dependencies(android_source_jars ${jar_target}) if(QT_WILL_INSTALL) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${jar_name}-${PROJECT_VERSION}.jar" DESTINATION "${INSTALL_DATADIR}/android/${module}" COMPONENT _install_android_source_jar_${module} EXCLUDE_FROM_ALL ) add_custom_target(install_android_source_jar_${module} COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component _install_android_source_jar_${module} COMMENT "Installing Android source jar for ${module}" ) else() add_custom_target(install_android_source_jar_${module}) endif() add_dependencies(install_android_source_jar_${module} ${jar_target}) add_dependencies(install_android_source_jars install_android_source_jar_${module}) endfunction()