CMake: Only load Qt6FooPrivate automatically when building Qt

[ChangeLog][CMake] CMake packages of public Qt modules don't provide the
targets of their private counterparts anymore. User projects must now
call find_package(Qt6 COMPONENTS FooPrivate) to make use of the
Qt6::FooPrivate target. User projects that rely on the old behavior can
set the CMake variable QT_FIND_PRIVATE_MODULES to ON.

For user projects, the warning message we know from QMake is displayed.
The warning can be disabled by setting the CMake variable
QT_NO_PRIVATE_MODULE_WARNING to ON.

Within Qt itself, find_package(Qt6Foo) will still
find_package(Qt6FooPrivate).

For static Qt builds, we need to wrap usage of private Qt modules in
$<BUILD_INTERFACE> or $<BUILD_LOCAL_INTERFACE> (if CMake's version is >=
3.26). Static builds with CMake < 3.26 will always load the private
modules if the Qt6FooConfig.cmake from Qt's build tree is loaded. This
is the case in non-prefix builds and (in the future) when building
examples as external project.

This amends commit fbbf4ace0188b9718b6d7808021c0b887fd52d9f.

Task-number: QTBUG-87776
Change-Id: I78e95248f2b3fa73c3005c61df2fe4f71ad8eeb8
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
(cherry picked from commit ad7b94e163ac5c3959a7e38d7f48536be288a187)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Joerg Bornemann 2025-01-03 17:19:22 +01:00 committed by Qt Cherry-pick Bot
parent 49260691b2
commit e120fb78c9
9 changed files with 126 additions and 34 deletions

View File

