# Add a custom ${module_include_name}_header_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_headers_clean_target module_target module_include_name module_headers) # module_headers is a list of strings of the form # [:feature] 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() endif() list(APPEND hclean_headers ${entry_path}) endforeach() # Make sure that the header compiles with our strict options set(hcleanDEFS -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_ASCII -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_KEYWORDS -DQT_USE_QSTRINGBUILDER -DQT_USE_FAST_OPERATOR_PLUS) set(compiler_to_run "${CMAKE_CXX_COMPILER}") if(CMAKE_CXX_COMPILER_LAUNCHER) list(PREPEND compiler_to_run "${CMAKE_CXX_COMPILER_LAUNCHER}") endif() set(prop_prefix "") get_target_property(target_type "${target}" TYPE) if(target_type STREQUAL "INTERFACE_LIBRARY") set(prop_prefix "INTERFACE_") endif() set(target_includes_genex "$") set(includes_exist_genex "$") set(target_includes_joined_genex "$<${includes_exist_genex}:-I$>") # 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 # genex-enhanced compile definitions. An example of that is in # qttools/src/designer/src/uiplugin/CMakeLists.txt which ends up causing the following error # message: # CMake Error at qtbase/cmake/QtModuleHelpers.cmake:35 (add_library): # INTERFACE_LIBRARY targets may only have whitelisted properties. The # property "QT_PLUGIN_CLASS_NAME" is not allowed. # Call Stack (most recent call first): # src/designer/src/uiplugin/CMakeLists.txt:7 (qt_internal_add_module) if(NOT target_type STREQUAL "INTERFACE_LIBRARY") set(target_defines_genex "$") set(defines_exist_genex "$") set(target_defines_joined_genex "$<${defines_exist_genex}:-D$>") endif() # TODO: FIXME # Passing COMPILE_OPTIONS can break add_custom_command() if the values contain genexes # that add_custom_command does not support. # # Such a case happens on Linux where libraries linking against Threads::Threads bring in a # '<$' genex. # # If this is really required for headersclean (and qmake's headerclean implementation does # actually pass all flags of the associated target), we'll have to replace the genex usage # with an implementation that recursively processes a target's dependencies property # to compile an expanded list of values for COMPILE_OPTIONS, and then filter out any genexes. # # This is similar to the proposed workaround at # https://gitlab.kitware.com/cmake/cmake/-/issues/21074#note_814979 # # See also https://gitlab.kitware.com/cmake/cmake/-/issues/21336 # #set(target_compile_options_genex "$") #set(compile_options_exist_genex "$") #set(target_compile_options_joined_genex # "$<${compile_options_exist_genex}:$>") set(target_compile_flags_genex "$") set(compile_flags_exist_genex "$") set(target_compile_flags_joined_genex "$<${compile_flags_exist_genex}:$>") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang|IntelLLVM") # Turn on some extra warnings not found in -Wall -Wextra. set(hcleanFLAGS -Wall -Wextra -Werror -Woverloaded-virtual -Wshadow -Wundef -Wfloat-equal -Wnon-virtual-dtor -Wpointer-arith -Wformat-security -Wno-long-long -Wno-variadic-macros -pedantic-errors) if(QT_FEATURE_reduce_relocations AND UNIX) list(APPEND hcleanFLAGS -fPIC) endif() # options accepted by GCC and Clang list(APPEND hcleanFLAGS -Wchar-subscripts -Wold-style-cast) if (NOT ((TEST_architecture_arch STREQUAL arm) OR (TEST_architecture_arch STREQUAL mips))) list(APPEND hcleanFLAGS -Wcast-align) endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") list(APPEND hcleanFLAGS -Wzero-as-null-pointer-constant) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.5) list(APPEND hcleanFLAGS -Wdouble-promotion) endif() if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.9) list(APPEND hcleanFLAGS -Wfloat-conversion) # GCC 9 has a lot of false positives relating to these list(APPEND hcleanFlags -Wno-deprecated-copy -Wno-redundant-move -Wno-format-overflow -Wno-init-list-lifetime) endif() endif() # Use strict mode C++20, with no GNU extensions (see -pedantic-errors above). list(APPEND hcleanFLAGS -std=c++2a) separate_arguments(cxx_flags NATIVE_COMMAND ${CMAKE_CXX_FLAGS}) if(APPLE AND CMAKE_OSX_SYSROOT) list(APPEND cxx_flags "${CMAKE_CXX_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") endif() if(APPLE AND QT_FEATURE_framework) # For some reason CMake doesn't generate -iframework flags from the INCLUDE_DIRECTORIES # generator expression we provide, so pass it explicitly and hope for the best. list(APPEND framework_includes "-iframework" "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_LIBDIR}") # If additional package prefixes are provided, we consider they can contain frameworks # as well. foreach(prefix IN LISTS _qt_additional_packages_prefix_paths) if(prefix MATCHES "/lib/cmake$") # Cut CMake files path string(APPEND prefix "/../..") endif() get_filename_component(prefix "${prefix}" ABSOLUTE) set(libdir "${prefix}/${INSTALL_LIBDIR}") if(EXISTS "${libdir}") list(APPEND framework_includes "-iframework" "${libdir}") endif() 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) list(APPEND hclean_artifacts "${artifact_path}") endforeach() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # -Za would enable strict standards behavior, but we can't add it because # and violate the standards. set(hcleanFLAGS -std:c++latest -Zc:__cplusplus -WX -W3) # 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) list(APPEND hclean_artifacts "${artifact_path}") endforeach() else() message(ERROR "CMAKE_CXX_COMPILER_ID \"${CMAKE_CXX_COMPILER_ID}\" is not supported for the headersclean check.") endif() add_custom_target(${module_include_name}_header_check COMMENT "headersclean: Checking headers in ${module_include_name}" DEPENDS ${hclean_artifacts} VERBATIM) add_dependencies(${module_target} ${module_include_name}_header_check) endfunction()