diff options
Diffstat (limited to 'cmake/QtSyncQtHelpers.cmake')
-rw-r--r-- | cmake/QtSyncQtHelpers.cmake | 465 |
1 files changed, 296 insertions, 169 deletions
diff --git a/cmake/QtSyncQtHelpers.cmake b/cmake/QtSyncQtHelpers.cmake index 2014405017..0188b87c6a 100644 --- a/cmake/QtSyncQtHelpers.cmake +++ b/cmake/QtSyncQtHelpers.cmake @@ -1,197 +1,324 @@ -function(qt_ensure_perl) - if(DEFINED HOST_PERL) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# The function generates the Qt module header structure in build directory and creates install +# rules. Apart the lists of header files the function takes into account +# QT_REPO_PUBLIC_NAMESPACE_REGEX cache variable, that can be set by repository in .cmake.conf file. +# The variable tells the syncqt program, what namespaces are treated as public. Symbols in public +# namespaces are considered when generating CaMeL case header files. +function(qt_internal_target_sync_headers target module_headers module_headers_generated) + if(NOT TARGET ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt) + message(FATAL_ERROR "${QT_CMAKE_EXPORT_NAMESPACE}::syncqt is not a target.") + endif() + get_target_property(has_headers ${target} _qt_module_has_headers) + if(NOT has_headers) return() endif() - find_program(HOST_PERL "perl" DOC "Perl binary") - if (NOT HOST_PERL) - message(FATAL_ERROR "Perl needs to be available to build Qt.") + + qt_internal_module_info(module "${target}") + + get_target_property(sync_source_directory ${target} _qt_sync_source_directory) + set(syncqt_timestamp "${CMAKE_CURRENT_BINARY_DIR}/${target}_syncqt_timestamp") + set(syncqt_outputs "${syncqt_timestamp}") + + set(is_interface_lib FALSE) + get_target_property(type ${target} TYPE) + if(type STREQUAL "INTERFACE_LIBRARY") + set(is_interface_lib TRUE) endif() -endfunction() -function(qt_ensure_sync_qt) - qt_ensure_perl() - if(DEFINED QT_SYNCQT) - return() + set(version_script_private_content_file "") + if(NOT is_interface_lib) + list(APPEND syncqt_outputs + "${module_build_interface_include_dir}/${module}Version" + "${module_build_interface_include_dir}/qt${module_lower}version.h") + if(TEST_ld_version_script) + set(version_script_private_content_file + "${CMAKE_CURRENT_BINARY_DIR}/${target}.version.private_content") + set(version_script_args + "-versionScript" "${version_script_private_content_file}") + list(APPEND syncqt_outputs "${version_script_private_content_file}") + qt_internal_add_linker_version_script(${target} + PRIVATE_CONTENT_FILE "${version_script_private_content_file}") + endif() endif() - # When building qtbase, use the source syncqt, otherwise use the installed one. - set(SYNCQT_FROM_SOURCE "${QtBase_SOURCE_DIR}/libexec/syncqt.pl") - if(NOT ("${QtBase_SOURCE_DIR}" STREQUAL "") AND EXISTS "${SYNCQT_FROM_SOURCE}") - set(QT_SYNCQT "${SYNCQT_FROM_SOURCE}" CACHE FILEPATH "syncqt script") - message(STATUS "Using source syncqt found at: ${QT_SYNCQT}") - - qt_path_join(syncqt_install_dir ${QT_INSTALL_DIR} ${INSTALL_LIBEXECDIR}) - qt_copy_or_install(PROGRAMS "${SYNCQT_FROM_SOURCE}" - DESTINATION "${syncqt_install_dir}") - elseif(NOT "${QT_HOST_PATH}" STREQUAL "") - get_filename_component(syncqt_absolute_path - "${QT_HOST_PATH}/${QT${PROJECT_VERSION_MAJOR}_HOST_INFO_LIBEXECDIR}/syncqt.pl" - ABSOLUTE) - set(QT_SYNCQT "${syncqt_absolute_path}" CACHE FILEPATH "syncqt script") - message(STATUS "Using host syncqt found at: ${QT_SYNCQT}") + # Check for _qt_module_is_3rdparty_header_library flag to detect non-Qt modules and + # indicate this to syncqt. + get_target_property(is_3rd_party_library ${target} _qt_module_is_3rdparty_header_library) + set(non_qt_module_argument "") + if(is_3rd_party_library) + set(non_qt_module_argument "-nonQt") else() - get_filename_component(syncqt_absolute_path - "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_LIBEXECDIR}/syncqt.pl" - ABSOLUTE) - set(QT_SYNCQT "${syncqt_absolute_path}" CACHE FILEPATH "syncqt script") - message(STATUS "Using installed syncqt found at: ${QT_SYNCQT}") + list(APPEND syncqt_outputs "${module_build_interface_include_dir}/${module}") + get_target_property(no_headersclean_check ${target} _qt_no_headersclean_check) + if(NOT no_headersclean_check) + list(APPEND syncqt_outputs + "${CMAKE_CURRENT_BINARY_DIR}/${module}_header_check_exceptions") + endif() endif() -endfunction() -function(qt_install_injections target build_dir install_dir) - set(injections ${ARGN}) - set(module "Qt${target}") - get_target_property(target_type ${target} TYPE) - if (target_type STREQUAL "INTERFACE_LIBRARY") - set(is_framework FALSE) - else() + set(is_framework FALSE) + if(NOT is_interface_lib) get_target_property(is_framework ${target} FRAMEWORK) endif() - # examples: - # SYNCQT.INJECTIONS = src/corelib/global/qconfig.h:qconfig.h:QtConfig src/corelib/global/qconfig_p.h:5.12.0/QtCore/private/qconfig_p.h - # SYNCQT.INJECTIONS = src/gui/vulkan/qvulkanfunctions.h:^qvulkanfunctions.h:QVulkanFunctions:QVulkanDeviceFunctions src/gui/vulkan/qvulkanfunctions_p.h:^5.12.0/QtGui/private/qvulkanfunctions_p.h - # The are 3 parts to the assignment, divded by colons ':'. - # The first part contains a path to a generated file in a build folder. - # The second part contains the file name that the forwarding header should have, which points - # to the file in the first part. - # The third part contains multiple UpperCaseFileNames that should be forwarding headers to the - # header specified in the second part. - separate_arguments(injections UNIX_COMMAND "${injections}") - foreach(injection ${injections}) - string(REPLACE ":" ";" injection ${injection}) - # Part 1. - list(GET injection 0 file) - # Part 2. - list(GET injection 1 destination) - string(REGEX REPLACE "^\\^" "" destination "${destination}") - list(REMOVE_AT injection 0 1) - # Part 3. - set(fwd_hdrs ${injection}) - get_filename_component(destinationdir ${destination} DIRECTORY) - get_filename_component(destinationname ${destination} NAME) - get_filename_component(original_file_name ${file} NAME) - - # This describes a concrete example for easier comprehension: - # A file 'qtqml-config.h' is generated by qt_internal_feature_write_file into - # ${qtdeclarative_build_dir}/src/{module}/qtqml-config.h (part 1). - # - # Generate a lower case forwarding header (part 2) 'qtqml-config.h' at the following - # location: - # ${some_prefix}/include/${module}/qtqml-config.h. - # - # Inside this file, we #include the originally generated file, - # ${qtdeclarative_build_dir}/src/{module}/qtqml-config.h. - # - # ${some_prefix}'s value depends on the build type. - # If doing a prefix build, it should point to - # ${current_repo_build_dir} which is ${qtdeclarative_build_dir}. - # If doing a non-prefix build, it should point to - # ${qtbase_build_dir}. - # - # In the code below, ${some_prefix} == ${build_dir}. - set(lower_case_forwarding_header_path "${build_dir}/include/${module}") - if(destinationdir) - string(APPEND lower_case_forwarding_header_path "/${destinationdir}") - endif() - set(current_repo_build_dir "${PROJECT_BINARY_DIR}") - file(RELATIVE_PATH relpath - "${lower_case_forwarding_header_path}" - "${current_repo_build_dir}/${file}") - set(main_contents "#include \"${relpath}\"") + qt_internal_get_qt_all_known_modules(known_modules) - qt_configure_file(OUTPUT "${lower_case_forwarding_header_path}/${original_file_name}" - CONTENT "${main_contents}") + get_target_property(is_internal_module ${target} _qt_is_internal_module) + set(internal_module_argument "") + if(is_internal_module) + set(internal_module_argument "-internal") + endif() - if(is_framework) - if(file MATCHES "_p\\.h$") - set(header_type PRIVATE) - else() - set(header_type PUBLIC) - endif() - qt_copy_framework_headers(${target} ${header_type} - ${current_repo_build_dir}/${file}) + get_target_property(qpa_filter_regex ${target} _qt_module_qpa_headers_filter_regex) + get_target_property(rhi_filter_regex ${target} _qt_module_rhi_headers_filter_regex) + get_target_property(ssg_filter_regex ${target} _qt_module_ssg_headers_filter_regex) + get_target_property(private_filter_regex ${target} _qt_module_private_headers_filter_regex) + + # We need to use the real paths since otherwise it may lead to the invalid work of the + # std::filesystem API + get_filename_component(source_dir_real "${sync_source_directory}" REALPATH) + get_filename_component(binary_dir_real "${CMAKE_CURRENT_BINARY_DIR}" REALPATH) + + if(QT_REPO_PUBLIC_NAMESPACE_REGEX) + set(public_namespaces_filter -publicNamespaceFilter "${QT_REPO_PUBLIC_NAMESPACE_REGEX}") + endif() + + if(qpa_filter_regex) + set(qpa_filter_argument + -qpaHeadersFilter "${qpa_filter_regex}" + ) + endif() + + if(rhi_filter_regex) + set(rhi_filter_argument + -rhiHeadersFilter "${rhi_filter_regex}" + ) + endif() + + if(ssg_filter_regex) + set(ssg_filter_argument + -ssgHeadersFilter "${ssg_filter_regex}" + ) + endif() + + set(common_syncqt_arguments + -module "${module}" + -sourceDir "${source_dir_real}" + -binaryDir "${binary_dir_real}" + -privateHeadersFilter "${private_filter_regex}" + -includeDir "${module_build_interface_include_dir}" + -privateIncludeDir "${module_build_interface_private_include_dir}" + -qpaIncludeDir "${module_build_interface_qpa_include_dir}" + -rhiIncludeDir "${module_build_interface_rhi_include_dir}" + -ssgIncludeDir "${module_build_interface_ssg_include_dir}" + -generatedHeaders ${module_headers_generated} + ${qpa_filter_argument} + ${rhi_filter_argument} + ${ssg_filter_argument} + ${public_namespaces_filter} + ${non_qt_module_argument} + ${internal_module_argument} + ) + + if(QT_INTERNAL_ENABLE_SYNCQT_DEBUG_OUTPUT) + list(APPEND common_syncqt_arguments -debug) + endif() + + set(build_time_syncqt_arguments "") + if(WARNINGS_ARE_ERRORS) + if(is_interface_lib) + set(warnings_are_errors_enabled_genex 1) else() - # Copy the actual injected (generated) header file (not the just created forwarding one) - # to its install location when doing a prefix build. In an non-prefix build, the qt_install - # will be a no-op. - qt_path_join(install_destination - ${install_dir} ${INSTALL_INCLUDEDIR} ${module} ${destinationdir}) - qt_install(FILES ${current_repo_build_dir}/${file} - DESTINATION ${install_destination} - RENAME ${destinationname} OPTIONAL) + set(warnings_are_errors_enabled_genex + "$<NOT:$<BOOL:$<TARGET_PROPERTY:${target},QT_SKIP_WARNINGS_ARE_ERRORS>>>") endif() + list(APPEND build_time_syncqt_arguments + "$<${warnings_are_errors_enabled_genex}:-warningsAreErrors>") + endif() - # Generate UpperCaseNamed forwarding headers (part 3). - foreach(fwd_hdr ${fwd_hdrs}) - set(upper_case_forwarding_header_path "include/${module}") - if(destinationdir) - string(APPEND upper_case_forwarding_header_path "/${destinationdir}") - endif() + if(is_framework) + list(REMOVE_ITEM module_headers "${CMAKE_CURRENT_BINARY_DIR}/${target}_fake_header.h") + endif() + + # Filter the generated ui_ header files and header files located in the 'doc/' subdirectory. + list(FILTER module_headers EXCLUDE REGEX + "(.+/(ui_)[^/]+\\.h|${CMAKE_CURRENT_SOURCE_DIR}(/.+)?/doc/+\\.h)") + + set(syncqt_staging_dir "${module_build_interface_include_dir}/.syncqt_staging") - # Generate upper case forwarding header like QVulkanFunctions or QtConfig. - qt_configure_file(OUTPUT "${build_dir}/${upper_case_forwarding_header_path}/${fwd_hdr}" - CONTENT "#include \"${destinationname}\"\n") - - if(is_framework) - # Copy the forwarding header to the framework's Headers directory. - qt_copy_framework_headers(${target} PUBLIC - "${build_dir}/${upper_case_forwarding_header_path}/${fwd_hdr}") - else() - # Install the forwarding header. - qt_path_join(install_destination "${install_dir}" "${INSTALL_INCLUDEDIR}" ${module}) - qt_install(FILES "${build_dir}/${upper_case_forwarding_header_path}/${fwd_hdr}" - DESTINATION ${install_destination} OPTIONAL) + set(syncqt_args "${common_syncqt_arguments}") + list(APPEND syncqt_args + -headers ${module_headers} + -stagingDir "${syncqt_staging_dir}" + -knownModules ${known_modules} + ${version_script_args} + ) + list(JOIN syncqt_args "\n" syncqt_args_string) + set(syncqt_args_rsp "${binary_dir_real}/${target}_syncqt_args") + qt_configure_file(OUTPUT "${syncqt_args_rsp}" CONTENT "${syncqt_args_string}") + + get_target_property(external_headers_dir ${target} _qt_external_headers_dir) + if(external_headers_dir) + if(NOT IS_ABSOLUTE "${external_headers_dir}") + get_filename_component(external_headers_dir "${external_headers_dir}" ABSOLUTE) + endif() + if(EXISTS "${external_headers_dir}") + set(external_headers_dir_copy_cmd + COMMAND + ${CMAKE_COMMAND} + -E copy_directory + "${external_headers_dir}" + "${module_build_interface_include_dir}" + ) + endif() + endif() + add_custom_command( + OUTPUT + ${syncqt_outputs} + COMMAND + ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt + "@${syncqt_args_rsp}" + ${build_time_syncqt_arguments} + ${external_headers_dir_copy_cmd} + COMMAND + ${CMAKE_COMMAND} -E touch "${syncqt_timestamp}" + DEPENDS + ${syncqt_args_rsp} + ${module_headers} + ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt + "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_internal_sync_headers_deps>>" + COMMENT + "Running syncqt.cpp for module: ${module}" + VERBATIM + ) + + set(add_sync_headers_to_all "") + if(is_interface_lib) + set(add_sync_headers_to_all ALL) + endif() + + add_custom_target(${target}_sync_headers + ${add_sync_headers_to_all} + DEPENDS + ${syncqt_outputs} + ) + add_dependencies(sync_headers ${target}_sync_headers) + set_target_properties(${target} + PROPERTIES _qt_internal_sync_headers_target ${target}_sync_headers) + + if(is_3rd_party_library) + add_dependencies(thirdparty_sync_headers ${target}_sync_headers) + endif() + # This target is required when building docs, to make all header files and their aliases + # available for qdoc. + # ${target}_sync_headers is added as dependency to make sure that + # ${target}_sync_all_public_headers is running after ${target}_sync_headers, when building docs. + set(syncqt_all_args "${common_syncqt_arguments};-all") + list(JOIN syncqt_all_args "\n" syncqt_all_args_string) + set(syncqt_all_args_rsp "${binary_dir_real}/${target}_syncqt_all_args") + qt_configure_file(OUTPUT "${syncqt_all_args_rsp}" CONTENT "${syncqt_all_args_string}") + add_custom_target(${target}_sync_all_public_headers + COMMAND + ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt + "@${syncqt_all_args_rsp}" + ${external_headers_dir_copy_cmd} + DEPENDS + ${module_headers} + ${syncqt_all_args_rsp} + ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt + ${target}_sync_headers + VERBATIM + ) + + if(NOT TARGET sync_all_public_headers) + add_custom_target(sync_all_public_headers) + endif() + add_dependencies(sync_all_public_headers ${target}_sync_all_public_headers) + + if(NOT is_3rd_party_library AND NOT is_framework AND module_headers) + # Install all the CaMeL style aliases of header files from the staging directory in one rule + qt_install(DIRECTORY "${syncqt_staging_dir}/" + DESTINATION "${module_install_interface_include_dir}" + ) + endif() + + if(NOT is_interface_lib) + set_property(TARGET ${target} + APPEND PROPERTY AUTOGEN_TARGET_DEPENDS "${target}_sync_headers") + endif() + add_dependencies(${target} "${target}_sync_headers") + + + get_target_property(private_module_target ${target} _qt_private_module_target_name) + if(private_module_target) + add_dependencies(${private_module_target} "${target}_sync_headers") + endif() + + # Run sync Qt first time at configure step to make all header files available for the code model + # of IDEs. + get_property(synced_modules GLOBAL PROPERTY _qt_synced_modules) + if(NOT "${module}" IN_LIST synced_modules AND QT_SYNC_HEADERS_AT_CONFIGURE_TIME) + message(STATUS "Running syncqt.cpp for module: ${module}") + get_target_property(syncqt_location ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt LOCATION) + execute_process( + COMMAND + ${syncqt_location} + "@${syncqt_args_rsp}" + RESULT_VARIABLE syncqt_result + OUTPUT_VARIABLE syncqt_output + ERROR_VARIABLE syncqt_output + ) + if(NOT syncqt_result EQUAL 0) + if(syncqt_output STREQUAL "") + string(JOIN "" syncqt_output "The syncqt process exited with code ${syncqt_result}" + " and without any useful output. This can happen if syncqt crashes due to the" + " incompatibilities with the standard C++ library located by either PATH or" + " LD_LIBRARY_PATH environment variables. Please make sure that PATH or" + " LD_LIBRARY_PATH don't point to the standard libraries different from the one you" + " use for building Qt.") endif() - endforeach() - endforeach() + message(FATAL_ERROR + "syncqt.cpp failed for module ${module}:\n${syncqt_output}") + endif() + if(syncqt_output) + message(WARNING "${syncqt_output}") + endif() + set_property(GLOBAL APPEND PROPERTY _qt_synced_modules ${module}) + endif() endfunction() -function(qt_read_headers_pri module_include_dir resultVarPrefix) - file(STRINGS "${module_include_dir}/headers.pri" headers_pri_contents) - foreach(line ${headers_pri_contents}) - if("${line}" MATCHES "SYNCQT.HEADER_FILES = (.*)") - set(public_module_headers "${CMAKE_MATCH_1}") - separate_arguments(public_module_headers UNIX_COMMAND "${public_module_headers}") - elseif("${line}" MATCHES "SYNCQT.PRIVATE_HEADER_FILES = (.*)") - set(private_module_headers "${CMAKE_MATCH_1}") - separate_arguments(private_module_headers UNIX_COMMAND "${private_module_headers}") - elseif("${line}" MATCHES "SYNCQT.GENERATED_HEADER_FILES = (.*)") - set(generated_module_headers "${CMAKE_MATCH_1}") - separate_arguments(generated_module_headers UNIX_COMMAND "${generated_module_headers}") - foreach(generated_header ${generated_module_headers}) - list(APPEND public_module_headers "${module_include_dir}/${generated_header}") - endforeach() - elseif("${line}" MATCHES "SYNCQT.INJECTIONS = (.*)") - set(injections "${CMAKE_MATCH_1}") - elseif("${line}" MATCHES "SYNCQT.([A-Z_]+)_HEADER_FILES = (.+)") - set(prefix "${CMAKE_MATCH_1}") - string(TOLOWER "${prefix}" prefix) - set(entries "${CMAKE_MATCH_2}") - separate_arguments(entries UNIX_COMMAND "${entries}") - set("${resultVarPrefix}_${prefix}" "${entries}" PARENT_SCOPE) +function(qt_internal_collect_sync_header_dependencies out_var skip_non_existing) + list(LENGTH ARGN sync_headers_target_count) + if(sync_headers_target_count EQUAL 0) + message(FATAL_ERROR "Invalid use of qt_internal_collect_sync_header_dependencies," + " dependencies are not specified") + endif() + + set(${out_var} "") + foreach(sync_headers_target IN LISTS ARGN) + set(sync_headers_target "${sync_headers_target}_sync_headers") + if(NOT skip_non_existing OR TARGET ${sync_headers_target}) + list(APPEND ${out_var} ${sync_headers_target}) endif() endforeach() - set(${resultVarPrefix}_public "${public_module_headers}" PARENT_SCOPE) - set(${resultVarPrefix}_private "${private_module_headers}" PARENT_SCOPE) - set(${resultVarPrefix}_injections "${injections}" PARENT_SCOPE) -endfunction() - -function(qt_compute_injection_forwarding_header target) - qt_parse_all_arguments(arg "qt_compute_injection_forwarding_header" - "PRIVATE" "SOURCE;OUT_VAR" "" ${ARGN}) - qt_internal_module_info(module "${target}") - get_filename_component(file_name "${arg_SOURCE}" NAME) + list(REMOVE_DUPLICATES ${out_var}) - set(source_absolute_path "${CMAKE_CURRENT_BINARY_DIR}/${arg_SOURCE}") - file(RELATIVE_PATH relpath "${PROJECT_BINARY_DIR}" "${source_absolute_path}") + set(${out_var} "${${out_var}}" PARENT_SCOPE) +endfunction() - if (arg_PRIVATE) - set(fwd "${PROJECT_VERSION}/${module}/private/${file_name}") - else() - set(fwd "${file_name}") +function(qt_internal_add_sync_header_dependencies target) + qt_internal_collect_sync_header_dependencies(sync_headers_targets FALSE ${ARGN}) + if(sync_headers_targets) + add_dependencies(${target} ${sync_headers_targets}) endif() +endfunction() - string(APPEND ${arg_OUT_VAR} " ${relpath}:${fwd}") - set(${arg_OUT_VAR} ${${arg_OUT_VAR}} PARENT_SCOPE) +function(qt_internal_add_autogen_sync_header_dependencies target) + qt_internal_collect_sync_header_dependencies(sync_headers_targets TRUE ${ARGN}) + foreach(sync_headers_target IN LISTS sync_headers_targets) + set_property(TARGET ${target} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS + "${sync_headers_target}") + endforeach() endfunction() |