qt5base-lts/cmake/QtHeadersClean.cmake
Alexey Edelev c2c4a21374 Add the common headersclean_check target
Add headersclean_check that allows to run all module-specific
'_headersclean_check' targets in single command. Also add
dependency on input header files for the headers clean custom
commands.

Also adjust the '_headersclean_check' target names to match
the naming of module targets - the 'Qt' prefix is removed.

Change-Id: I17c3442c3c51ce99cd9b38c85d606e9ababbb013
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
2022-08-09 15:21:52 +02:00

247 lines
11 KiB
CMake

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
# 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]
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_TYPESAFE_FLAGS
-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
"$<TARGET_PROPERTY:${module_target},${prop_prefix}INCLUDE_DIRECTORIES>")
set(includes_exist_genex "$<BOOL:${target_includes_genex}>")
set(target_includes_joined_genex
"$<${includes_exist_genex}:-I$<JOIN:${target_includes_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
"$<TARGET_PROPERTY:${module_target},${prop_prefix}COMPILE_DEFINITIONS>")
set(defines_exist_genex "$<BOOL:${target_defines_genex}>")
set(target_defines_joined_genex
"$<${defines_exist_genex}:-D$<JOIN:${target_defines_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
# '<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>' 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 "$<TARGET_PROPERTY:${module_target},COMPILE_OPTIONS>")
#set(compile_options_exist_genex "$<BOOL:${target_compile_options_genex}>")
#set(target_compile_options_joined_genex
# "$<${compile_options_exist_genex}:$<JOIN:${target_compile_options_genex},;>>")
set(target_compile_flags_genex
"$<TARGET_PROPERTY:${module_target},${prop_prefix}COMPILE_FLAGS>")
set(compile_flags_exist_genex "$<BOOL:${target_compile_flags_genex}>")
set(target_compile_flags_joined_genex
"$<${compile_flags_exist_genex}:$<JOIN:${target_compile_flags_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)
endif()
endif()
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang|IntelLLVM")
list(APPEND hcleanFLAGS -Wshorten-64-to-32)
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
DEPENDS "${input_path}"
)
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
# <windows.h> and <GL.h> 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
DEPENDS "${input_path}"
)
list(APPEND hclean_artifacts "${artifact_path}")
endforeach()
else()
message(FATAL_ERROR "CMAKE_CXX_COMPILER_ID \"${CMAKE_CXX_COMPILER_ID}\" is not supported"
" for the headersclean check.")
endif()
add_custom_target(${module_target}_headersclean_check
COMMENT "headersclean: Checking headers in ${module_include_name}"
DEPENDS ${hclean_artifacts}
VERBATIM)
if(NOT TARGET headersclean_check)
add_custom_target(headersclean_check ALL)
endif()
add_dependencies(headersclean_check ${module_target}_headersclean_check)
endfunction()