CMake: Add targets for building Android docs from Java sources

One can now run "ninja android_docs" to generate HTML docs for Android
Java sources of Qt. This is done with javadoc.

One can now run "ninja android_source_jars" to generate source jars for
Android. They're supposed to be loaded into an IDE.

The targets are enabled automatically for Android builds.
For non-Android builds, set QT_BUILD_HOST_JAVA_DOCS=ON to create those
targets. In that case you must also set ANDROID_SDK_ROOT.

Fixes: QTBUG-117028
Change-Id: I2df51153359a95870c055d3ee373b8381f10cb51
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
This commit is contained in:
Joerg Bornemann 2025-03-28 09:00:01 +01:00
parent 89e7facc2e
commit eaf87cdfd8
10 changed files with 315 additions and 32 deletions

View File

@ -287,3 +287,189 @@ function(qt_internal_android_dependencies target)
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()

View File

@ -65,6 +65,11 @@ function(qt_auto_detect_wasm)
endfunction()
function(qt_auto_detect_android)
# Don't assume an Android build if we're requesting to build Java documentation on the host.
if(QT_BUILD_HOST_JAVA_DOCS)
return()
endif()
# We assume an Android build if any of the ANDROID_* cache variables are set.
if(DEFINED ANDROID_SDK_ROOT
OR DEFINED ANDROID_NDK_ROOT

View File

@ -449,6 +449,7 @@ macro(qt_internal_setup_build_and_global_variables)
qt_internal_setup_find_host_info_package()
qt_internal_setup_build_docs()
qt_internal_setup_build_java_docs_on_host()
qt_internal_include_qt_platform_android()

View File

@ -403,6 +403,16 @@ macro(qt_internal_setup_build_docs)
option(QT_BUILD_DOCS "Generate Qt documentation targets" ON)
endmacro()
macro(qt_internal_setup_build_java_docs_on_host)
option(QT_BUILD_HOST_JAVA_DOCS "Generate Java documentation targets on host" OFF)
if(QT_BUILD_HOST_JAVA_DOCS)
find_package(Java)
if(Java_FOUND)
include(UseJava)
endif()
endif()
endmacro()
macro(qt_internal_set_use_ccache)
option(QT_USE_CCACHE "Enable the use of ccache")
if(QT_USE_CCACHE)

View File

@ -79,6 +79,7 @@ function(qt_internal_add_docs)
set(opt_args
SHOW_INTERNAL
SKIP_JAVADOC
)
set(single_args "")
set(multi_args
@ -352,9 +353,9 @@ function(qt_internal_add_docs)
if (QT_WILL_INSTALL)
install(DIRECTORY "${qdoc_output_dir}/"
DESTINATION "${INSTALL_DOCDIR}/${doc_target}"
COMPONENT _install_html_docs_${target}
EXCLUDE_FROM_ALL
DESTINATION "${INSTALL_DOCDIR}/${doc_target}"
COMPONENT _install_html_docs_${target}
EXCLUDE_FROM_ALL
)
add_custom_target(install_html_docs_${target}
@ -365,9 +366,9 @@ function(qt_internal_add_docs)
)
install(FILES "${qch_file_path}"
DESTINATION "${INSTALL_DOCDIR}"
COMPONENT _install_qch_docs_${target}
EXCLUDE_FROM_ALL
DESTINATION "${INSTALL_DOCDIR}"
COMPONENT _install_qch_docs_${target}
EXCLUDE_FROM_ALL
)
add_custom_target(install_qch_docs_${target}
@ -403,4 +404,73 @@ function(qt_internal_add_docs)
qt_internal_add_doc_tool_dependency(prepare_docs_${target} qdoc)
qt_internal_add_doc_tool_dependency(qch_docs_${target} qhelpgenerator)
endif()
_qt_internal_forward_function_args(
FORWARD_PREFIX arg
FORWARD_OUT_VAR add_java_documentation_args
FORWARD_OPTIONS
SKIP_JAVADOC
)
qt_internal_add_java_documentation(${target} ${add_java_documentation_args}
OUTPUT_DIR "${qdoc_output_dir}"
)
endfunction()
function(qt_internal_add_java_documentation target)
if(NOT ANDROID AND NOT QT_BUILD_HOST_JAVA_DOCS)
return()
endif()
set(no_value_options
SKIP_JAVADOC
)
set(single_value_options
OUTPUT_DIR
)
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)
# Use a default output directory based on the project name.
if(NOT DEFINED arg_OUTPUT_DIR)
if (QT_WILL_INSTALL)
set(arg_OUTPUT_DIR "${CMAKE_BINARY_DIR}/${INSTALL_DOCDIR}")
else()
set(arg_OUTPUT_DIR "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_DOCDIR}")
endif()
string(APPEND arg_OUTPUT_DIR "/${PROJECT_NAME}")
endif()
qt_internal_collect_jar_sources(sources)
# Bail out if we haven't found relevant sources.
if(sources STREQUAL "")
return()
endif()
if(NOT TARGET docs_android)
add_custom_target(docs_android)
add_custom_target(install_docs_android)
add_dependencies(install_docs_android docs_android)
add_custom_target(android_source_jars)
add_custom_target(install_android_source_jars)
add_dependencies(install_android_source_jars android_source_jars)
endif()
if(NOT ANDROID)
qt_internal_set_up_build_host_java_docs()
endif()
if(NOT arg_SKIP_JAVADOC)
qt_internal_add_javadoc_target(
MODULE ${target}
SOURCES ${sources}
OUTPUT_DIR "${arg_OUTPUT_DIR}"
)
endif()
qt_internal_create_source_jar(SOURCES ${sources} MODULE ${target})
endfunction()

View File

@ -28,6 +28,7 @@ function(qt_internal_add_jar target)
get_filename_component(absolute_path "${path}" ABSOLUTE)
list(APPEND absolute_sources "${absolute_path}")
endforeach()
set_property(DIRECTORY APPEND PROPERTY _qt_jar_sources "${absolute_sources}")
add_jar(${target} SOURCES ${absolute_sources} ${ARGV})

View File

@ -23,32 +23,7 @@ if (NOT IS_DIRECTORY "${ANDROID_SDK_ROOT}")
message(FATAL_ERROR "Could not find ANDROID_SDK_ROOT or path is not a directory: ${ANDROID_SDK_ROOT}")
endif()
# This variable specifies the API level used for building Java code, it can be the same as Qt for
# Android's maximum supported Android version or higher.
if(NOT QT_ANDROID_API_USED_FOR_JAVA)
set(QT_ANDROID_API_USED_FOR_JAVA "android-35")
endif()
set(jar_location "${ANDROID_SDK_ROOT}/platforms/${QT_ANDROID_API_USED_FOR_JAVA}/android.jar")
if(NOT EXISTS "${jar_location}")
_qt_internal_detect_latest_android_platform(android_platform_latest)
if(android_platform_latest)
message(NOTICE "The default platform SDK ${QT_ANDROID_API_USED_FOR_JAVA} not found, "
"using the latest installed ${android_platform_latest} instead.")
set(QT_ANDROID_API_USED_FOR_JAVA ${android_platform_latest})
endif()
endif()
set(QT_ANDROID_JAR "${ANDROID_SDK_ROOT}/platforms/${QT_ANDROID_API_USED_FOR_JAVA}/android.jar")
if(NOT EXISTS "${QT_ANDROID_JAR}")
message(FATAL_ERROR
"No suitable Android SDK platform found in '${ANDROID_SDK_ROOT}/platforms'."
" The minimum version required for building Java code is ${QT_ANDROID_API_USED_FOR_JAVA}"
)
endif()
message(STATUS "Using Android SDK API ${QT_ANDROID_API_USED_FOR_JAVA} from "
"${ANDROID_SDK_ROOT}/platforms")
_qt_internal_locate_android_jar()
# Locate Java
include(UseJava)

View File

@ -45,3 +45,36 @@ function(_qt_internal_sort_android_platforms out_var)
endif()
set("${out_var}" "${platforms}" PARENT_SCOPE)
endfunction()
function(_qt_internal_locate_android_jar)
# This variable specifies the API level used for building Java code, it can be the same as Qt
# for Android's maximum supported Android version or higher.
if(NOT QT_ANDROID_API_USED_FOR_JAVA)
set(QT_ANDROID_API_USED_FOR_JAVA "android-35")
endif()
set(jar_location "${ANDROID_SDK_ROOT}/platforms/${QT_ANDROID_API_USED_FOR_JAVA}/android.jar")
if(NOT EXISTS "${jar_location}")
_qt_internal_detect_latest_android_platform(android_platform_latest)
if(android_platform_latest)
message(NOTICE "The default platform SDK ${QT_ANDROID_API_USED_FOR_JAVA} not found, "
"using the latest installed ${android_platform_latest} instead.")
set(QT_ANDROID_API_USED_FOR_JAVA ${android_platform_latest})
endif()
endif()
set(QT_ANDROID_JAR "${ANDROID_SDK_ROOT}/platforms/${QT_ANDROID_API_USED_FOR_JAVA}/android.jar")
if(NOT EXISTS "${QT_ANDROID_JAR}")
message(FATAL_ERROR
"No suitable Android SDK platform found in '${ANDROID_SDK_ROOT}/platforms'."
" The minimum version required for building Java code is ${QT_ANDROID_API_USED_FOR_JAVA}"
)
endif()
message(STATUS "Using Android SDK API ${QT_ANDROID_API_USED_FOR_JAVA} from "
"${ANDROID_SDK_ROOT}/platforms")
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()

View File

@ -11,3 +11,4 @@ if (ANDROID)
add_subdirectory(templates_aar)
endif()
qt_internal_add_java_documentation(Global)

View File

@ -385,6 +385,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_opens
qt_internal_add_docs(Network
doc/qtnetwork.qdocconf
SKIP_JAVADOC
)
# See mkspecs/common/msvc-desktop.conf