summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cmake.conf3
-rw-r--r--cmake/QtBaseGlobalTargets.cmake1
-rw-r--r--cmake/QtBuild.cmake9
-rw-r--r--cmake/QtBuildInternals/QtBuildInternalsConfig.cmake14
-rw-r--r--cmake/QtDocsHelpers.cmake6
-rw-r--r--cmake/QtExecutableHelpers.cmake2
-rw-r--r--cmake/QtFrameworkHelpers.cmake25
-rw-r--r--cmake/QtHeadersClean.cmake180
-rw-r--r--cmake/QtModuleHeadersCheck.cmake33
-rw-r--r--cmake/QtModuleHelpers.cmake156
-rw-r--r--cmake/QtPluginHelpers.cmake2
-rw-r--r--cmake/QtPostProcessHelpers.cmake8
-rw-r--r--cmake/QtSyncQtHelpers.cmake266
-rw-r--r--cmake/QtToolHelpers.cmake3
-rw-r--r--qmake/CMakeLists.txt3
-rw-r--r--src/3rdparty/harfbuzz-ng/CMakeLists.txt2
-rw-r--r--src/3rdparty/zlib/CMakeLists.txt2
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/corelib/CMakeLists.txt13
-rw-r--r--src/corelib/global/qtconfigmacros.h5
-rw-r--r--src/corelib/global/qtversionchecks.h5
-rw-r--r--src/entrypoint/CMakeLists.txt2
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/eglfs/CMakeLists.txt2
-rw-r--r--src/tools/bootstrap/CMakeLists.txt4
-rw-r--r--src/tools/syncqt/CMakeLists.txt30
-rw-r--r--src/tools/syncqt/main.cpp1567
-rw-r--r--tests/auto/cmake/CMakeLists.txt46
-rw-r--r--tests/auto/cmake/mockplugins/.cmake.conf2
-rw-r--r--tests/auto/cmake/mockplugins/mockplugins1/CMakeLists.txt1
-rw-r--r--tests/auto/cmake/mockplugins/mockplugins3/CMakeLists.txt1
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