# Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause function(qt_ensure_perl) find_program(HOST_PERL "perl" DOC "Perl binary") if (NOT HOST_PERL) message(FATAL_ERROR "Perl needs to be available to build Qt.") endif() endfunction() function(qt_ensure_sync_qt) qt_ensure_perl() if(DEFINED QT_SYNCQT) return() endif() get_property(QT_SYNCQT GLOBAL PROPERTY _qt_syncqt) if(NOT "${QT_SYNCQT}" STREQUAL "") set(QT_SYNCQT "${QT_SYNCQT}" PARENT_SCOPE) return() 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(syncqt_absolute_path "${SYNCQT_FROM_SOURCE}") message(STATUS "Using source syncqt found at: ${syncqt_absolute_path}") 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) message(STATUS "Using host syncqt found at: ${syncqt_absolute_path}") else() get_filename_component(syncqt_absolute_path "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_LIBEXECDIR}/syncqt.pl" ABSOLUTE) message(STATUS "Using installed syncqt found at: ${syncqt_absolute_path}") endif() set(QT_SYNCQT "${syncqt_absolute_path}" PARENT_SCOPE) set_property(GLOBAL PROPERTY _qt_syncqt "${syncqt_absolute_path}") endfunction() function(qt_install_injections target build_dir install_dir) set(injections ${ARGN}) qt_internal_module_info(module ${target}) get_target_property(target_type ${target} TYPE) if (target_type STREQUAL "INTERFACE_LIBRARY") set(is_framework FALSE) else() 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_include_name}/qtqml-config.h (part 1). # # Generate a lower case forwarding header (part 2) 'qtqml-config.h' at the following # location: # ${some_prefix}/include/${module_include_name}/qtqml-config.h. # # Inside this file, we #include the originally generated file, # ${qtdeclarative_build_dir}/src/{module_include_name}/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_include_name}") 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_configure_file(OUTPUT "${lower_case_forwarding_header_path}/${original_file_name}" CONTENT "${main_contents}") 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}) 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_include_name} ${destinationdir}) qt_install(FILES ${current_repo_build_dir}/${file} DESTINATION ${install_destination} RENAME ${destinationname} OPTIONAL) endif() # Generate UpperCaseNamed forwarding headers (part 3). foreach(fwd_hdr ${fwd_hdrs}) set(upper_case_forwarding_header_path "include/${module_include_name}") if(destinationdir) string(APPEND upper_case_forwarding_header_path "/${destinationdir}") endif() # 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_include_name}) qt_install(FILES "${build_dir}/${upper_case_forwarding_header_path}/${fwd_hdr}" DESTINATION ${install_destination} OPTIONAL) endif() endforeach() endforeach() 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() list(TRANSFORM generated_module_headers PREPEND "${module_include_dir}/") 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) endif() endforeach() set(${resultVarPrefix}_generated "${generated_module_headers}" PARENT_SCOPE) 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) set(source_absolute_path "${CMAKE_CURRENT_BINARY_DIR}/${arg_SOURCE}") file(RELATIVE_PATH relpath "${PROJECT_BINARY_DIR}" "${source_absolute_path}") if (arg_PRIVATE) set(fwd "${PROJECT_VERSION}/${module_include_name}/private/${file_name}") else() set(fwd "${file_name}") endif() string(APPEND ${arg_OUT_VAR} " ${relpath}:${fwd}") set(${arg_OUT_VAR} ${${arg_OUT_VAR}} PARENT_SCOPE) endfunction() # 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() 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() 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() # 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() list(APPEND syncqt_outputs "${module_build_interface_include_dir}/${module}") if(QT_FEATURE_headersclean) list(APPEND syncqt_outputs "${CMAKE_CURRENT_BINARY_DIR}/${module}_header_check_exceptions") endif() endif() set(is_framework FALSE) if(NOT is_interface_lib) get_target_property(is_framework ${target} FRAMEWORK) if(is_framework) qt_internal_get_framework_info(fw ${target}) get_target_property(fw_output_base_dir ${target} LIBRARY_OUTPUT_DIRECTORY) set(framework_args "-framework" "-frameworkIncludeDir" "${fw_output_base_dir}/${fw_versioned_header_dir}" ) endif() endif() qt_internal_get_qt_all_known_modules(known_modules) 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() get_target_property(qpa_filter_regex ${target} _qt_module_qpa_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() 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}" ${qpa_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) list(APPEND build_time_syncqt_arguments -warningsAreErrors) 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(module_headers_rsp "${binary_dir_real}/module_headers") list(JOIN module_headers "\n" module_headers_string) qt_configure_file_v2(OUTPUT "${module_headers_rsp}" CONTENT "${module_headers_string}") set(module_headers_generated_rsp "${binary_dir_real}/module_headers_generated") list(JOIN module_headers_generated "\n" module_headers_generated_string) qt_configure_file_v2(OUTPUT "${module_headers_generated_rsp}" CONTENT "${module_headers_generated_string}") set(syncqt_staging_dir "${module_build_interface_include_dir}/.syncqt_staging") add_custom_command( OUTPUT ${syncqt_outputs} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt ${common_syncqt_arguments} ${build_time_syncqt_arguments} -headers "@${module_headers_rsp}" -generatedHeaders "@${module_headers_generated_rsp}" -stagingDir "${syncqt_staging_dir}" -knownModules ${known_modules} ${framework_args} ${version_script_args} COMMAND ${CMAKE_COMMAND} -E touch "${syncqt_timestamp}" DEPENDS ${module_headers_rsp} ${module_headers_generated_rsp} ${module_headers} ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt COMMENT "Running syncqt.cpp for module: ${module}" VERBATIM ) add_custom_target(${target}_sync_headers DEPENDS ${syncqt_outputs} ) add_dependencies(sync_headers ${target}_sync_headers) # 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. add_custom_target(${target}_sync_all_public_headers COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt ${common_syncqt_arguments} -all DEPENDS ${module_headers} ${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) # 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) message(STATUS "Running syncqt.cpp for module: ${module}") get_target_property(syncqt_location ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt LOCATION) execute_process( COMMAND ${syncqt_location} ${common_syncqt_arguments} -headers "@${module_headers_rsp}" -generatedHeaders "@${module_headers_generated_rsp}" -stagingDir "${syncqt_staging_dir}" -knownModules ${known_modules} ${framework_args} RESULT_VARIABLE syncqt_result OUTPUT_VARIABLE syncqt_output ERROR_VARIABLE syncqt_output ) if(NOT syncqt_result EQUAL 0) message(FATAL_ERROR "syncqt.cpp failed for module ${module}:\n${syncqt_output}") endif() if(syncqt_output) message("${syncqt_output}") endif() set_property(GLOBAL APPEND PROPERTY _qt_synced_modules ${module}) endif() endfunction() function(qt_internal_collect_sync_header_dependencies out_var skip_non_existing) if(NOT QT_USE_SYNCQT_CPP) set(${out_var} "" PARENT_SCOPE) return() endif() 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() list(REMOVE_DUPLICATES ${out_var}) set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() 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() 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()