qtbase/cmake/QtAutogenHelpers.cmake
Alexandru Croitor 71130a16f8 CMake: Work around upstream AUTOGEN issue with discarded dependencies
Upstream CMake has a nasty bug where AUTOGEN dependencies are
discarded if a library is added as a dependency more than once (or
rather the number of times it is added is not equal to the number of
configured build configs).

This can result in racy build failures, where a <module>_autogen
target is supposed to depend on some <other_module>_sync_headers
target, but doesn't, and thus moc generates empty metatypes files
because it can't find a synced header.

To avoid the AUTOGEN dependencies from being discarded, manually
add all dependencies passed to qt_internal_extend_target to the
AUTOGEN_TARGET_DEPENDS property.

The issue is fixed in CMake 4.0, so the workaround is gated on the
cmake version used. Add a flag called
QT_NO_AUTOGEN_DISCARDED_DEPENDENCIES_WORKAROUND to disable the
workaround, in case the fix gets reverted upstream.

Pick-to: 6.8 6.5
Fixes: QTBUG-133725
Change-Id: I857d7e774fc9f9f5203a58311daea5e7177e0d67
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
(cherry picked from commit fcb20586316aff2ccc39e2ae840b79f8b0e904a7)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
2025-02-17 16:13:53 +00:00

267 lines
11 KiB
CMake

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Initial autogen setup for a target to specify certain CMake properties which are common
# to all autogen tools. Also enable AUTOMOC by default.
function(qt_autogen_tools_initial_setup target)
set_property(TARGET "${target}" PROPERTY INTERFACE_QT_MAJOR_VERSION ${PROJECT_VERSION_MAJOR})
set_property(TARGET "${target}" APPEND PROPERTY COMPATIBLE_INTERFACE_STRING QT_MAJOR_VERSION)
set_directory_properties(PROPERTIES
QT_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}
QT_VERSION_MINOR ${PROJECT_VERSION_MINOR}
QT_VERSION_PATCH ${PROJECT_VERSION_PATCH}
)
qt_enable_autogen_tool(${target} "moc" ON)
endfunction()
# Enables or disables an autogen tool like moc, uic or rcc on ${target}.
function(qt_enable_autogen_tool target tool enable)
string(TOUPPER "${tool}" captitalAutogenTool)
get_target_property(tool_enabled ${target} AUTO${captitalAutogenTool})
get_target_property(autogen_target_depends ${target} AUTOGEN_TARGET_DEPENDS)
if(NOT autogen_target_depends)
set(autogen_target_depends "")
endif()
set(tool_executable "$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::${tool}>")
set(tool_target_name ${QT_CMAKE_EXPORT_NAMESPACE}::${tool})
if(enable)
list(APPEND autogen_target_depends ${tool_target_name})
else()
list(REMOVE_ITEM autogen_target_depends ${tool_target_name})
endif()
# f66c1db16c050c9d685a44a38ad7c5cf9f6fcc96 in qtbase introduced a new macro
# that the moc scanner has to look for. Inform the CMake moc scanner about it.
if(tool STREQUAL "moc" AND enable)
set_target_properties("${target}" PROPERTIES
AUTOMOC_MACRO_NAMES "${CMAKE_AUTOMOC_MACRO_NAMES};Q_ENUM_NS;Q_GADGET_EXPORT")
if (TARGET Qt::Platform)
get_target_property(_abi_tag Qt::Platform qt_libcpp_abi_tag)
if (_abi_tag)
set_property(TARGET "${target}" APPEND PROPERTY
AUTOMOC_MOC_OPTIONS --libcpp-abi-version "${_abi_tag}"
)
endif()
endif()
endif()
set_target_properties("${target}"
PROPERTIES
AUTO${captitalAutogenTool} "${enable}"
AUTO${captitalAutogenTool}_EXECUTABLE "${tool_executable}"
AUTOGEN_TARGET_DEPENDS "${autogen_target_depends}"
)
endfunction()
# This function adds or removes additional AUTOGEN tools to a target: AUTOMOC/UIC/RCC
function(qt_autogen_tools target)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "${__default_private_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(arg_ENABLE_AUTOGEN_TOOLS)
foreach(tool ${arg_ENABLE_AUTOGEN_TOOLS})
qt_enable_autogen_tool(${target} ${tool} ON)
endforeach()
endif()
if(arg_DISABLE_AUTOGEN_TOOLS)
foreach(tool ${arg_DISABLE_AUTOGEN_TOOLS})
qt_enable_autogen_tool(${target} ${tool} OFF)
endforeach()
endif()
endfunction()
# Complete manual moc invocation with full control.
# Use AUTOMOC whenever possible.
# Multi-value Arguments:
# INCLUDE_DIRECTORIES
# Specifies a list of include directories used by 'moc'.
# INCLUDE_DIRECTORY_TARGETS
# Specifies a list of targets to extract the INTERFACE_INCLUDE_DIRECTORIES
# property and use it as the 'moc' include directories.(Deprecated use TARGETS instead)
# DEFINITIONS
# List of the definitions that should be added to the moc command line arguments.
# Supports the syntax both with and without the prepending '-D'.
# TARGETS
# The list of targets that will be used to collect the INTERFACE_INCLUDE_DIRECTORIES,
# INCLUDE_DIRECTORIES, and COMPILE_DEFINITIONS properties.
function(qt_manual_moc result)
cmake_parse_arguments(arg
""
"OUTPUT_MOC_JSON_FILES"
"FLAGS;INCLUDE_DIRECTORIES;INCLUDE_DIRECTORY_TARGETS;DEFINITIONS;TARGETS"
${ARGN})
set(moc_files)
set(metatypes_json_list)
foreach(infile ${arg_UNPARSED_ARGUMENTS})
qt_make_output_file("${infile}" "moc_" ".cpp"
"${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" outfile)
list(APPEND moc_files "${outfile}")
set(moc_parameters_file "${outfile}_parameters$<$<BOOL:$<CONFIG>>:_$<CONFIG>>")
set(moc_parameters ${arg_FLAGS} -o "${outfile}" "${infile}")
foreach(dir IN ITEMS ${arg_INCLUDE_DIRECTORIES})
list(APPEND moc_parameters
"-I\n${dir}")
endforeach()
foreach(dep IN LISTS arg_INCLUDE_DIRECTORY_TARGETS arg_TARGETS)
set(include_expr "$<TARGET_PROPERTY:${dep},INTERFACE_INCLUDE_DIRECTORIES>")
list(APPEND moc_parameters
"$<$<BOOL:${include_expr}>:-I\n$<JOIN:${include_expr},\n-I\n>>")
if(APPLE AND TARGET ${dep})
get_target_property(is_versionless ${dep} _qt_is_versionless_target)
if(is_versionless)
string(REGEX REPLACE "^Qt::(.*)" "\\1" dep "${dep}")
set(dep "${QT_CMAKE_EXPORT_NAMESPACE}::${dep}")
endif()
get_target_property(alias_dep ${dep} ALIASED_TARGET)
if(alias_dep)
set(dep ${alias_dep})
endif()
get_target_property(loc ${dep} IMPORTED_LOCATION)
string(REGEX REPLACE "(.*)/Qt[^/]+\\.framework.*" "\\1" loc "${loc}")
if(loc)
list(APPEND moc_parameters "\n-F\n${loc}\n")
endif()
endif()
endforeach()
foreach(dep IN LISTS arg_TARGETS)
set(include_property_expr
"$<TARGET_GENEX_EVAL:${dep},$<TARGET_PROPERTY:${dep},INCLUDE_DIRECTORIES>>")
list(APPEND moc_parameters
"$<$<BOOL:${include_property_expr}>:-I\n$<JOIN:${include_property_expr},\n-I\n>>")
set(defines_property_expr
"$<TARGET_GENEX_EVAL:${dep},$<TARGET_PROPERTY:${dep},COMPILE_DEFINITIONS>>")
set(defines_with_d "$<FILTER:${defines_property_expr},INCLUDE,^-D>")
set(defines_without_d "$<FILTER:${defines_property_expr},EXCLUDE,^-D>")
list(APPEND moc_parameters
"$<$<BOOL:${defines_with_d}>:$<JOIN:${defines_with_d},\n>>")
list(APPEND moc_parameters
"$<$<BOOL:${defines_without_d}>:-D\n$<JOIN:${defines_without_d},\n-D\n>>")
endforeach()
foreach(def IN LISTS arg_DEFINITIONS)
if(NOT def MATCHES "^-D")
list(APPEND moc_parameters "-D\n${def}")
else()
list(APPEND moc_parameters "${def}")
endif()
endforeach()
set(metatypes_byproducts)
if (arg_OUTPUT_MOC_JSON_FILES)
set(moc_json_file "${outfile}.json")
list(APPEND moc_parameters --output-json)
list(APPEND metatypes_json_list "${outfile}.json")
set(metatypes_byproducts "${outfile}.json")
endif()
if (TARGET Qt::Platform)
get_target_property(_abi_tag Qt::Platform qt_libcpp_abi_tag)
if (_abi_tag)
list(APPEND moc_parameters --libcpp-abi-version "${_abi_tag}")
endif()
endif()
string (REPLACE ";" "\n" moc_parameters "${moc_parameters}")
file(GENERATE OUTPUT "${moc_parameters_file}" CONTENT "${moc_parameters}\n")
add_custom_command(OUTPUT "${outfile}" ${metatypes_byproducts}
COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::moc "@${moc_parameters_file}"
DEPENDS "${infile}" ${moc_depends} ${QT_CMAKE_EXPORT_NAMESPACE}::moc
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM)
endforeach()
set("${result}" ${moc_files} PARENT_SCOPE)
# Register generated json files
if (arg_OUTPUT_MOC_JSON_FILES)
set(${arg_OUTPUT_MOC_JSON_FILES} "${metatypes_json_list}" PARENT_SCOPE)
endif()
endfunction()
# From Qt6CoreMacros
# Function used to create the names of output files preserving relative dirs
function(qt_make_output_file infile prefix suffix source_dir binary_dir result)
get_filename_component(outfilename "${infile}" NAME_WE)
set(base_dir "${source_dir}")
string(FIND "${infile}" "${binary_dir}/" in_binary)
if (in_binary EQUAL 0)
set(base_dir "${binary_dir}")
endif()
get_filename_component(abs_infile "${infile}" ABSOLUTE BASE_DIR "${base_dir}")
file(RELATIVE_PATH rel_infile "${base_dir}" "${abs_infile}")
string(REPLACE "../" "__/" mapped_infile "${rel_infile}")
get_filename_component(abs_mapped_infile "${mapped_infile}" ABSOLUTE BASE_DIR "${binary_dir}")
get_filename_component(outpath "${abs_mapped_infile}" PATH)
file(MAKE_DIRECTORY "${outpath}")
set("${result}" "${outpath}/${prefix}${outfilename}${suffix}" PARENT_SCOPE)
endfunction()
# Work around AUTOGEN issue when a library is added as a dependency more than once, and the autogen
# library dependency results in being discarded. To mitigate that, add all autogen dependencies
# manually, based on the passed in dependencies.
# CMake 4.0+ has a fix, so we don't need the extra logic.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/26700
function(qt_internal_work_around_autogen_discarded_dependencies target)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0
OR QT_NO_AUTOGEN_DISCARDED_DEPENDENCIES_WORKAROUND)
return()
endif()
set(libraries ${ARGN})
set(final_libraries "")
foreach(lib IN LISTS libraries)
# Skip non-target dependencies.
if(NOT TARGET "${lib}")
continue()
endif()
# Resolve alias targets, because AUTOGEN_TARGET_DEPENDS doesn't seem to handle them.
get_target_property(aliased_target "${lib}" ALIASED_TARGET)
if(aliased_target)
set(lib "${aliased_target}")
endif()
# Skip imported targets, they don't have sync_headers targets.
get_target_property(imported "${lib}" IMPORTED)
if(imported)
continue()
endif()
# Resolve Qt private modules to their public counterparts.
get_target_property(is_private_module "${lib}" _qt_is_private_module)
get_target_property(public_module_target "${lib}" _qt_public_module_target_name)
if(is_private_module AND public_module_target)
set(lib "${public_module_target}")
endif()
# Another TARGET check, just in case.
if(TARGET "${lib}")
list(APPEND final_libraries "${lib}")
endif()
endforeach()
if(final_libraries)
set_property(TARGET ${target} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS "${final_libraries}")
endif()
endfunction()