diff options
31 files changed, 2214 insertions, 182 deletions
diff --git a/.cmake.conf b/.cmake.conf index fce7707bcd..4f4c0a2017 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -31,3 +31,6 @@ set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_STATIC "3.21") # in sync. set(QT_MIN_NEW_POLICY_CMAKE_VERSION "3.16") set(QT_MAX_NEW_POLICY_CMAKE_VERSION "3.21") + +# Use cpp-based syncqt +set(QT_USE_SYNCQT_CPP TRUE) diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake index ba56ea1704..ac3b63a874 100644 --- a/cmake/QtBaseGlobalTargets.cmake +++ b/cmake/QtBaseGlobalTargets.cmake @@ -247,6 +247,7 @@ qt_copy_or_install(FILES cmake/QtLalrHelpers.cmake cmake/QtModuleConfig.cmake.in cmake/QtModuleDependencies.cmake.in + cmake/QtModuleHeadersCheck.cmake cmake/QtModuleHelpers.cmake cmake/QtModuleToolsConfig.cmake.in cmake/QtModuleToolsDependencies.cmake.in diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake index a99bb20c4a..2db677bef8 100644 --- a/cmake/QtBuild.cmake +++ b/cmake/QtBuild.cmake @@ -228,6 +228,15 @@ if(NOT QT_MKSPECS_DIR) set(QT_MKSPECS_DIR "${QT_MKSPECS_DIR}" CACHE INTERNAL "") endif() +# macOS versions 10.14 and less don't have the implementation of std::filesystem API. +if(CMAKE_HOST_APPLE AND CMAKE_HOST_SYSTEM_VERSION VERSION_LESS "19.0.0") + message(FATAL_ERROR "macOS versions less than 10.15 are not supported for building Qt.") +endif() + +if(NOT DEFINED QT_USE_SYNCQT_CPP) + set(QT_USE_SYNCQT_CPP FALSE) +endif() + # the default RPATH to be used when installing, but only if it's not a system directory list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}" isSystemDir) if("${isSystemDir}" STREQUAL "-1") diff --git a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake index 161c0bf5f1..fc978525a3 100644 --- a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake +++ b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake @@ -419,6 +419,10 @@ macro(qt_build_repo_begin) add_dependencies(install_docs install_html_docs install_qch_docs) endif() + if(NOT TARGET sync_headers) + add_custom_target(sync_headers) + endif() + # Add global qt_plugins, qpa_plugins and qpa_default_plugins convenience custom targets. # Internal executables will add a dependency on the qpa_default_plugins target, # so that building and running a test ensures it won't fail at runtime due to a missing qpa @@ -474,6 +478,10 @@ macro(qt_build_repo_begin) if(NOT TARGET benchmark) add_custom_target(benchmark) endif() + + if(QT_INTERNAL_SYNCED_MODULES) + set_property(GLOBAL PROPERTY _qt_synced_modules ${QT_INTERNAL_SYNCED_MODULES}) + endif() endmacro() macro(qt_build_repo_end) @@ -510,6 +518,12 @@ macro(qt_build_repo_end) if(NOT QT_SUPERBUILD) qt_print_build_instructions() endif() + + get_property(synced_modules GLOBAL PROPERTY _qt_synced_modules) + if(synced_modules) + set(QT_INTERNAL_SYNCED_MODULES ${synced_modules} CACHE INTERNAL + "List of the synced modules. Prevents running syncqt.cpp after the first configuring.") + endif() endmacro() macro(qt_build_repo) diff --git a/cmake/QtDocsHelpers.cmake b/cmake/QtDocsHelpers.cmake index 53d1ab4be1..04114cb921 100644 --- a/cmake/QtDocsHelpers.cmake +++ b/cmake/QtDocsHelpers.cmake @@ -140,6 +140,12 @@ function(qt_internal_add_docs) ) add_dependencies(prepare_docs_${target} qattributionsscanner_${target}) + if(QT_USE_SYNCQT_CPP) + if(NOT TARGET sync_all_public_headers) + add_custom_target(sync_all_public_headers) + endif() + add_dependencies(prepare_docs_${target} sync_all_public_headers) + endif() # generate docs target set(generate_qdoc_args diff --git a/cmake/QtExecutableHelpers.cmake b/cmake/QtExecutableHelpers.cmake index b8bccdbb07..4bd03c8f8c 100644 --- a/cmake/QtExecutableHelpers.cmake +++ b/cmake/QtExecutableHelpers.cmake @@ -429,7 +429,7 @@ function(qt_internal_add_configure_time_executable target) set(cmake_flags_arg) if(arg_CMAKE_FLAGS) - set(cmake_flags_arg CMAKE_FLAGS ${arg_CMAKE_FLAGS}) + set(cmake_flags_arg CMAKE_FLAGS "${arg_CMAKE_FLAGS}") endif() configure_file("${template}" "${target_binary_dir}/CMakeLists.txt" @ONLY) try_compile(result diff --git a/cmake/QtFrameworkHelpers.cmake b/cmake/QtFrameworkHelpers.cmake index 7effc579f6..3b4cb01223 100644 --- a/cmake/QtFrameworkHelpers.cmake +++ b/cmake/QtFrameworkHelpers.cmake @@ -97,6 +97,19 @@ function(qt_copy_framework_headers target) QT_COPIED_FRAMEWORK_HEADERS "${out_files}") endfunction() +function(qt_internal_generate_fake_framework_header target) + # Hack to create the "Headers" symlink in the framework: + # Create a fake header file and copy it into the framework by marking it as PUBLIC_HEADER. + # CMake now takes care of creating the symlink. + set(fake_header "${CMAKE_CURRENT_BINARY_DIR}/${target}_fake_header.h") + qt_internal_get_main_cmake_configuration(main_config) + file(GENERATE OUTPUT "${fake_header}" CONTENT "// ignore this file\n" + CONDITION "$<CONFIG:${main_config}>") + target_sources(${target} PRIVATE "${fake_header}") + set_source_files_properties("${fake_header}" PROPERTIES GENERATED ON) + set_property(TARGET ${target} APPEND PROPERTY PUBLIC_HEADER "${fake_header}") +endfunction() + function(qt_finalize_framework_headers_copy target) get_target_property(target_type ${target} TYPE) if(${target_type} STREQUAL "INTERFACE_LIBRARY") @@ -108,17 +121,7 @@ function(qt_finalize_framework_headers_copy target) endif() get_target_property(headers ${target} QT_COPIED_FRAMEWORK_HEADERS) if(headers) - # Hack to create the "Headers" symlink in the framework: - # Create a fake header file and copy it into the framework by marking it as PUBLIC_HEADER. - # CMake now takes care of creating the symlink. - set(fake_header ${target}_fake_header.h) - qt_internal_get_main_cmake_configuration(main_config) - file(GENERATE OUTPUT ${fake_header} CONTENT "// ignore this file\n" - CONDITION "$<CONFIG:${main_config}>") - string(PREPEND fake_header "${CMAKE_CURRENT_BINARY_DIR}/") - target_sources(${target} PRIVATE ${fake_header}) - set_source_files_properties(${fake_header} PROPERTIES GENERATED ON) - set_property(TARGET ${target} APPEND PROPERTY PUBLIC_HEADER ${fake_header}) + qt_internal_generate_fake_framework_header(${target}) # Add a target, e.g. Core_framework_headers, that triggers the header copy. add_custom_target(${target}_framework_headers DEPENDS ${headers}) diff --git a/cmake/QtHeadersClean.cmake b/cmake/QtHeadersClean.cmake index 29385de5fd..6300a81832 100644 --- a/cmake/QtHeadersClean.cmake +++ b/cmake/QtHeadersClean.cmake @@ -4,26 +4,18 @@ # Add a custom ${module_target}_headersclean_check target that builds each header in # ${module_headers} with a custom set of defines. This makes sure our public headers # are self-contained, and also compile with more strict compiler options. -function(qt_internal_add_headersclean_target - module_target - module_include_name - module_headers) - # module_headers is a list of strings of the form - # <headerfile>[:feature] +function(qt_internal_add_headersclean_target module_target module_headers) + get_target_property(has_headers ${module_target} _qt_module_has_headers) + if(NOT has_headers) + return() + endif() + set(hclean_headers "") - foreach(entry ${module_headers}) - string(REPLACE ":" ";" entry_list ${entry}) - list(LENGTH entry_list entry_list_length) - list(GET entry_list 0 entry_path) - - if (${entry_list_length} EQUAL 2) - list(GET entry_list 1 entry_feature) - if (NOT QT_FEATURE_${entry_feature}) - message(STATUS "headersclean: Ignoring header ${entry_path} because of missing feature ${entry_feature}") - continue() - endif() + foreach(header IN LISTS module_headers) + get_filename_component(header_name "${header}" NAME) + if(header_name MATCHES "^q[^_]+\\.h$" AND NOT header_name MATCHES ".*(global|exports)\\.h") + list(APPEND hclean_headers "${header}") endif() - list(APPEND hclean_headers ${entry_path}) endforeach() # Make sure that the header compiles with our strict options @@ -53,6 +45,12 @@ function(qt_internal_add_headersclean_target set(target_includes_joined_genex "$<${includes_exist_genex}:-I$<JOIN:${target_includes_genex},;-I>>") + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) + set(config_suffix "$<$<NOT:$<CONFIG:${first_config_type}>>:-$<CONFIG>>") + endif() + # qmake doesn't seem to add the defines that are set by the header_only_module when checking the # the cleanliness of the module's header files. # This allows us to bypass an error with CMake 3.18 and lower when trying to evaluate @@ -165,35 +163,23 @@ function(qt_internal_add_headersclean_target endforeach() endif() - foreach(header ${hclean_headers}) - get_filename_component(input_path "${header}" ABSOLUTE) - set(artifact_path "header_check/${header}.o") - get_filename_component(artifact_directory "${artifact_path}" DIRECTORY) - set(comment_header_path "${CMAKE_CURRENT_SOURCE_DIR}/${header}") - file(RELATIVE_PATH comment_header_path "${PROJECT_SOURCE_DIR}" "${comment_header_path}") - - add_custom_command( - OUTPUT "${artifact_path}" - COMMENT "headersclean: Checking header ${comment_header_path}" - COMMAND ${CMAKE_COMMAND} -E make_directory "${artifact_directory}" - COMMAND - ${compiler_to_run} -c ${cxx_flags} - "${target_compile_flags_joined_genex}" - "${target_defines_joined_genex}" - ${hcleanFLAGS} - "${target_includes_joined_genex}" - ${framework_includes} - ${hcleanDEFS} - -xc++ "${input_path}" - -o${artifact_path} - IMPLICIT_DEPENDS CXX - VERBATIM - COMMAND_EXPAND_LISTS - DEPENDS "${input_path}" - ) - list(APPEND hclean_artifacts "${artifact_path}") - endforeach() + set(compiler_command_line + "${compiler_to_run}" "-c" "${cxx_flags}" + "${target_compile_flags_joined_genex}" + "${target_defines_joined_genex}" + "${hcleanFLAGS}" + "${target_includes_joined_genex}" + "${framework_includes}" + "${hcleanDEFS}" + ) + string(JOIN " " compiler_command_line_variables + "-xc++" + "\${INPUT_HEADER_FILE}" + "-o" + "\${OUTPUT_ARTIFACT}" + ) + set(input_header_path_type ABSOLUTE) elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # -Za would enable strict standards behavior, but we can't add it because # <windows.h> and <GL.h> violate the standards. @@ -202,37 +188,87 @@ function(qt_internal_add_headersclean_target # cl.exe needs a source path get_filename_component(source_path "${QT_MKSPECS_DIR}/features/data/dummy.cpp" REALPATH) - foreach(header ${hclean_headers}) - # We need realpath here to make sure path starts with drive letter - get_filename_component(input_path "${header}" REALPATH) - set(artifact_path "header_${header}.o") - set(comment_header_path "${CMAKE_CURRENT_SOURCE_DIR}/${header}") - file(RELATIVE_PATH comment_header_path "${PROJECT_SOURCE_DIR}" "${comment_header_path}") - - add_custom_command( - OUTPUT "${artifact_path}" - COMMENT "headersclean: Checking header ${comment_header_path}" - COMMAND - ${compiler_to_run} -nologo -c ${CMAKE_CXX_FLAGS} - "${target_compile_flags_joined_genex}" - "${target_defines_joined_genex}" - ${hcleanFLAGS} - "${target_includes_joined_genex}" - ${hcleanDEFS} - -FI "${input_path}" - -Fo${artifact_path} "${source_path}" - IMPLICIT_DEPENDS CXX - VERBATIM - COMMAND_EXPAND_LISTS - DEPENDS "${input_path}" - ) - list(APPEND hclean_artifacts "${artifact_path}") - endforeach() + set(compiler_command_line + "${compiler_to_run}" "-nologo" "-c" "${CMAKE_CXX_FLAGS}" + "${target_compile_flags_joined_genex}" + "${target_defines_joined_genex}" + "${hcleanFLAGS}" + "${target_includes_joined_genex}" + "${hcleanDEFS}" + ) + string(JOIN " " compiler_command_line_variables + "-FI" + "\${INPUT_HEADER_FILE}" + "-Fo\${OUTPUT_ARTIFACT}" + "${source_path}" + ) + + set(input_header_path_type REALPATH) else() message(FATAL_ERROR "CMAKE_CXX_COMPILER_ID \"${CMAKE_CXX_COMPILER_ID}\" is not supported" " for the headersclean check.") endif() + get_target_property(module_include_name ${target} _qt_module_include_name) + + unset(header_check_exceptions) + if(QT_USE_SYNCQT_CPP) + set(header_check_exceptions + "${CMAKE_CURRENT_BINARY_DIR}/${module_include_name}_header_check_exceptions") + endif() + set(headers_check_parameters + "${CMAKE_CURRENT_BINARY_DIR}/${module_target}HeadersCheckParameters${config_suffix}.cmake") + string(JOIN "\n" headers_check_parameters_content + "set(HEADER_CHECK_EXCEPTIONS" + " \"${header_check_exceptions}\")" + "set(HEADER_CHECK_COMPILER_COMMAND_LINE" + " \[\[$<JOIN:${compiler_command_line},\]\]\n \[\[>\]\]\n" + " ${compiler_command_line_variables}" + ")" + ) + file(GENERATE OUTPUT "${headers_check_parameters}" + CONTENT "${headers_check_parameters_content}") + + set(sync_headers_dep "") + if(QT_USE_SYNCQT_CPP) + set(sync_headers_dep "sync_headers") + endif() + + foreach(header ${hclean_headers}) + # We need realpath here to make sure path starts with drive letter + get_filename_component(input_path "${header}" ${input_header_path_type}) + + get_filename_component(input_file_name ${input_path} NAME) + set(artifact_path "${CMAKE_CURRENT_BINARY_DIR}/header_check/${input_file_name}.o") + + if(input_path MATCHES "${CMAKE_BINARY_DIR}") + set(input_base_dir "${CMAKE_BINARY_DIR}") + elseif(input_path MATCHES "${CMAKE_SOURCE_DIR}") + set(input_base_dir "${CMAKE_SOURCE_DIR}") + endif() + file(RELATIVE_PATH comment_header_path "${input_base_dir}" "${input_path}") + + add_custom_command( + OUTPUT "${artifact_path}" + COMMENT "headersclean: Checking header ${comment_header_path}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/header_check" + COMMAND ${CMAKE_COMMAND} + -DINPUT_HEADER_FILE=${input_path} + -DOUTPUT_ARTIFACT=${artifact_path} + -DPARAMETERS=${headers_check_parameters} + -P "${QT_CMAKE_DIR}/QtModuleHeadersCheck.cmake" + IMPLICIT_DEPENDS CXX + VERBATIM + COMMAND_EXPAND_LISTS + DEPENDS + ${headers_check_parameters} + ${sync_headers_dep} + ${input_path} + ${header_check_exceptions} + ) + list(APPEND hclean_artifacts "${artifact_path}") + endforeach() + add_custom_target(${module_target}_headersclean_check COMMENT "headersclean: Checking headers in ${module_include_name}" DEPENDS ${hclean_artifacts} diff --git a/cmake/QtModuleHeadersCheck.cmake b/cmake/QtModuleHeadersCheck.cmake new file mode 100644 index 0000000000..d241f5bb55 --- /dev/null +++ b/cmake/QtModuleHeadersCheck.cmake @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16) +# The PARAMETERS file should specify the following variables for the correct work of +# this script: +# HEADER_CHECK_EXCEPTIONS - path to file that contains exceptions. +# The file is created by syncqt. +# +# HEADER_CHECK_COMPILER_COMMAND_LINE - compiler command line +include("${PARAMETERS}") + +if(EXISTS ${HEADER_CHECK_EXCEPTIONS}) + file(READ ${HEADER_CHECK_EXCEPTIONS} header_check_exception_list) +endif() + +file(TO_CMAKE_PATH "${INPUT_HEADER_FILE}" header) +foreach(exception IN LISTS header_check_exception_list) + file(TO_CMAKE_PATH "${exception}" exception) + if(exception STREQUAL header) + file(WRITE "${OUTPUT_ARTIFACT}" "skipped") + return() + endif() +endforeach() + +execute_process(COMMAND ${HEADER_CHECK_COMPILER_COMMAND_LINE} + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE output +) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "${INPUT_HEADER_FILE} header check" + " failed: ${HEADER_CHECK_COMPILER_COMMAND_LINE}\n" + " ${output}") +endif() diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index cf64e485e8..a5ff37d530 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -29,6 +29,7 @@ macro(qt_internal_get_internal_add_module_keywords option_args single_args multi EXTERNAL_HEADERS_DIR PRIVATE_HEADER_FILTERS QPA_HEADER_FILTERS + HEADER_SYNC_SOURCE_DIRECTORY ${__default_target_info_args} ) set(${multi_args} @@ -111,6 +112,12 @@ endfunction() # QPA_HEADER_FILTERS # The regular expressions that filter QPA header files out of target sources. # The value must use the following format 'regex1|regex2|regex3'. +# +# HEADER_SYNC_SOURCE_DIRECTORY +# The source directory for header sync procedure. Header files outside this directory will be +# ignored by syncqt. The specifying this directory allows to skip the parsing of the whole +# CMAKE_CURRENT_SOURCE_DIR for the header files that needs to be synced and only parse the +# single subdirectory, that meanwhile can be outside the CMAKE_CURRENT_SOURCE_DIR tree. function(qt_internal_add_module target) qt_internal_get_internal_add_module_keywords( module_option_args @@ -382,13 +389,18 @@ function(qt_internal_add_module target) else() set_property(TARGET ${target} APPEND PROPERTY EXPORT_PROPERTIES _qt_module_include_name) set_target_properties("${target}" PROPERTIES - _qt_module_include_name "${module_include_name}") + _qt_module_include_name "${module_include_name}" + _qt_module_has_headers ON + ) - # Use QT_BUILD_DIR for the syncqt call. - # So we either write the generated files into the qtbase non-prefix build root, or the - # module specific build root. + # Need to call qt_ensure_sync_qt to install syncqt.pl script. qt_ensure_sync_qt() - set(syncqt_full_command "${HOST_PERL}" -w "${QT_SYNCQT}" + # Repo uses old perl script to sync files. + if(NOT QT_USE_SYNCQT_CPP) + # Use QT_BUILD_DIR for the syncqt call. + # So we either write the generated files into the qtbase non-prefix build root, or the + # module specific build root. + set(syncqt_full_command "${HOST_PERL}" -w "${QT_SYNCQT}" -quiet -check-includes -module "${module_include_name}" @@ -396,18 +408,24 @@ function(qt_internal_add_module target) -outdir "${QT_BUILD_DIR}" -builddir "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") - message(STATUS "Running syncqt for module: '${module_include_name}' ") - execute_process(COMMAND ${syncqt_full_command} RESULT_VARIABLE syncqt_ret) - if(NOT syncqt_ret EQUAL 0) - message(FATAL_ERROR "Failed to run syncqt, return code: ${syncqt_ret}") - endif() - - set_target_properties("${target}" PROPERTIES - _qt_module_has_headers ON) - - ### FIXME: Can we replace headers.pri? - qt_read_headers_pri("${module_build_interface_include_dir}" "module_headers") + message(STATUS "Running syncqt for module: '${module_include_name}' ") + execute_process(COMMAND ${syncqt_full_command} RESULT_VARIABLE syncqt_ret) + if(NOT syncqt_ret EQUAL 0) + message(FATAL_ERROR "Failed to run syncqt, return code: ${syncqt_ret}") + endif() + ### FIXME: Can we replace headers.pri? + qt_read_headers_pri("${module_build_interface_include_dir}" "module_headers") + set_property(TARGET ${target} APPEND PROPERTY + _qt_module_timestamp_dependencies "${module_headers_generated}") + else() + set(sync_source_directory "${CMAKE_CURRENT_SOURCE_DIR}") + if(arg_HEADER_SYNC_SOURCE_DIRECTORY) + set(sync_source_directory "${arg_HEADER_SYNC_SOURCE_DIRECTORY}") + endif() + set_target_properties(${target} PROPERTIES + _qt_sync_source_directory "${sync_source_directory}") + endif() # We should not generate export headers if module is defined as pure STATIC. # Static libraries don't need to export their symbols, and corner cases when sources are # also used in shared libraries, should be handled manually. @@ -428,6 +446,9 @@ function(qt_internal_add_module target) set(module_depends_header "${module_build_interface_include_dir}/${module_include_name}Depends") + set_source_files_properties("${module_depends_header}" PROPERTIES GENERATED TRUE) + set_target_properties(${target} PROPERTIES _qt_module_depends_header + "${module_depends_header}") if(NOT ${arg_HEADER_MODULE}) set(module_header "${module_build_interface_include_dir}/${module_include_name}") set_property(TARGET "${target}" PROPERTY MODULE_HEADER @@ -455,15 +476,17 @@ function(qt_internal_add_module target) DESTINATION "${module_install_interface_include_dir}" ) else() - if(arg_EXTERNAL_HEADERS) - set(module_headers_public "${arg_EXTERNAL_HEADERS}") + if(NOT QT_USE_SYNCQT_CPP) + if(arg_EXTERNAL_HEADERS) + set(module_headers_public "${arg_EXTERNAL_HEADERS}") + endif() + qt_internal_install_module_headers(${target} + PUBLIC + ${module_headers_public} + "${module_depends_header}" + "${module_header}" + ) endif() - qt_internal_install_module_headers(${target} - PUBLIC - ${module_headers_public} - "${module_depends_header}" - "${module_header}" - ) endif() endif() @@ -650,7 +673,7 @@ function(qt_internal_add_module target) ) endif() - if(NOT arg_HEADER_MODULE) + if(NOT arg_HEADER_MODULE AND NOT QT_USE_SYNCQT_CPP) if(DEFINED module_headers_private) qt_internal_add_linker_version_script("${target}" PRIVATE_HEADERS ${module_headers_private} ${module_headers_qpa}) else() @@ -671,7 +694,7 @@ function(qt_internal_add_module target) string(APPEND final_injections "${extra_library_injections} ") endif() - if(final_injections) + if(final_injections AND NOT QT_USE_SYNCQT_CPP) qt_install_injections(${target} "${QT_BUILD_DIR}" "${QT_INSTALL_DIR}" ${final_injections}) endif() @@ -853,10 +876,9 @@ set(QT_LIBINFIX \"${QT_LIBINFIX}\")") endif() endif() - if(QT_FEATURE_headersclean AND NOT arg_NO_MODULE_HEADERS) + if(QT_FEATURE_headersclean AND NOT arg_NO_MODULE_HEADERS AND NOT QT_USE_SYNCQT_CPP) qt_internal_add_headersclean_target( ${target} - "${module_include_name}" "${module_headers_clean}") endif() @@ -890,16 +912,29 @@ endfunction() function(qt_finalize_module target) qt_internal_collect_module_headers(module_headers ${target}) - set_property(TARGET ${target} APPEND PROPERTY - _qt_module_timestamp_dependencies "${module_headers_public}") # qt_internal_install_module_headers needs to be called before # qt_finalize_framework_headers_copy, because the last uses the QT_COPIED_FRAMEWORK_HEADERS - # property which supposed to be updated inside every qt_internal_install_module_headers call. - qt_internal_install_module_headers(${target} - PRIVATE ${module_headers_private} - QPA ${module_headers_qpa} - ) + # property which supposed to be updated inside every qt_internal_install_module_headers + # call. + if(QT_USE_SYNCQT_CPP) + if(QT_FEATURE_headersclean) + qt_internal_add_headersclean_target(${target} "${module_headers_public}") + endif() + qt_internal_target_sync_headers(${target} "${module_headers_all}" + "${module_headers_generated}") + get_target_property(module_depends_header ${target} _qt_module_depends_header) + qt_internal_install_module_headers(${target} + PUBLIC ${module_headers_public} "${module_depends_header}" + PRIVATE ${module_headers_private} + QPA ${module_headers_qpa} + ) + else() + qt_internal_install_module_headers(${target} + PRIVATE ${module_headers_private} + QPA ${module_headers_qpa} + ) + endif() qt_finalize_framework_headers_copy(${target}) qt_generate_prl_file(${target} "${INSTALL_LIBDIR}") @@ -1098,6 +1133,13 @@ function(qt_internal_generate_cpp_global_exports target module_define_infix) set(${out_public_header} "${generated_header_path}" PARENT_SCOPE) target_sources(${target} PRIVATE "${generated_header_path}") + set_source_files_properties("${generated_header_path}" PROPERTIES GENERATED TRUE) + if(NOT QT_USE_SYNCQT_CPP) + qt_internal_install_module_headers(${target} + PUBLIC + "${generated_header_path}" + ) + endif() if(arg_GENERATE_PRIVATE_CPP_EXPORTS) set(generated_private_header_path @@ -1110,35 +1152,7 @@ function(qt_internal_generate_cpp_global_exports target module_define_infix) set(${out_private_header} "${generated_private_header_path}" PARENT_SCOPE) target_sources(${target} PRIVATE "${generated_private_header_path}") - endif() - - get_target_property(is_framework ${target} FRAMEWORK) - - get_target_property(target_type ${target} TYPE) - set(is_interface_lib 0) - if(target_type STREQUAL "INTERFACE_LIBRARY") - set(is_interface_lib 1) - endif() - - set_property(TARGET ${target} APPEND PROPERTY - _qt_module_timestamp_dependencies "${generated_header_path}") - - if(is_framework) - if(NOT is_interface_lib) - qt_copy_framework_headers(${target} PUBLIC "${generated_header_path}") - - if(arg_GENERATE_PRIVATE_CPP_EXPORTS) - qt_copy_framework_headers(${target} PRIVATE "${generated_private_header_path}") - endif() - endif() - else() - qt_install(FILES "${generated_header_path}" - DESTINATION "${module_install_interface_include_dir}") - - if(arg_GENERATE_PRIVATE_CPP_EXPORTS) - qt_install(FILES "${generated_private_header_path}" - DESTINATION "${module_install_interface_private_include_dir}") - endif() + set_source_files_properties("${generated_private_header_path}" PROPERTIES GENERATED TRUE) endif() endfunction() @@ -1180,10 +1194,9 @@ function(qt_internal_install_module_headers target) qt_install(FILES ${arg_PRIVATE} DESTINATION "${module_install_interface_private_include_dir}") endif() - endif() - - if(arg_QPA) - qt_install(FILES ${arg_QPA} DESTINATION "${module_install_interface_qpa_include_dir}") + if(arg_QPA) + qt_install(FILES ${arg_QPA} DESTINATION "${module_install_interface_qpa_include_dir}") + endif() endif() endfunction() @@ -1191,6 +1204,7 @@ function(qt_internal_collect_module_headers out_var target) set(${out_var}_public "") set(${out_var}_private "") set(${out_var}_qpa "") + set(${out_var}_all "") qt_internal_get_target_sources(sources ${target}) @@ -1203,6 +1217,7 @@ function(qt_internal_collect_module_headers out_var target) if(NOT file_name MATCHES ".+\\.h$") continue() endif() + get_source_file_property(is_generated "${file_path}" GENERATED) get_filename_component(file_path "${file_path}" ABSOLUTE) get_filename_component(file_path "${file_path}" REALPATH) list(APPEND ${out_var}_all "${file_path}") @@ -1213,6 +1228,9 @@ function(qt_internal_collect_module_headers out_var target) elseif(NOT public_filter OR file_name MATCHES "${public_filter}") list(APPEND ${out_var}_public "${file_path}") endif() + if(is_generated) + list(APPEND ${out_var}_generated "${file_path}") + endif() endforeach() set(header_types public private qpa) @@ -1226,6 +1244,8 @@ function(qt_internal_collect_module_headers out_var target) set(${out_var}_${header_type} "${${out_var}_${header_type}}" PARENT_SCOPE) endforeach() + set(${out_var}_all "${${out_var}_all}" PARENT_SCOPE) + set(${out_var}_generated "${${out_var}_generated}" PARENT_SCOPE) if(has_header_types_properties) set_target_properties(${target} PROPERTIES ${has_header_types_properties}) diff --git a/cmake/QtPluginHelpers.cmake b/cmake/QtPluginHelpers.cmake index 7b4f1ae669..aca5421221 100644 --- a/cmake/QtPluginHelpers.cmake +++ b/cmake/QtPluginHelpers.cmake @@ -276,6 +276,8 @@ function(qt_internal_add_plugin target) ) endif() endif() + + qt_internal_add_autogen_sync_header_dependencies(${target} ${qt_module_target}) endif() # Change the configuration file install location for qml plugins into the Qml package location. diff --git a/cmake/QtPostProcessHelpers.cmake b/cmake/QtPostProcessHelpers.cmake index 967413265c..0ca6bdbfa9 100644 --- a/cmake/QtPostProcessHelpers.cmake +++ b/cmake/QtPostProcessHelpers.cmake @@ -1,8 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -function(qt_internal_write_depends_file target module_include_name) - set(outfile "${QT_BUILD_DIR}/include/${module_include_name}/${module_include_name}Depends") +function(qt_internal_write_depends_file target) + get_target_property(module_depends_header ${target} _qt_module_depends_header) + set(outfile "${module_depends_header}") set(contents "/* This file was generated by cmake with the info from ${target} target. */\n") string(APPEND contents "#ifdef __cplusplus /* create empty PCH in C mode */\n") foreach (m ${ARGN}) @@ -249,8 +250,7 @@ function(qt_internal_create_module_depends_file target) get_target_property(hasModuleHeaders "${target}" _qt_module_has_headers) if (${hasModuleHeaders}) - get_target_property(module_include_name "${target}" _qt_module_include_name) - qt_internal_write_depends_file(${target} ${module_include_name} ${qtdeps}) + qt_internal_write_depends_file(${target} ${qtdeps}) endif() if(third_party_deps OR main_module_tool_deps OR target_deps) diff --git a/cmake/QtSyncQtHelpers.cmake b/cmake/QtSyncQtHelpers.cmake index 40f9cf8e25..fa7e2bf066 100644 --- a/cmake/QtSyncQtHelpers.cmake +++ b/cmake/QtSyncQtHelpers.cmake @@ -206,3 +206,269 @@ function(qt_compute_injection_forwarding_header target) 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() + + + 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} + -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 + "Unable to execute syncqt.cpp for module ${target}: ${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() diff --git a/cmake/QtToolHelpers.cmake b/cmake/QtToolHelpers.cmake index 4184610ecd..8af9e50d68 100644 --- a/cmake/QtToolHelpers.cmake +++ b/cmake/QtToolHelpers.cmake @@ -634,10 +634,11 @@ function(qt_internal_add_configure_time_tool target_name) set(extra_args "INSTALL_DIRECTORY" "${install_dir}") endif() + string(REPLACE "\\\;" "\\\\\\\;" unparsed_arguments "${arg_UNPARSED_ARGUMENTS}") qt_internal_add_configure_time_executable(${target_name} OUTPUT_NAME ${name} ${extra_args} - ${arg_UNPARSED_ARGUMENTS} + ${unparsed_arguments} ) if(NOT arg_NO_INSTALL AND arg_TOOLS_TARGET) diff --git a/qmake/CMakeLists.txt b/qmake/CMakeLists.txt index bb455d28df..8b74b85c49 100644 --- a/qmake/CMakeLists.txt +++ b/qmake/CMakeLists.txt @@ -15,6 +15,9 @@ qt_add_library(QtLibraryInfo OBJECT propertyprinter.cpp propertyprinter.h qmakelibraryinfo.cpp qmakelibraryinfo.h ) + +qt_internal_add_sync_header_dependencies(QtLibraryInfo Core) + set_target_properties(QtLibraryInfo PROPERTIES COMPILE_OPTIONS $<TARGET_PROPERTY:Qt::Core,INTERFACE_COMPILE_OPTIONS> COMPILE_DEFINITIONS $<TARGET_PROPERTY:Qt::Core,INTERFACE_COMPILE_DEFINITIONS> diff --git a/src/3rdparty/harfbuzz-ng/CMakeLists.txt b/src/3rdparty/harfbuzz-ng/CMakeLists.txt index d52a5b7c5b..b03ad1279c 100644 --- a/src/3rdparty/harfbuzz-ng/CMakeLists.txt +++ b/src/3rdparty/harfbuzz-ng/CMakeLists.txt @@ -70,6 +70,8 @@ qt_internal_add_3rdparty_library(BundledHarfbuzz $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/harfbuzz> ) +qt_internal_add_sync_header_dependencies(BundledHarfbuzz Core) + # GHS compiler doesn't support the __restrict keyword if(INTEGRITY) target_compile_definitions(BundledHarfbuzz PRIVATE __restrict=) diff --git a/src/3rdparty/zlib/CMakeLists.txt b/src/3rdparty/zlib/CMakeLists.txt index 701dab7dd3..ee9ece80fc 100644 --- a/src/3rdparty/zlib/CMakeLists.txt +++ b/src/3rdparty/zlib/CMakeLists.txt @@ -34,6 +34,8 @@ qt_internal_add_3rdparty_library(BundledZLIB $<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES> ) +qt_internal_add_sync_header_dependencies(BundledZLIB Core) + qt_disable_warnings(BundledZLIB) qt_set_symbol_visibility_hidden(BundledZLIB) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index df235433a2..0a2c955ffe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,8 @@ if(QT_FEATURE_gui) qt_feature_evaluate_features("${CMAKE_CURRENT_SOURCE_DIR}/gui/configure.cmake") endif() +add_subdirectory(tools/syncqt) + function(find_or_build_bootstrap_names) if (QT_WILL_BUILD_TOOLS) add_subdirectory(tools/bootstrap) # bootstrap library diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index cd698a57ef..1b41c659dc 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -377,10 +377,15 @@ endif() # additional json files. qt6_extract_metatypes(Core ${core_metatype_args}) -set_property(TARGET Core APPEND PROPERTY - PUBLIC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h") -set_property(TARGET Core APPEND PROPERTY - PRIVATE_HEADER "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h") +target_sources(Core PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h" + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h" +) +set_source_files_properties( + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig.h" + "${CMAKE_CURRENT_BINARY_DIR}/global/qconfig_p.h" + PROPERTIES GENERATED TRUE +) # Find ELF interpreter and define a macro for that: if ((LINUX OR HURD) AND NOT CMAKE_CROSSCOMPILING AND BUILD_SHARED_LIBS) diff --git a/src/corelib/global/qtconfigmacros.h b/src/corelib/global/qtconfigmacros.h index 0ff6e2955a..51f6e651bc 100644 --- a/src/corelib/global/qtconfigmacros.h +++ b/src/corelib/global/qtconfigmacros.h @@ -5,7 +5,10 @@ #define QTCONFIGMACROS_H #ifdef QT_BOOTSTRAPPED -#include <QtCore/qconfig-bootstrapped.h> +// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by +// the include path specified for Bootstrap library in the source tree instead of the build tree as +// it's done for regular header files. +#include "qconfig-bootstrapped.h" #else #include <QtCore/qconfig.h> #include <QtCore/qtcore-config.h> diff --git a/src/corelib/global/qtversionchecks.h b/src/corelib/global/qtversionchecks.h index d6fad1ed6c..8f3bd8b371 100644 --- a/src/corelib/global/qtversionchecks.h +++ b/src/corelib/global/qtversionchecks.h @@ -10,7 +10,10 @@ #endif #ifdef QT_BOOTSTRAPPED -#include <QtCore/qconfig-bootstrapped.h> +// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by +// the include path specified for Bootstrap library in the source tree instead of the build tree as +// it's done for regular header files. +#include "qconfig-bootstrapped.h" #else #include <QtCore/qconfig.h> #include <QtCore/qtcore-config.h> diff --git a/src/entrypoint/CMakeLists.txt b/src/entrypoint/CMakeLists.txt index 81a68aff04..845ce419ea 100644 --- a/src/entrypoint/CMakeLists.txt +++ b/src/entrypoint/CMakeLists.txt @@ -109,6 +109,8 @@ if(WIN32) target_compile_definitions(EntryPointPrivate INTERFACE QT_NEEDS_QMAIN) qt_internal_extend_target(EntryPointImplementation DEFINES QT_NEEDS_QMAIN) endif() + + qt_internal_add_sync_header_dependencies(EntryPointImplementation Core) endif() if(CMAKE_SYSTEM_NAME STREQUAL "iOS") diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index f6c2b2979a..b611724835 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -887,7 +887,6 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_filesystemmodel qt_internal_extend_target(Gui CONDITION QT_FEATURE_vulkan SOURCES rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h - rhi/qrhivulkan_p_p.h rhi/qrhivulkanext_p.h vulkan/qbasicvulkanplatforminstance.cpp vulkan/qbasicvulkanplatforminstance_p.h vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h diff --git a/src/plugins/platforms/eglfs/CMakeLists.txt b/src/plugins/platforms/eglfs/CMakeLists.txt index 8972e7fa26..108900518c 100644 --- a/src/plugins/platforms/eglfs/CMakeLists.txt +++ b/src/plugins/platforms/eglfs/CMakeLists.txt @@ -46,6 +46,8 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate Qt::FbSupportPrivate Qt::GuiPrivate EGL::EGL # special case + HEADER_SYNC_SOURCE_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}/api" ) #### Keys ignored in scope 2:.:.:eglfsdeviceintegration.pro:<TRUE>: diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt index 8964e27fda..66d16781aa 100644 --- a/src/tools/bootstrap/CMakeLists.txt +++ b/src/tools/bootstrap/CMakeLists.txt @@ -11,6 +11,9 @@ # The bootstrap library has a few manual tweaks compared to other # libraries. qt_add_library(Bootstrap STATIC) + +qt_internal_add_sync_header_dependencies(Bootstrap Core) + # special case end qt_internal_extend_target(Bootstrap SOURCES @@ -104,6 +107,7 @@ qt_internal_extend_target(Bootstrap ../../3rdparty/tinycbor/src PUBLIC_INCLUDE_DIRECTORIES # special case $<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES> # special case + ../../corelib/global PUBLIC_LIBRARIES # special case Qt::Platform # special case ) diff --git a/src/tools/syncqt/CMakeLists.txt b/src/tools/syncqt/CMakeLists.txt new file mode 100644 index 0000000000..0152c53450 --- /dev/null +++ b/src/tools/syncqt/CMakeLists.txt @@ -0,0 +1,30 @@ +# The tool should be optimized for maximum performance when working. +qt_internal_get_optimize_full_flags(optimize_full_flags) + +set(compile_definitions + QT_VERSION_STR="${PROJECT_VERSION}" + QT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + QT_VERSION_MINOR=${PROJECT_VERSION_MINOR} + QT_VERSION_PATCH=${PROJECT_VERSION_PATCH} + QT_NAMESPACE="${QT_NAMESPACE}" +) + +if(CMAKE_OSX_ARCHITECTURES) + set(osx_architectures "-DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}") +endif() +qt_get_tool_target_name(target_name syncqt) +qt_internal_add_configure_time_tool(${target_name} + DEFINES ${compile_definitions} + COMPILE_OPTIONS ${optimize_full_flags} + TOOLS_TARGET Core + INSTALL_DIRECTORY "${INSTALL_LIBEXECDIR}" + CMAKE_FLAGS + -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=TRUE + -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} + # std::filesystem API is only available in macOS 10.15+ + -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.15 + "${osx_architectures}" + SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" + CONFIG RelWithDebInfo +) diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp new file mode 100644 index 0000000000..03a80ef973 --- /dev/null +++ b/src/tools/syncqt/main.cpp @@ -0,0 +1,1567 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +/* + * The tool generates deployment artifacts for the Qt builds such as: + * - CaMeL case header files named by public C++ symbols located in public module header files + * - Header file that contains the module version information, and named as <module>Vesion + * - LD version script if applicable + * - Aliases or copies of the header files sorted by the generic Qt-types: public/private/qpa + * and stored in the corresponding directories. Also copies the aliases to the framework-specific + * directories. + * Also the tool executes conformity checks on each header file if applicable, to make sure they + * follow rules that are relevant for their header type. + * The tool can be run in two modes: with either '-all' or '-headers' options specified. Depending + * on the selected mode, the tool either scans the filesystem to find header files or use the + * pre-defined list of header files. + */ + +#include <iostream> +#include <fstream> +#include <string> +#include <sstream> +#include <filesystem> +#include <unordered_map> +#include <vector> +#include <regex> +#include <map> +#include <set> +#include <array> + +enum ErrorCodes { + NoError = 0, + InvalidArguments, + SyncFailed, +}; + +// Enum contains the list of checks that can be executed on header files. +enum HeaderChecks { + NoChecks = 0, + NamespaceChecks = 1, /* Checks if header file is wrapped with QT_<BEGIN|END>_NAMESPACE macros */ + PrivateHeaderChecks = 2, /* Checks if the public header includes a private header */ + IncludeChecks = 4, /* Checks if the real header file but not an alias is included */ + WeMeantItChecks = 8, /* Checks if private header files contains 'We meant it' disclaimer */ + CriticalChecks = PrivateHeaderChecks, /* Checks that lead to the fatal error of the sync + process */ + AllChecks = NamespaceChecks | PrivateHeaderChecks | IncludeChecks | WeMeantItChecks, +}; + +constexpr int LinkerScriptCommentAlignment = 55; + +static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$"); + +// This comparator is used to sort include records in master header. +// It's used to put q.*global.h file to the top of the list and sort all other files alphabetically. +bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b) +{ + std::smatch amatch; + std::smatch bmatch; + + if (std::regex_match(a, amatch, GlobalHeaderRegex)) { + if (std::regex_match(b, bmatch, GlobalHeaderRegex)) { + return amatch[1].str().empty() + || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str()); + } + return true; + } else if (std::regex_match(b, bmatch, GlobalHeaderRegex)) { + return false; + } + + return a < b; +}; + +namespace utils { +std::string asciiToLower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; }); + return s; +} + +std::string asciiToUpper(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return (c >= 'a' && c <= 'z') ? c & 0xdf : c; }); + return s; +} + +class DummyOutputStream : public std::ostream +{ + struct : public std::streambuf + { + int overflow(int c) override { return c; } + } buff; + +public: + DummyOutputStream() : std::ostream(&buff) { } +} DummyOutput; + +void printInternalError() +{ + std::cerr << "Internal error. Please create bugreport at https://bugreports.qt.io " + "using 'Build tools: Other component.'" + << std::endl; +} + +std::filesystem::path normilizedPath(const std::string &path) +{ + return std::filesystem::path(std::filesystem::absolute(path).generic_string()); +} +} + +using FileStamp = std::filesystem::file_time_type; + +class CommandLineOptions +{ + template<typename T> + struct CommandLineOption + { + CommandLineOption(T *_value, bool _isOptional = false) + : value(_value), isOptional(_isOptional) + { + } + + T *value; + bool isOptional; + }; + +public: + CommandLineOptions(int argc, char *argv[]) : m_isValid(parseArguments(argc, argv)) { } + + bool isValid() const { return m_isValid; } + + const std::string &moduleName() const { return m_moduleName; } + + const std::string &sourceDir() const { return m_sourceDir; } + + const std::string &binaryDir() const { return m_binaryDir; } + + const std::string &includeDir() const { return m_includeDir; } + + const std::string &privateIncludeDir() const { return m_privateIncludeDir; } + + const std::string &frameworkIncludeDir() const { return m_frameworkIncludeDir; } + + const std::string &qpaIncludeDir() const { return m_qpaIncludeDir; } + + const std::string &stagingDir() const { return m_stagingDir; } + + const std::string &versionScriptFile() const { return m_versionScriptFile; } + + const std::set<std::string> &knownModules() const { return m_knownModules; } + + const std::regex &qpaHeadersRegex() const { return m_qpaHeadersRegex; } + + const std::regex &privateHeadersRegex() const { return m_privateHeadersRegex; } + + const std::regex &publicNamespaceRegex() const { return m_publicNamespaceRegex; } + + const std::set<std::string> &headers() const { return m_headers; } + + const std::set<std::string> &generatedHeaders() const { return m_generatedHeaders; } + + bool scanAllMode() const { return m_scanAllMode; } + + bool isFramework() const { return m_isFramework; } + + bool isInternal() const { return m_isInternal; } + + bool isNonQtModule() const { return m_isNonQtModule; } + + bool printHelpOnly() const { return m_printHelpOnly; } + + bool debug() const { return m_debug; } + + bool copy() const { return m_copy; } + + bool minimal() const { return m_minimal; } + + bool showOnly() const { return m_showOnly; } + + void printHelp() const + { + std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>" + " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir>" + " -stagingDir <dir> <-headers <header list>|-all> [-debug]" + " [-versionScript <path>] [-qpaHeadersFilter <regex>]" + " [-framework [-frameworkIncludeDir <dir>]]" + " [-knownModules <module1> <module2>... <moduleN>]" + " [-nonQt] [-internal] [-copy]\n" + "" + "Mandatory arguments:\n" + " -module Module name.\n" + " -headers List of header files.\n" + " -all In 'all' mode syncqt scans source\n" + " directory for public qt headers and\n" + " artifacts not considering CMake source\n" + " tree. The main use cases are the \n" + " generating of documentation and creating\n" + " API review changes.\n" + " -sourceDir Module source directory.\n" + " -binaryDir Module build directory.\n" + " -includeDir Module include directory where the\n" + " generated header files will be located.\n" + " -privateIncludeDir Module include directory for the\n" + " generated private header files.\n" + " -qpaIncludeDir Module include directory for the \n" + " generated QPA header files.\n" + " -stagingDir Temporary staging directory to collect\n" + " artifacts that need to be installed.\n" + " -knownModules list of known modules. syncqt uses the\n" + " list to check the #include macros\n" + " consistency.\n" + "Optional arguments:\n" + " -internal Indicates that the module is internal.\n" + " -nonQt Indicates that the module is not a Qt\n" + " module.\n" + " -privateHeadersFilter Regex that filters private header files\n" + " from the list of 'headers'.\n" + " -qpaHeadersFilter Regex that filters qpa header files from.\n" + " the list of 'headers'.\n" + " -publicNamespaceFilter Symbols that are in the specified\n" + " namespace.\n" + " are treated as public symbols.\n" + " -versionScript Generate linker version script by\n" + " provided path.\n" + " -debug Enable debug output.\n" + " -framework Indicates that module is framework.\n" + " -frameworkIncludeDir The directory to store the framework\n" + " header files.\n" + " E.g. QtCore.framework/Versions/A/Headers\n" + " -copy Copy header files instead of creating\n" + " aliases.\n" + " -minimal Do not create CaMeL case headers for the\n" + " public C++ symbols.\n" + " -showonly Show actions, but not perform them.\n" + " -help Print this help.\n"; + } + +private: + template<typename T> + [[nodiscard]] bool checkRequiredArguments(const std::unordered_map<std::string, T> &arguments) + { + bool ret = true; + for (const auto &argument : arguments) { + if (!argument.second.isOptional + && (!argument.second.value || argument.second.value->size()) == 0) { + std::cerr << "Missing argument: " << argument.first << std::endl; + ret = false; + } + } + return ret; + } + + [[nodiscard]] bool parseArguments(int argc, char *argv[]) + { + std::string qpaHeadersFilter; + std::string privateHeadersFilter; + std::string publicNamespaceFilter; + std::set<std::string> generatedHeaders; + static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = { + { "-module", { &m_moduleName } }, + { "-sourceDir", { &m_sourceDir } }, + { "-binaryDir", { &m_binaryDir } }, + { "-privateHeadersFilter", { &privateHeadersFilter, true } }, + { "-qpaHeadersFilter", { &qpaHeadersFilter, true } }, + { "-includeDir", { &m_includeDir } }, + { "-privateIncludeDir", { &m_privateIncludeDir } }, + { "-qpaIncludeDir", { &m_qpaIncludeDir } }, + { "-stagingDir", { &m_stagingDir, true } }, + { "-versionScript", { &m_versionScriptFile, true } }, + { "-frameworkIncludeDir", { &m_frameworkIncludeDir, true } }, + { "-publicNamespaceFilter", { &publicNamespaceFilter, true } }, + }; + + static const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>> + listArgumentMap = { + { "-headers", { &m_headers, true } }, + { "-generatedHeaders", { &generatedHeaders, true } }, + { "-knownModules", { &m_knownModules, true } }, + }; + + static const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = { + { "-nonQt", { &m_isNonQtModule, true } }, { "-debug", { &m_debug, true } }, + { "-help", { &m_printHelpOnly, true } }, { "-framework", { &m_isFramework, true } }, + { "-internal", { &m_isInternal, true } }, { "-all", { &m_scanAllMode, true } }, + { "-copy", { &m_copy, true } }, { "-minimal", { &m_minimal, true } }, + { "-showonly", { &m_showOnly, true } }, { "-showOnly", { &m_showOnly, true } }, + }; + + std::string *currentValue = nullptr; + std::set<std::string> *currentListValue = nullptr; + for (int i = 1; i < argc; ++i) { + std::string arg(argv[i]); + if (arg.size() == 0) + continue; + + if (arg.size() > 0 && arg[0] == '-') { + currentValue = nullptr; + currentListValue = nullptr; + { + auto it = stringArgumentMap.find(arg); + if (it != stringArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + currentValue = it->second.value; + continue; + } + } + + { + auto it = boolArgumentMap.find(arg); + if (it != boolArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + *(it->second.value) = true; + continue; + } + } + + { + auto it = listArgumentMap.find(arg); + if (it != listArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + currentListValue = it->second.value; + continue; + } + } + + std::cerr << "Unknown argument: " << arg << std::endl; + return false; + } else { + if (currentValue != nullptr) { + *currentValue = arg; + currentValue = nullptr; + } else if (currentListValue != nullptr) { + currentListValue->insert(arg); + } else { + std::cerr << "Unknown argument: " << arg << std::endl; + } + } + } + + if (!qpaHeadersFilter.empty()) + m_qpaHeadersRegex = std::regex(qpaHeadersFilter); + + if (!privateHeadersFilter.empty()) + m_privateHeadersRegex = std::regex(privateHeadersFilter); + + if (!publicNamespaceFilter.empty()) + m_publicNamespaceRegex = std::regex(publicNamespaceFilter); + + if (m_headers.empty() && !m_scanAllMode) { + std::cerr << "You need to specify either -headers or -all option."; + return false; + } + + if (!m_headers.empty() && m_scanAllMode) { + std::cerr << "Both -headers and -all are specified. Need to choose only one" + "operational mode."; + return false; + } + + for (auto header : generatedHeaders) { + if (header.size() == 0) + continue; + if (header[0] == '@') { + std::ifstream ifs(header.substr(1), std::ifstream::in); + if (ifs.is_open()) { + std::string headerFromFile; + while (std::getline(ifs, headerFromFile)) { + if (!headerFromFile.empty()) + m_generatedHeaders.insert(headerFromFile); + } + } + } else { + m_generatedHeaders.insert(header); + } + } + + bool ret = true; + ret &= checkRequiredArguments(stringArgumentMap); + ret &= checkRequiredArguments(listArgumentMap); + + normilizePaths(); + + return ret; + } + + // Convert all paths from command line to a generic one. + void normilizePaths() + { + static std::array<std::string *, 8> paths = { + &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir, + &m_qpaIncludeDir, &m_stagingDir, &m_versionScriptFile, &m_frameworkIncludeDir + }; + for (auto path : paths) { + if (!path->empty()) + *path = utils::normilizedPath(*path).generic_string(); + } + } + + std::string m_moduleName; + std::string m_sourceDir; + std::string m_binaryDir; + std::string m_includeDir; + std::string m_privateIncludeDir; + std::string m_qpaIncludeDir; + std::string m_stagingDir; + std::string m_versionScriptFile; + std::string m_frameworkIncludeDir; + std::set<std::string> m_knownModules; + std::set<std::string> m_headers; + std::set<std::string> m_generatedHeaders; + bool m_scanAllMode = false; + bool m_copy = false; + bool m_isFramework = false; + bool m_isNonQtModule = false; + bool m_isInternal = false; + bool m_printHelpOnly = false; + bool m_debug = false; + bool m_minimal = false; + bool m_showOnly = false; + std::regex m_qpaHeadersRegex; + std::regex m_privateHeadersRegex; + std::regex m_publicNamespaceRegex; + + bool m_isValid; +}; + +class SyncScanner +{ + class SymbolDescriptor + { + public: + // Where the symbol comes from + enum SourceType { + Pragma = 0, // pragma qt_class is mentioned a header file + Declaration, // The symbol declaration inside a header file + MaxSourceType + }; + + void update(const std::string &file, SourceType type) + { + if (type < m_type) { + m_file = file; + m_type = type; + } + } + + // The file that contains a symbol. + const std::string &file() const { return m_file; } + + private: + SourceType m_type = MaxSourceType; + std::string m_file; + }; + using SymbolContainer = std::unordered_map<std::string, SymbolDescriptor>; + + struct ParsingResult + { + std::vector<std::string> versionScriptContent; + std::string requireConfig; + bool masterInclude = true; + }; + + CommandLineOptions *m_commandLineArgs = nullptr; + + std::map<std::string /* header file name */, std::string /* header feature guard name */, + decltype(MasterHeaderIncludeComparator) *> + m_masterHeaderContents; + + std::unordered_map<std::string /* the deprecated header name*/, + std::string /* the replacement */> + m_deprecatedHeaders; + std::vector<std::string> m_versionScriptContents; + std::set<std::string> m_producedHeaders; + std::vector<std::string> m_headerCheckExceptions; + SymbolContainer m_symbols; + std::ostream &scannerDebug() const + { + if (m_commandLineArgs->debug()) + return std::cout; + return utils::DummyOutput; + } + + enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active; + + std::filesystem::path m_currentFile; + std::string m_currentFilename; + std::string m_currentFileString; + size_t m_currentFileLineNumber = 0; + bool m_currentFileInSourceDir = false; + + enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4 }; + unsigned int m_currentFileType = PublicHeader; + +public: + SyncScanner(CommandLineOptions *commandLineArgs) + : m_commandLineArgs(commandLineArgs), m_masterHeaderContents(MasterHeaderIncludeComparator) + { + } + + // The function converts the relative path to a header files to the absolute. It also makes the + // path canonical(removes '..' and '.' parts of the path). The source directory passed in + // '-sourceDir' command line argument is used as base path for relative paths to create the + // absolute path. + [[nodiscard]] std::filesystem::path makeHeaderAbsolute(const std::string &filename) const; + + ErrorCodes sync() + { + m_versionScriptGeneratorState = + m_commandLineArgs->versionScriptFile().empty() ? Stopped : Active; + auto error = NoError; + + // In the scan all mode we ingore the list of header files that is specified in the + // '-headers' argument, and collect header files from the source directory tree. + if (m_commandLineArgs->scanAllMode()) { + for (auto const &entry : + std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) { + if (entry.is_regular_file() && isHeader(entry) + && !isDocFileHeuristic(entry.path().generic_string())) { + const std::string filePath = entry.path().generic_string(); + const std::string fileName = entry.path().filename().generic_string(); + scannerDebug() << "Checking: " << filePath << std::endl; + if (!processHeader(makeHeaderAbsolute(filePath))) + error = SyncFailed; + } + } + } else { + // Since the list of header file is quite big syncqt supports response files to avoid + // the issues with long command lines. + std::set<std::string> rspHeaders; + const auto &headers = m_commandLineArgs->headers(); + for (auto it = headers.begin(); it != headers.end(); ++it) { + const auto &header = *it; + if (header.size() > 0 && header[0] == '@') { + std::ifstream ifs(header.substr(1), std::ifstream::in); + if (ifs.is_open()) { + std::string headerFromFile; + while (std::getline(ifs, headerFromFile)) { + if (!headerFromFile.empty()) + rspHeaders.insert(headerFromFile); + } + } + } else if (!processHeader(makeHeaderAbsolute(header))) { + error = SyncFailed; + } + } + for (const auto &header : rspHeaders) { + if (!processHeader(makeHeaderAbsolute(header))) + error = SyncFailed; + } + } + + // No further processing in minimal mode. + if (m_commandLineArgs->minimal()) + return error; + + // Generate aliases for all unique symbols collected during the header files parsing. + for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) { + const std::string &filename = it->second.file(); + if (!filename.empty()) { + if (generateQtCamelCaseFileIfContentChanged( + m_commandLineArgs->includeDir() + '/' + it->first, filename)) { + m_producedHeaders.insert(it->first); + } else { + error = SyncFailed; + } + } + } + + // Generate the header file containing version information. + if (!m_commandLineArgs->isNonQtModule()) { + std::string moduleNameLower = utils::asciiToLower(m_commandLineArgs->moduleName()); + std::string versionHeaderFilename(moduleNameLower + "version.h"); + std::string versionHeaderCamel(m_commandLineArgs->moduleName() + "Version"); + std::string versionFile = m_commandLineArgs->includeDir() + '/' + versionHeaderFilename; + + std::error_code ec; + FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec); + if (ec) + originalStamp = FileStamp::clock::now(); + + if (generateVersionHeader(versionFile)) { + if (!generateAliasedHeaderFileIfTimestampChanged( + m_commandLineArgs->includeDir() + '/' + versionHeaderCamel, + versionHeaderFilename, originalStamp)) { + error = SyncFailed; + } + m_masterHeaderContents[versionHeaderFilename] = {}; + m_producedHeaders.insert(versionHeaderFilename); + m_producedHeaders.insert(versionHeaderCamel); + } else { + error = SyncFailed; + } + } + + if (!m_commandLineArgs->scanAllMode()) { + if (!m_commandLineArgs->isNonQtModule()) { + if (!generateDeprecatedHeaders()) + error = SyncFailed; + + if (!generateHeaderCheckExceptions()) + error = SyncFailed; + } + + if (!m_commandLineArgs->versionScriptFile().empty()) { + if (!generateLinkerVersionScript()) + error = SyncFailed; + } + } + + if (!m_commandLineArgs->isNonQtModule()) { + if (!generateMasterHeader()) + error = SyncFailed; + } + + if (!m_commandLineArgs->scanAllMode()) { + // Copy the generated files to a spearate staging directory to make the installation + // process eaiser. + if (!copyGeneratedHeadersToStagingDirectory(m_commandLineArgs->stagingDir())) + error = SyncFailed; + // We also need to have a copy of the generated header files in framework include + // directories when building with '-framework'. + if (m_commandLineArgs->isFramework()) { + if (!copyGeneratedHeadersToStagingDirectory( + m_commandLineArgs->frameworkIncludeDir(), true)) + error = SyncFailed; + } + } + return error; + } + + // The function copies files, that were generated while the sync procedure to a staging + // directory. This is necessary to simplify the installation of the generated files. + [[nodiscard]] bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, + bool skipCleanup = false) + { + bool result = true; + if (!std::filesystem::exists(outputDirectory)) { + std::filesystem::create_directories(outputDirectory); + } else if (!skipCleanup) { + for (const auto &entry : + std::filesystem::recursive_directory_iterator(outputDirectory)) { + if (m_producedHeaders.find(entry.path().filename().generic_string()) + == m_producedHeaders.end()) { + std::filesystem::remove(entry.path()); + } + } + } + + for (const auto &header : m_producedHeaders) { + std::filesystem::path src(m_commandLineArgs->includeDir() + '/' + header); + std::filesystem::path dst(outputDirectory + '/' + header); + if (!m_commandLineArgs->showOnly()) + result &= updateOrCopy(src, dst); + } + return result; + } + + void resetCurrentFileInfoData(const std::filesystem::path &headerFile) + { + // This regex filters the generated '*exports.h' and '*exports_p.h' header files. + static const std::regex ExportsHeaderRegex("^q(.*)exports(_p)?\\.h$"); + + m_currentFile = headerFile; + m_currentFileLineNumber = 0; + m_currentFilename = m_currentFile.filename().generic_string(); + m_currentFileType = PublicHeader; + m_currentFileString = m_currentFile.generic_string(); + m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->sourceDir()) == 0; + + if (isHeaderPrivate(m_currentFilename)) + m_currentFileType = PrivateHeader; + + if (isHeaderQpa(m_currentFilename)) + m_currentFileType = QpaHeader | PrivateHeader; + + if (std::regex_match(m_currentFilename, ExportsHeaderRegex)) + m_currentFileType |= ExportHeader; + } + + [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile) + { + // This regex filters any paths that contain the '3rdparty' directory. + static const std::regex ThirdPartyFolderRegex(".+/3rdparty/.+"); + + // This regex filters '-config.h' and '-config_p.h' header files. + static const std::regex ConfigHeaderRegex("^(q|.+-)config(_p)?\\.h"); + + resetCurrentFileInfoData(headerFile); + // We assume that header files ouside of the module source or build directories do not + // belong to the module. Skip any processing. + if (!m_currentFileInSourceDir + && m_currentFileString.find(m_commandLineArgs->binaryDir()) != 0) { + scannerDebug() << "Header file: " << headerFile + << " is outside the sync directories. Skipping." << std::endl; + m_headerCheckExceptions.push_back(m_currentFileString); + return true; + } + + // Check if a directory is passed as argument. That shouldn't happen, print error and exit. + if (m_currentFilename.empty()) { + std::cerr << "Header file name of " << m_currentFileString << "is empty"; + return false; + } + + std::error_code ec; + FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec); + if (ec) + originalStamp = FileStamp::clock::now(); + + bool isPrivate = m_currentFileType & PrivateHeader; + bool isQpa = m_currentFileType & QpaHeader; + bool isExport = m_currentFileType & ExportHeader; + scannerDebug() << headerFile << " m_currentFilename: " << m_currentFilename + << " isPrivate: " << isPrivate << " isQpa: " << isQpa << std::endl; + + // Chose the directory where to generate the header aliases or to copy header file if + // the '-copy' argument is passed. + std::string outputDir = m_commandLineArgs->includeDir(); + if (isQpa) + outputDir = m_commandLineArgs->qpaIncludeDir(); + else if (isPrivate) + outputDir = m_commandLineArgs->privateIncludeDir(); + + if (!std::filesystem::exists(outputDir)) + std::filesystem::create_directories(outputDir); + + bool headerFileExists = std::filesystem::exists(headerFile); + std::string aliasedFilepath = + std::filesystem::relative(headerFile, outputDir).generic_string(); + std::string aliasPath = outputDir + '/' + m_currentFilename; + + // If the '-copy' argument is passed, we copy the original file to a corresponding output + // directory otherwise we only create a header file alias that contains relative path to + // the original header file in the module source or build tree. + if (m_commandLineArgs->copy() && headerFileExists) { + if (!updateOrCopy(headerFile, aliasPath)) + return false; + } else { + if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath, + originalStamp)) + return false; + } + + // No further processing in minimal mode. + if (m_commandLineArgs->minimal()) + return true; + + // Stop processing if header files doesn't exist. This happens at configure time, since + // either header files are generated later than syncqt is running or header files only + // generated at build time. These files will be processed at build time, if CMake files + // contain the correct dependencies between the missing header files and the module + // 'sync_headers' targets. + if (!headerFileExists) { + scannerDebug() << "Header file: " << headerFile + << " doesn't exist, but is added to syncqt scanning. Skipping."; + return true; + } + + bool isGenerated = isHeaderGenerated(m_currentFileString); + bool is3rdParty = std::regex_match(m_currentFileString, ThirdPartyFolderRegex); + // No processing of generated Qt config header files. + if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) { + unsigned int skipChecks = m_commandLineArgs->scanAllMode() ? AllChecks : NoChecks; + + // Collect checks that should skipped for the header file. + if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa + || !m_currentFileInSourceDir || isGenerated) { + skipChecks = AllChecks; + } else { + if (std::regex_match(m_currentFilename, GlobalHeaderRegex) || isExport) + skipChecks |= NamespaceChecks; + + if (isHeaderPCH(m_currentFilename)) + skipChecks |= WeMeantItChecks; + + if (isPrivate) { + skipChecks |= NamespaceChecks; + skipChecks |= PrivateHeaderChecks; + skipChecks |= IncludeChecks; + } else { + skipChecks |= WeMeantItChecks; + } + } + + ParsingResult parsingResult; + parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty + && !isQpa && !isPrivate && !isGenerated; + if (!parseHeader(headerFile, originalStamp, parsingResult, skipChecks)) + return false; + + // Record the private header file inside the version script content. + if (isPrivate && !m_commandLineArgs->versionScriptFile().empty() + && parsingResult.versionScriptContent.size() > 0) { + m_versionScriptContents.insert(m_versionScriptContents.end(), + parsingResult.versionScriptContent.begin(), + parsingResult.versionScriptContent.end()); + } + + // Add the '#if QT_CONFIG(<feature>)' check for header files that supposed to be + // included into the module master header only if corresponding feature is enabled. + if (!isQpa && !isPrivate) { + if (m_currentFilename.find('_') == std::string::npos + && parsingResult.masterInclude) { + m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig; + } + } + } else if (m_currentFilename == "qconfig.h") { + // Hardcode generating of QtConfig alias + updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma); + } + + return true; + } + + void parseVersionScriptContent(const std::string buffer, ParsingResult &result) + { + // This regex looks for the symbols that needs to be placed into linker version script. + static const std::regex VersionScriptSymbolRegex( + "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?"); + + // This regex looks for the namespaces that needs to be placed into linker version script. + static const std::regex VersionScriptNamespaceRegex( + "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*"); + + // This regex filters the tailing colon from the symbol name. + static const std::regex TrailingColonRegex("([\\w]+):$"); + + switch (m_versionScriptGeneratorState) { + case Ignore: + m_versionScriptGeneratorState = Active; + return; + case Stopped: + return; + case IgnoreNext: + m_versionScriptGeneratorState = Ignore; + break; + case Active: + break; + } + + std::smatch match; + std::string symbol; + if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty()) + symbol = match[1].str(); + else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex)) + symbol = match[1].str(); + + if (std::regex_match(symbol, match, TrailingColonRegex)) + symbol = match[1].str(); + + // checkLineForSymbols(buffer, symbol); + if (symbol.size() > 0 && symbol[symbol.size() - 1] != ';') { + std::string relPath = m_currentFileInSourceDir + ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir()) + .string() + : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir()) + .string(); + + std::string versionStringRecord = " *"; + size_t startPos = 0; + size_t endPos = 0; + while (endPos != std::string::npos) { + endPos = symbol.find("::", startPos); + size_t length = endPos != std::string::npos ? (endPos - startPos) + : (symbol.size() - startPos); + if (length > 0) { + std::string symbolPart = symbol.substr(startPos, length); + versionStringRecord += std::to_string(symbolPart.size()); + versionStringRecord += symbolPart; + } + startPos = endPos + 2; + } + versionStringRecord += "*;"; + if (versionStringRecord.size() < LinkerScriptCommentAlignment) + versionStringRecord += + std::string(LinkerScriptCommentAlignment - versionStringRecord.size(), ' '); + versionStringRecord += " # "; + versionStringRecord += relPath; + versionStringRecord += ":"; + versionStringRecord += std::to_string(m_currentFileLineNumber); + versionStringRecord += "\n"; + result.versionScriptContent.push_back(versionStringRecord); + } + } + + // The function parses 'headerFile' and collect artifacts that are used at generating step. + // 'timeStamp' is saved in internal structures to compare it when generating files. + // 'result' the function output value that stores the result of parsing. + // 'skipChecks' checks that are not applicable for the header file. + [[nodiscard]] bool parseHeader(const std::filesystem::path &headerFile, + const FileStamp &timeStamp, ParsingResult &result, + unsigned int skipChecks) + { + if (m_commandLineArgs->showOnly()) + std::cout << headerFile << " [" << m_commandLineArgs->moduleName() << "]" << std::endl; + // This regex checks if line contains a macro. + static const std::regex MacroRegex("^\\s*#.*"); + + // The regex's bellow check line for known pragmas: + // - 'qt_sync_skip_header_check' avoid any header checks. + // + // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is + // found. Important note: All the parsing artifacts were found before this point are + // stored for further processing. + // + // - 'qt_sync_suspend_processing' pauses processing and skip lines inside a header until + // 'qt_sync_resume_processing' is found. 'qt_sync_stop_processing' stops processing if + // it's found before the 'qt_sync_resume_processing'. + // + // - 'qt_sync_resume_processing' resumes processing after 'qt_sync_suspend_processing'. + // + // - 'qt_class(<symbol>)' manually declares the 'symbol' that should be used to generate + // the CaMeL case header alias. + // + // - 'qt_deprecates(<deprecated header file>)' indicates that this header file replaces + // the 'deprecated header file'. syncqt will create the deprecated header file' with + // the special deprecation content. See the 'generateDeprecatedHeaders' function + // for details. + // + // - 'qt_no_master_include' indicates that syncqt should avoid including this header + // files into the module master header file. + static const std::regex SkipHeaderCheckRegex("^#\\s*pragma qt_sync_skip_header_check$"); + static const std::regex StopProcessingRegex("^#\\s*pragma qt_sync_stop_processing$"); + static const std::regex SuspendProcessingRegex("^#\\s*pragma qt_sync_suspend_processing$"); + static const std::regex ResumeProcessingRegex("^#\\s*pragma qt_sync_resume_processing$"); + static const std::regex ExplixitClassPragmaRegex("^#\\s*pragma qt_class\\(([^\\)]+)\\)$"); + static const std::regex DeprecatesPragmaRegex("^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$"); + static const std::regex NoMasterIncludePragmaRegex("^#\\s*pragma qt_no_master_include$"); + + // This regex checks if header contains 'We mean it' disclaimer. All private headers should + // contain them. + static const std::regex WeMeantItRegex("\\s*// We mean it\\."); + + // The regex's check if the content of header files is wrapped with the Qt namespace macros. + static const std::regex BeginNamespaceRegex("^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$"); + static const std::regex EndNamespaceRegex("^QT_END_NAMESPACE(_[A-Z_]+)?$"); + + // This regex checks if line contains the include macro of the following formats: + // - #include <file> + // - #include "file" + // - # include <file> + static const std::regex IncludeRegex("^#\\s*include\\s*[<\"](.+)[>\"]"); + + // This regex checks if line contains namespace definition. + static const std::regex NamespaceRegex("\\s*namespace ([^ ]*)\\s+"); + + // This regex checks if line contains the Qt iterator declaration, that need to have + // CaMel case header alias. + static const std::regex DeclareIteratorRegex("^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$"); + + // This regex checks if header file contains the QT_REQUIRE_CONFIG call. + // The macro argument is used to wrap an include of the header file inside the module master + // header file with the '#if QT_CONFIG(<feature>)' guard. + static const std::regex RequireConfigRegex("^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$"); + + // This regex looks for the ELFVERSION tag this is control key-word for the version script + // content processing. + // ELFVERSION tag accepts the following values: + // - stop - stops the symbols lookup for a versino script starting from this line. + // - ignore-next - ignores the line followed but the current one. + // - ignore - ignores the current line. + static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*"); + + std::ifstream input(headerFile, std::ifstream::in); + if (!input.is_open()) { + std::cerr << "Unable to open " << headerFile << std::endl; + return false; + } + + bool hasQtBeginNamespace = false; + std::string qtBeginNamespace; + std::string qtEndNamespace; + bool hasWeMeantIt = false; + bool isSuspended = false; + bool isMultiLineComment = false; + std::size_t bracesDepth = 0; + std::size_t namespaceCount = 0; + std::string namespaceString; + + std::smatch match; + + std::string buffer; + std::string line; + std::string tmpLine; + std::size_t linesProcessed = 0; + int faults = NoChecks; + + // Read file line by line + while (std::getline(input, tmpLine)) { + ++m_currentFileLineNumber; + line.append(tmpLine); + if (line.empty() || line.at(line.size() - 1) == '\\') { + continue; + } + buffer.clear(); + buffer.reserve(line.size()); + // Optimize processing by looking for a special sequences such as: + // - start-end of comments + // - start-end of class/structures + // And avoid processing of the the data inside these blocks. + for (std::size_t i = 0; i < line.size(); ++i) { + if (bracesDepth == namespaceCount) { + if (line[i] == '/') { + if ((i + 1) < line.size()) { + if (line[i + 1] == '*') { + isMultiLineComment = true; + continue; + } else if (line[i + 1] == '/') { // Single line comment + if (!(skipChecks & WeMeantItChecks) + && std::regex_match(line, WeMeantItRegex)) { + hasWeMeantIt = true; + continue; + } + if (m_versionScriptGeneratorState != Stopped + && std::regex_match(line, match, ElfVersionTagRegex)) { + if (match[1].str() == "ignore") + m_versionScriptGeneratorState = Ignore; + else if (match[1].str() == "ignore-next") + m_versionScriptGeneratorState = IgnoreNext; + else if (match[1].str() == "stop") + m_versionScriptGeneratorState = Stopped; + } + break; + } + } + } else if (line[i] == '*' && (i + 1) < line.size() && line[i + 1] == '/') { + ++i; + isMultiLineComment = false; + continue; + } + } + + if (isMultiLineComment) { + if (!(skipChecks & WeMeantItChecks) && std::regex_match(line, WeMeantItRegex)) { + hasWeMeantIt = true; + continue; + } + continue; + } + + if (line[i] == '{') { + if (std::regex_match(buffer, match, NamespaceRegex)) { + ++namespaceCount; + namespaceString += "::"; + namespaceString += match[1].str(); + } + ++bracesDepth; + continue; + } else if (line[i] == '}') { + if (namespaceCount > 0 && bracesDepth == namespaceCount) { + namespaceString.resize(namespaceString.rfind("::")); + --namespaceCount; + } + --bracesDepth; + } else if (bracesDepth == namespaceCount) { + buffer += line[i]; + } + } + line.clear(); + + if (buffer.size() == 0) + continue; + scannerDebug() << m_currentFilename << ": " << buffer << std::endl; + + if (m_currentFileType & PrivateHeader) { + parseVersionScriptContent(buffer, result); + } + + ++linesProcessed; + + bool skipSymbols = + (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader); + + // Parse pragmas + if (std::regex_match(buffer, MacroRegex)) { + if (std::regex_match(buffer, SkipHeaderCheckRegex)) { + skipChecks = AllChecks; + faults = NoChecks; + } else if (std::regex_match(buffer, StopProcessingRegex)) { + if (skipChecks == AllChecks) + m_headerCheckExceptions.push_back(m_currentFileString); + return true; + } else if (std::regex_match(buffer, SuspendProcessingRegex)) { + isSuspended = true; + } else if (std::regex_match(buffer, ResumeProcessingRegex)) { + isSuspended = false; + } else if (std::regex_match(buffer, match, ExplixitClassPragmaRegex)) { + if (!skipSymbols) { + updateSymbolDescriptor(match[1].str(), m_currentFilename, + SymbolDescriptor::Pragma); + } else { + // TODO: warn about skipping symbols that are defined explicitly + } + } else if (std::regex_match(buffer, NoMasterIncludePragmaRegex)) { + result.masterInclude = false; + } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) { + m_deprecatedHeaders[match[1].str()] = + m_commandLineArgs->moduleName() + '/' + m_currentFilename; + } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) { + if (!(skipChecks & IncludeChecks)) { + std::string includedHeader = match[1].str(); + if (!(skipChecks & PrivateHeaderChecks) + && isHeaderPrivate(std::filesystem::path(includedHeader) + .filename() + .generic_string())) { + faults |= PrivateHeaderChecks; + std::cerr << m_commandLineArgs->moduleName() + << ": ERROR: " << m_currentFilename + << " includes private header " << includedHeader << std::endl; + } + for (const auto &module : m_commandLineArgs->knownModules()) { + faults |= IncludeChecks; + std::string suggestedHeader = "Qt" + module + '/' + includedHeader; + if (std::filesystem::exists(m_commandLineArgs->includeDir() + "/../" + + suggestedHeader)) { + std::cerr << m_commandLineArgs->moduleName() + << ": WARNING: " << m_currentFilename << " includes " + << includedHeader << " when it should include " + << suggestedHeader << std::endl; + } + } + } + } + continue; + } + + // Logic below this line is affected by the 'qt_sync_suspend_processing' and + // 'qt_sync_resume_processing' pragmas. + if (isSuspended) + continue; + + // Look for the symbols in header file. + if (!skipSymbols) { + std::string symbol; + if (checkLineForSymbols(buffer, symbol)) { + if (namespaceCount == 0 + || std::regex_match(namespaceString, + m_commandLineArgs->publicNamespaceRegex())) { + updateSymbolDescriptor(symbol, m_currentFilename, + SymbolDescriptor::Declaration); + } + continue; + } else if (std::regex_match(buffer, match, DeclareIteratorRegex)) { + std::string iteratorSymbol = match[1].str() + "Iterator"; + updateSymbolDescriptor(std::string("Q") + iteratorSymbol, m_currentFilename, + SymbolDescriptor::Declaration); + updateSymbolDescriptor(std::string("QMutable") + iteratorSymbol, + m_currentFilename, SymbolDescriptor::Declaration); + continue; + } else if (std::regex_match(buffer, match, RequireConfigRegex)) { + result.requireConfig = match[1].str(); + continue; + } + } + + // Check for both QT_BEGIN_NAMESPACE and QT_END_NAMESPACE macros are present in the + // header file. + if (!(skipChecks & NamespaceChecks)) { + if (std::regex_match(buffer, match, BeginNamespaceRegex)) { + qtBeginNamespace = match[1].str(); + hasQtBeginNamespace = true; + } else if (std::regex_match(buffer, match, EndNamespaceRegex)) { + qtEndNamespace = match[1].str(); + } + } + } + + // Error out if namespace checks are failed. + if (!(skipChecks & NamespaceChecks)) { + if (hasQtBeginNamespace) { + if (qtBeginNamespace != qtEndNamespace) { + faults |= NamespaceChecks; + std::cerr << m_commandLineArgs->moduleName() + << ":WARNING: " << m_currentFilename + << " the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace + << " doesn't match the end namespace macro QT_END_NAMESPACE" + << qtEndNamespace << std::endl; + } + } else { + faults |= NamespaceChecks; + std::cerr << m_commandLineArgs->moduleName() << ": WARNING: " << m_currentFilename + << " does not include QT_BEGIN_NAMESPACE" << std::endl; + } + } + + if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) { + faults |= WeMeantItChecks; + std::cerr << m_commandLineArgs->moduleName() << ": WARNING: " << m_currentFilename + << " does not have the \"We mean it.\" warning\n" + << std::endl; + } + + scannerDebug() << "linesTotal: " << m_currentFileLineNumber + << " linesProcessed: " << linesProcessed << std::endl; + + if (skipChecks == AllChecks) + m_headerCheckExceptions.push_back(m_currentFileString); + + // Exit with an error if any of critical checks are present. + return !(faults & CriticalChecks); + } + + // The function checks if line contains the symbol that needs to have a CaMeL-style alias. + [[nodiscard]] bool checkLineForSymbols(const std::string &line, std::string &symbol) + { + scannerDebug() << "checkLineForSymbols: " << line << std::endl; + + // This regex checks if line contains class or structure declaration like: + // - <class|stuct> StructName + // - template <> class ClassName + // - class ClassName : [public|protected|private] BaseClassName + // - class ClassName [final|Q_DECL_FINAL|sealed] + // And possible combinations of the above variants. + static const std::regex ClassRegex( + "^ *(template *<.*> *)?(class|struct) +([^ <>]* " + "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> " + "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? " + "*.*)? *$"); + + // This regex checks if line contains function pointer typedef declaration like: + // - typedef void (* QFunctionPointerType)(int, char); + static const std::regex FunctionPointerRegex( + "^ *typedef *.*\\(\\*(Q[^\\)]+)\\)\\(.*\\); *"); + + // This regex checks if line contains class or structure typedef declaration like: + // - typedef AnySymbol<char> QAnySymbolType; + static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$"); + + // This regex checks if symbols is the Qt public symbol. Assume that Qt public symbols start + // with the capital 'Q'. + static const std::regex QtClassRegex("^Q\\w+$"); + + std::smatch match; + if (std::regex_match(line, match, FunctionPointerRegex)) { + symbol = match[1].str(); + } else if (std::regex_match(line, match, TypedefRegex)) { + symbol = match[2].str(); + } else if (std::regex_match(line, match, ClassRegex)) { + symbol = match[4].str(); + if (!std::regex_match(symbol, QtClassRegex)) + symbol.clear(); + } else { + return false; + } + return !symbol.empty(); + } + + [[nodiscard]] bool isHeaderQpa(const std::string &headerFileName) + { + return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex()); + } + + [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile) + { + return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex()); + } + + [[nodiscard]] bool isHeaderPCH(const std::string &headerFilename) + { + static const std::string pchSuffix("_pch.h"); + return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size()) + != std::string::npos; + } + + [[nodiscard]] bool isHeader(const std::filesystem::path &path) + { + return path.extension().string() == ".h"; + } + + [[nodiscard]] bool isDocFileHeuristic(const std::string &headerFilePath) + { + return headerFilePath.find("/doc/") != std::string::npos; + } + + [[nodiscard]] bool isHeaderGenerated(const std::string &header) + { + return m_commandLineArgs->generatedHeaders().find(header) + != m_commandLineArgs->generatedHeaders().end(); + } + + [[nodiscard]] bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath); + + [[nodiscard]] bool generateAliasedHeaderFileIfTimestampChanged( + const std::string &outputFilePath, const std::string &aliasedFilePath, + const FileStamp &originalStamp = FileStamp::clock::now()); + + bool writeIfDifferent(const std::string &outputFile, const std::string &buffer); + + [[nodiscard]] bool generateMasterHeader() + { + if (m_masterHeaderContents.empty()) + return true; + + std::string outputFile = + m_commandLineArgs->includeDir() + '/' + m_commandLineArgs->moduleName(); + + std::string moduleUpper = utils::asciiToUpper(m_commandLineArgs->moduleName()); + std::stringstream buffer; + buffer << "#ifndef QT_" << moduleUpper << "_MODULE_H\n" + << "#define QT_" << moduleUpper << "_MODULE_H\n" + << "#include <" << m_commandLineArgs->moduleName() << "/" + << m_commandLineArgs->moduleName() << "Depends>\n"; + for (const auto &headerContents : m_masterHeaderContents) { + if (!headerContents.second.empty()) { + buffer << "#if QT_CONFIG(" << headerContents.second << ")\n" + << "#include \"" << headerContents.first << "\"\n" + << "#endif\n"; + } else { + buffer << "#include \"" << headerContents.first << "\"\n"; + } + } + buffer << "#endif\n"; + + m_producedHeaders.insert(m_commandLineArgs->moduleName()); + return writeIfDifferent(outputFile, buffer.str()); + } + + [[nodiscard]] bool generateVersionHeader(const std::string &outputFile) + { + std::string moduleNameUpper = utils::asciiToUpper( m_commandLineArgs->moduleName()); + + std::stringstream buffer; + buffer << "/* This file was generated by syncqt. */\n" + << "#ifndef QT_" << moduleNameUpper << "_VERSION_H\n" + << "#define QT_" << moduleNameUpper << "_VERSION_H\n\n" + << "#define " << moduleNameUpper << "_VERSION_STR \"" << QT_VERSION_STR << "\"\n\n" + << "#define " << moduleNameUpper << "_VERSION " + << "0x0" << QT_VERSION_MAJOR << "0" << QT_VERSION_MINOR << "0" << QT_VERSION_PATCH + << "\n\n" + << "#endif // QT_" << moduleNameUpper << "_VERSION_H\n"; + + return writeIfDifferent(outputFile, buffer.str()); + } + + [[nodiscard]] bool generateDeprecatedHeaders() + { + static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]"); + static std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName(); + for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) { + std::string &replacement = it->second; + std::string qualifiedHeaderName = + std::regex_replace(it->first, cIdentifierSymbolsRegex, "_"); + std::string guard = guard_base + "_" + qualifiedHeaderName; + std::string warningText = "Header <" + m_commandLineArgs->moduleName() + "/" + it->first + + "> is deprecated. Please include <" + replacement + "> instead."; + std::stringstream buffer; + buffer << "#ifndef " << guard << "\n" + << "#define " << guard << "\n" + << "#if defined(__GNUC__)\n" + << "# warning " << warningText << "\n" + << "#elif defined(_MSC_VER)\n" + << "# pragma message (\"" << warningText << "\")\n" + << "#endif\n" + << "#include <" << replacement << ">\n" + << "#if 0\n" + // TODO: Looks like qt_no_master_include is useless since deprecated headers are + // generated by syncqt but are never scanned. + << "#pragma qt_no_master_include\n" + << "#endif\n" + << "#endif\n"; + writeIfDifferent(m_commandLineArgs->includeDir() + '/' + it->first, buffer.str()); + } + return true; + } + + [[nodiscard]] bool generateHeaderCheckExceptions() + { + std::stringstream buffer; + for (const auto &header : m_headerCheckExceptions) + buffer << header << ";"; + return writeIfDifferent(m_commandLineArgs->binaryDir() + '/' + + m_commandLineArgs->moduleName() + + "_header_check_exceptions", + buffer.str()); + } + + [[nodiscard]] bool generateLinkerVersionScript() + { + std::stringstream buffer; + for (const auto &content : m_versionScriptContents) + buffer << content; + return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str()); + } + + bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst); + void updateSymbolDescriptor(const std::string &symbol, const std::string &file, + SymbolDescriptor::SourceType type); +}; + +// The function updates information about the symbol: +// - The path and modification time of the file where the symbol was found. +// - The source of finding +// Also displays a short info about a symbol in show only mode. +void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file, + SymbolDescriptor::SourceType type) +{ + if (m_commandLineArgs->showOnly()) + std::cout << " SYMBOL: " << symbol << std::endl; + m_symbols[symbol].update(file, type); +} + +[[nodiscard]] std::filesystem::path +SyncScanner::makeHeaderAbsolute(const std::string &filename) const +{ + if (std::filesystem::path(filename).is_relative()) + return utils::normilizedPath(m_commandLineArgs->sourceDir() + '/' + filename); + + return utils::normilizedPath(filename); +} + +bool SyncScanner::updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) +{ + if (m_commandLineArgs->showOnly()) + return true; + + if (src == dst) { + std::cout << "Source and destination paths are same when copying " << src.string() + << ". Skipping." << std::endl; + return true; + } + + std::error_code ec; + std::filesystem::copy(src, dst, std::filesystem::copy_options::update_existing, ec); + if (ec) { + ec.clear(); + std::filesystem::remove(dst, ec); + if (ec) { + // On some file systems(e.g. vboxfs) the std::filesystem::copy doesn't support + // std::filesystem::copy_options::overwrite_existing remove file first and then copy. + std::cerr << "Unable to remove file: " << src << " to " << dst << " error: (" + << ec.value() << ")" << ec.message() << std::endl; + return false; + } + + std::filesystem::copy(src, dst, std::filesystem::copy_options::overwrite_existing, ec); + if (ec) { + std::cerr << "Unable to copy file: " << src << " to " << dst << " error: (" + << ec.value() << ")" << ec.message() << std::endl; + return false; + } + } + return true; +} + +// The function generates Qt CaMeL-case files. +// CaMeL-case files can have timestamp that is the same as or newer than timestamp of file that +// supposed to included there. It's not an issue when we generate regular aliases because the +// content of aliases is always the same, but we only want to "touch" them when content of original +// is changed. +bool SyncScanner::generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath) +{ + if (m_commandLineArgs->showOnly()) + return true; + + std::string buffer = "#include \""; + buffer += aliasedFilePath; + buffer += "\"\n"; + + return writeIfDifferent(outputFilePath, buffer); +} + +// The function generates aliases for files in source tree. Since the content of these aliases is +// always same, it's ok to check only timestamp and touch files in case if stamp of original is +// newer than the timestamp of an alias. +bool SyncScanner::generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath, + const FileStamp &originalStamp) +{ + if (m_commandLineArgs->showOnly()) + return true; + + if (std::filesystem::exists({ outputFilePath }) + && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) { + return true; + } + scannerDebug() << "Rewrite " << outputFilePath << std::endl; + + std::ofstream ofs; + ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc); + if (!ofs.is_open()) { + std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl; + return false; + } + ofs << "#include \"" << aliasedFilePath << "\"\n"; + ofs.close(); + return true; +} + +bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::string &buffer) +{ + if (m_commandLineArgs->showOnly()) + return true; + + static const std::streamsize bufferSize = 1025; + bool differs = false; + std::filesystem::path outputFilePath(outputFile); + + std::string outputDirectory = outputFilePath.parent_path().string(); + if (!std::filesystem::exists(outputDirectory)) + std::filesystem::create_directories(outputDirectory); + + if (std::filesystem::exists(outputFilePath) + && buffer.size() == std::filesystem::file_size(outputFilePath)) { + char rdBuffer[bufferSize]; + memset(rdBuffer, 0, bufferSize); + + std::ifstream ifs(outputFile, std::fstream::in); + std::streamsize currentPos = 0; + + std::size_t bytesRead = 0; + do { + ifs.read(rdBuffer, bufferSize - 1); // Read by 1K + bytesRead = ifs.gcount(); + if (buffer.compare(currentPos, bytesRead, rdBuffer) != 0) { + differs = true; + break; + } + currentPos += bytesRead; + memset(rdBuffer, 0, bufferSize); + } while (bytesRead > 0); + + ifs.close(); + } else { + differs = true; + } + + scannerDebug() << "Update: " << outputFile << " " << differs << std::endl; + if (differs) { + std::ofstream ofs; + ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc); + if (!ofs.is_open()) { + std::cerr << "Unable to write header content to " << outputFilePath << std::endl; + return false; + } + ofs << buffer; + + ofs.close(); + } + return true; +} + +int main(int argc, char *argv[]) +{ + CommandLineOptions options(argc, argv); + if (!options.isValid()) + return InvalidArguments; + + if (options.printHelpOnly()) { + options.printHelp(); + return NoError; + } + + SyncScanner scanner = SyncScanner(&options); + return scanner.sync(); +} diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 5153350eb6..30cd340f16 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -145,6 +145,12 @@ if(IOS) return() endif() +set(is_qt_build_platform TRUE) +# macOS versions less than 10.15 are not supported for building Qt. +if(CMAKE_HOST_APPLE AND CMAKE_HOST_SYSTEM_VERSION VERSION_LESS "19.0.0") + set(is_qt_build_platform FALSE) +endif() + _qt_internal_test_expect_pass(test_umbrella_config) _qt_internal_test_expect_pass(test_wrap_cpp_and_resources) if (NOT NO_WIDGETS) @@ -274,21 +280,23 @@ elseif(QT6_INSTALL_BINS) endif() # Test building and installing a few dummy Qt modules and plugins. -set(mockplugins_test_args "") -if(NOT QT_FEATURE_no_prefix) - list(APPEND mockplugins_test_args - BINARY "${CMAKE_COMMAND}" - BINARY_ARGS - "-DQT_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/mockplugins" - -P "${qt_install_prefix}/${qt_install_bin_dir}/qt-cmake-private-install.cmake" - ) -endif() -_qt_internal_test_expect_pass(mockplugins ${mockplugins_test_args}) -set_tests_properties(mockplugins PROPERTIES FIXTURES_SETUP build_mockplugins) +if(is_qt_build_platform) + set(mockplugins_test_args "") + if(NOT QT_FEATURE_no_prefix) + list(APPEND mockplugins_test_args + BINARY "${CMAKE_COMMAND}" + BINARY_ARGS + "-DQT_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/mockplugins" + -P "${qt_install_prefix}/${qt_install_bin_dir}/qt-cmake-private-install.cmake" + ) + endif() + _qt_internal_test_expect_pass(mockplugins ${mockplugins_test_args}) + set_tests_properties(mockplugins PROPERTIES FIXTURES_SETUP build_mockplugins) -# Test importing the plugins built in the project above. -_qt_internal_test_expect_pass(test_import_plugins BINARY ${CMAKE_CTEST_COMMAND} BINARY_ARGS -V) -set_tests_properties(test_import_plugins PROPERTIES FIXTURES_REQUIRED build_mockplugins) + # Test importing the plugins built in the project above. + _qt_internal_test_expect_pass(test_import_plugins BINARY ${CMAKE_CTEST_COMMAND} BINARY_ARGS -V) + set_tests_properties(test_import_plugins PROPERTIES FIXTURES_REQUIRED build_mockplugins) +endif() _qt_internal_test_expect_pass(test_versionless_targets) @@ -307,11 +315,13 @@ include(test_plugin_shared_static_flavor.cmake) _qt_internal_test_expect_pass(tst_qaddpreroutine BINARY tst_qaddpreroutine) -_qt_internal_test_expect_pass(test_static_resources - BINARY "${CMAKE_CTEST_COMMAND}" - BINARY_ARGS "-V") +if(is_qt_build_platform) + _qt_internal_test_expect_pass(test_static_resources + BINARY "${CMAKE_CTEST_COMMAND}" + BINARY_ARGS "-V") -_qt_internal_test_expect_pass(test_generating_cpp_exports) + _qt_internal_test_expect_pass(test_generating_cpp_exports) +endif() _qt_internal_test_expect_pass(test_qt_extract_metatypes) diff --git a/tests/auto/cmake/mockplugins/.cmake.conf b/tests/auto/cmake/mockplugins/.cmake.conf index 377be0059e..edb49ceeb2 100644 --- a/tests/auto/cmake/mockplugins/.cmake.conf +++ b/tests/auto/cmake/mockplugins/.cmake.conf @@ -1 +1,3 @@ set(QT_REPO_MODULE_VERSION "6.5.0") + +set(QT_USE_SYNCQT_CPP TRUE) diff --git a/tests/auto/cmake/mockplugins/mockplugins1/CMakeLists.txt b/tests/auto/cmake/mockplugins/mockplugins1/CMakeLists.txt index 1d672c231f..933f6dde5d 100644 --- a/tests/auto/cmake/mockplugins/mockplugins1/CMakeLists.txt +++ b/tests/auto/cmake/mockplugins/mockplugins1/CMakeLists.txt @@ -4,6 +4,7 @@ qt_internal_add_module(MockPlugins1 PLUGIN_TYPES mockplugin SOURCES + qmockplugin.h fake.cpp LIBRARIES Qt::CorePrivate diff --git a/tests/auto/cmake/mockplugins/mockplugins3/CMakeLists.txt b/tests/auto/cmake/mockplugins/mockplugins3/CMakeLists.txt index ead4800798..5df9c1b685 100644 --- a/tests/auto/cmake/mockplugins/mockplugins3/CMakeLists.txt +++ b/tests/auto/cmake/mockplugins/mockplugins3/CMakeLists.txt @@ -4,6 +4,7 @@ qt_internal_add_module(MockPlugins3 PLUGIN_TYPES mockauxplugin SOURCES + qmockauxplugin.h fake.cpp LIBRARIES Qt::CorePrivate |