@ -35,8 +35,17 @@ if (NOT QT_NO_CREATE_TARGETS AND @INSTALL_CMAKE_NAMESPACE@@target@_FOUND)
endif()
# Find the private module counterpart.
if (@INSTALL_CMAKE_NAMESPACE@@target@_FOUND AND NOT @arg_NO_PRIVATE_MODULE@)
if(NOT @INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND)
set(__qt_@target@_always_load_private_module OFF)
include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@-build.cmake" OPTIONAL)
if (@INSTALL_CMAKE_NAMESPACE@@target@_FOUND
AND NOT @INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND
AND NOT @arg_NO_PRIVATE_MODULE@
AND (
__qt_@target@_always_load_private_module
OR DEFINED QT_REPO_MODULE_VERSION
OR QT_FIND_PRIVATE_MODULES
)
)
if("${_qt_cmake_dir}" STREQUAL "")
set(_qt_cmake_dir "${QT_TOOLCHAIN_RELOCATABLE_CMAKE_DIR}")
endif()
@ -54,7 +63,7 @@ if (@INSTALL_CMAKE_NAMESPACE@@target@_FOUND AND NOT @arg_NO_PRIVATE_MODULE@)
${_qt_additional_packages_prefix_paths}
${__qt_use_no_default_path_for_qt_packages}
)
endif()
if(NOT @INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND)
get_property(@INSTALL_CMAKE_NAMESPACE@@target_private@_warning_shown GLOBAL PROPERTY
@INSTALL_CMAKE_NAMESPACE@@target_private@_warning_shown
@ -71,6 +80,7 @@ if (@INSTALL_CMAKE_NAMESPACE@@target@_FOUND AND NOT @arg_NO_PRIVATE_MODULE@)
endif()
endif()
endif()
unset(__qt_@target@_always_load_private_module)
if (NOT QT_NO_CREATE_TARGETS AND @INSTALL_CMAKE_NAMESPACE@@target@_FOUND)
# DEPRECATED

View File

@ -20,6 +20,20 @@ if(NOT DEFINED "@INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND")
set("@INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND" TRUE)
endif()
if(NOT __qt_@target@_always_load_private_module
AND NOT DEFINED QT_REPO_MODULE_VERSION
AND NOT QT_NO_PRIVATE_MODULE_WARNING
AND NOT __qt_private_module_@target_private@_warning_shown)
message(WARNING
"This project is using headers of the @target_private@ module and will therefore be tied "
"to this specific Qt module build version. "
"Running this project against other versions of the Qt modules may crash at any arbitrary "
"point. This is not a bug, but a result of using Qt internals. You have been warned! "
"\nYou can disable this warning by setting QT_NO_PRIVATE_MODULE_WARNING to ON."
)
set(__qt_private_module_@target_private@_warning_shown TRUE)
endif()
if(NOT QT_NO_CREATE_TARGETS AND @INSTALL_CMAKE_NAMESPACE@@target_private@_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target_private@Targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target_private@AdditionalTargetInfo.cmake")

View File

@ -327,7 +327,7 @@ function(qt_internal_add_module target)
set_target_properties(${target_private} PROPERTIES
_qt_config_module_name ${arg_CONFIG_MODULE_NAME}_private
_qt_package_version "${PROJECT_VERSION}"
_qt_package_name "${INSTALL_CMAKE_NAMESPACE}${target}"
_qt_package_name "${INSTALL_CMAKE_NAMESPACE}${target}Private"
_qt_is_private_module TRUE
_qt_public_module_target_name "${target}"
)
@ -790,7 +790,14 @@ set(QT_ALLOW_MISSING_TOOLS_PACKAGES TRUE)")
qt_internal_get_min_new_policy_cmake_version(min_new_policy_version)
qt_internal_get_max_new_policy_cmake_version(max_new_policy_version)
if(is_static_lib)
set(write_basic_module_package_args IS_STATIC_LIB)
else()
set(write_basic_module_package_args "")
endif()
qt_internal_write_basic_module_package("${target}" "${target_private}"
${write_basic_module_package_args}
CONFIG_BUILD_DIR ${config_build_dir}
CONFIG_INSTALL_DIR ${config_install_dir}
)
@ -809,7 +816,8 @@ set(QT_ALLOW_MISSING_TOOLS_PACKAGES TRUE)")
)
if(NOT arg_NO_PRIVATE_MODULE)
qt_internal_write_basic_module_package(${target} ${target_private}
qt_internal_write_basic_module_package("${target}" "${target_private}"
${write_basic_module_package_args}
PRIVATE
CONFIG_BUILD_DIR ${private_config_build_dir}
CONFIG_INSTALL_DIR ${private_config_install_dir}
@ -981,11 +989,9 @@ endfunction()
#
# If PRIVATE is specified, write Qt6FooPrivate.
# Otherwise write its public counterpart.
#
# Note that this function is supposed to be called from qt_internal_add_module, and depends on
# variables set in the scope of that function, e.g. target and target_private.
function(qt_internal_write_basic_module_package)
function(qt_internal_write_basic_module_package target target_private)
set(no_value_options
IS_STATIC_LIB
PRIVATE
)
set(single_value_options
@ -1005,6 +1011,23 @@ function(qt_internal_write_basic_module_package)
set(module_config_input_file "QtModuleConfig.cmake.in")
endif()
if(arg_IS_STATIC_LIB AND NOT arg_PRIVATE AND CMAKE_VERSION VERSION_LESS "3.26")
# We auto-load the private module package from the public module package if we have a static
# Qt module and CMake's version is < 3.26. This is needed for the case "Qt6Foo links against
# Qt6BarPrivate", because CMake generates a check for the target Qt6::BarPrivate in
# Qt6FooTargets.cmake. Once we can require CMake 3.26, we can simply link against
# $<BUILD_LOCAL_INTERFACE:Qt6BarPrivate> in qt_internal_extend_target.
#
# For older CMake versions, we create an additional CMake file that's optionally included by
# Qt6FooConfig.cmake to work around the lack of BUILD_LOCAL_INTERFACE.
file(CONFIGURE
OUTPUT "${arg_CONFIG_BUILD_DIR}/${package_name}-build.cmake"
CONTENT "# This file marks this directory as part of Qt's build tree.
set(__qt_${target}_always_load_private_module ON)
"
)
endif()
configure_package_config_file(
"${QT_CMAKE_DIR}/${module_config_input_file}"
"${arg_CONFIG_BUILD_DIR}/${package_name}Config.cmake"

View File

@ -172,12 +172,16 @@ function(__qt_internal_walk_libs
if(lib MATCHES "^\\$<TARGET_OBJECTS:")
# Skip object files.
continue()
elseif(lib MATCHES "^\\$<LINK_ONLY:(.*)>$")
set(lib_target ${CMAKE_MATCH_1})
else()
set(lib_target ${lib})
endif()
set(lib_target "${lib}")
# Unwrap targets like $<LINK_ONLY:$<BUILD_INTERFACE:Qt6::CorePrivate>>
while(lib_target
MATCHES "^\\$<(LINK_ONLY|BUILD_INTERFACE|BUILD_LOCAL_INTERFACE):(.*)>$")
set(lib_target ${CMAKE_MATCH_2})
endwhile()
# Skip CMAKE_DIRECTORY_ID_SEP. If a target_link_libraries is applied to a target
# that was defined in a different scope, CMake appends and prepends a special directory
# id separator. Filter those out.

View File

@ -148,6 +148,7 @@ function(qt_internal_extend_target target)
list(TRANSFORM arg_PUBLIC_LIBRARIES REPLACE "^Qt::" "${QT_CMAKE_EXPORT_NAMESPACE}::")
list(TRANSFORM arg_LIBRARIES REPLACE "^Qt::" "${QT_CMAKE_EXPORT_NAMESPACE}::")
qt_internal_wrap_private_modules(arg_LIBRARIES ${arg_LIBRARIES})
# Set-up the target
@ -257,6 +258,9 @@ function(qt_internal_extend_target target)
if(TARGET "${target_private}")
target_link_libraries("${target_private}"
INTERFACE ${arg_PRIVATE_MODULE_INTERFACE})
qt_internal_register_target_dependencies("${target_private}"
PUBLIC ${arg_PRIVATE_MODULE_INTERFACE}
)
elseif(arg_PRIVATE_MODULE_INTERFACE)
set(warning_message "")
string(APPEND warning_message
@ -268,9 +272,9 @@ function(qt_internal_extend_target target)
endif()
set(qt_register_target_dependencies_args "")
if(arg_PUBLIC_LIBRARIES OR arg_PRIVATE_MODULE_INTERFACE)
if(arg_PUBLIC_LIBRARIES)
list(APPEND qt_register_target_dependencies_args
PUBLIC ${arg_PUBLIC_LIBRARIES} ${arg_PRIVATE_MODULE_INTERFACE})
PUBLIC ${arg_PUBLIC_LIBRARIES})
endif()
if(qt_libs_private OR arg_LIBRARIES)
list(APPEND qt_register_target_dependencies_args
@ -337,6 +341,43 @@ function(qt_internal_extend_target target)
endif()
endfunction()
# Takes an output variable and a list of libraries.
#
# Every library that is a private module is wrapped in $<BUILD_INTERFACE> or
# $<BUILD_LOCAL_INTERFACE> if CMake is new enough.
#
# This is necessary for static builds, because if Qt6Foo links to Qt6BarPrivate, this link
# dependency is purely internal. If we don't do this, CMake adds a target check for Qt6BarPrivate
# in Qt6FooTargets.cmake. This breaks if Qt6BarPrivate is not find_package'ed before.
function(qt_internal_wrap_private_modules out_var)
set(result "")
if(CMAKE_VERSION VERSION_LESS "3.26")
set(wrapper_genex "BUILD_INTERFACE")
else()
set(wrapper_genex "BUILD_LOCAL_INTERFACE")
endif()
foreach(lib IN LISTS ARGN)
if(TARGET "${lib}")
get_target_property(lib_is_private_module ${lib} _qt_is_private_module)
if(lib_is_private_module)
# Add the public module as non-wrapped link dependency. This is necessary for
# targets that link only to the private module. Consumers of this target would then
# get a linker error about missing symbols from that Qt module.
get_target_property(lib_public_module_target ${lib} _qt_public_module_target_name)
list(APPEND result "${INSTALL_CMAKE_NAMESPACE}::${lib_public_module_target}")
# Wrap the private module in BUILD_LOCAL_INTERFACE.
set(lib "$<${wrapper_genex}:${lib}>")
endif()
endif()
list(APPEND result "${lib}")
endforeach()
set("${out_var}" "${result}" PARENT_SCOPE)
endfunction()
# Given CMAKE_CONFIG and ALL_CMAKE_CONFIGS, determines if a directory suffix needs to be appended
# to each destination, and sets the computed install target destination arguments in OUT_VAR.
# Defaults used for each of the destination types, and can be configured per destination type.

View File

@ -11,7 +11,7 @@ project(QtMockPlugins
LANGUAGES CXX C
)
find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core)
find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals CorePrivate)
qt_internal_project_setup()
find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS Gui Widgets Xml)

View File

@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16)
project(test_private_includes)
find_package(Qt6Gui REQUIRED)
find_package(Qt6GuiPrivate REQUIRED)
add_executable(testapp main.cpp)
target_link_libraries(testapp PRIVATE Qt::GuiPrivate)

View File

@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16)
project(test_private_targets)
find_package(Qt6Gui REQUIRED)
find_package(Qt6GuiPrivate REQUIRED)
add_executable(testapp main.cpp)
target_link_libraries(testapp Qt::GuiPrivate)

View File

@ -22,7 +22,7 @@ project(tst_qaddpreroutine
find_package(Qt6 COMPONENTS Core BuildInternals CONFIG REQUIRED)
qt_prepare_standalone_project()
find_package(Qt6 COMPONENTS Gui Test CONFIG REQUIRED)
find_package(Qt6 COMPONENTS GuiPrivate Test CONFIG REQUIRED)
qt_internal_add_plugin(QTBUG_90341ThemePlugin
NO_UNITY_BUILD