diff options
author | Joerg Bornemann <[email protected]> | 2025-01-03 17:19:22 +0100 |
---|---|---|
committer | Joerg Bornemann <[email protected]> | 2025-01-24 18:53:34 +0100 |
commit | ad7b94e163ac5c3959a7e38d7f48536be288a187 (patch) | |
tree | 6d92d6cc8853edaccc4441ba7fea74b2b6bc2195 | |
parent | cf9c94e85160a9d0f03e284ff92995900076faee (diff) |
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.
Pick-to: 6.9
Task-number: QTBUG-87776
Change-Id: I78e95248f2b3fa73c3005c61df2fe4f71ad8eeb8
Reviewed-by: Alexandru Croitor <[email protected]>
-rw-r--r-- | cmake/QtModuleConfig.cmake.in | 46 | ||||
-rw-r--r-- | cmake/QtModuleConfigPrivate.cmake.in | 14 | ||||
-rw-r--r-- | cmake/QtModuleHelpers.cmake | 35 | ||||
-rw-r--r-- | cmake/QtPublicWalkLibsHelpers.cmake | 12 | ||||
-rw-r--r-- | cmake/QtTargetHelpers.cmake | 45 | ||||
-rw-r--r-- | tests/auto/cmake/mockplugins/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/cmake/test_private_includes/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/cmake/test_private_targets/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/cmake/tst_qaddpreroutine/CMakeLists.txt | 2 |
9 files changed, 126 insertions, 34 deletions
diff --git a/cmake/QtModuleConfig.cmake.in b/cmake/QtModuleConfig.cmake.in index 93185857162..de243aff2b3 100644 --- a/cmake/QtModuleConfig.cmake.in +++ b/cmake/QtModuleConfig.cmake.in @@ -32,26 +32,35 @@ 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) - if("${_qt_cmake_dir}" STREQUAL "") - set(_qt_cmake_dir "${QT_TOOLCHAIN_RELOCATABLE_CMAKE_DIR}") - endif() - set(__qt_use_no_default_path_for_qt_packages "NO_DEFAULT_PATH") - if(QT_DISABLE_NO_DEFAULT_PATH_IN_QT_PACKAGES) - set(__qt_use_no_default_path_for_qt_packages "") - endif() - find_package(@INSTALL_CMAKE_NAMESPACE@@target_private@ "@PROJECT_VERSION@" EXACT - QUIET - CONFIG - PATHS - ${QT_BUILD_CMAKE_PREFIX_PATH} - "${CMAKE_CURRENT_LIST_DIR}/.." - "${_qt_cmake_dir}" - ${_qt_additional_packages_prefix_paths} - ${__qt_use_no_default_path_for_qt_packages} +set(__qt_@target@_always_load_private_module OFF) +include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@[email protected]" 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() + set(__qt_use_no_default_path_for_qt_packages "NO_DEFAULT_PATH") + if(QT_DISABLE_NO_DEFAULT_PATH_IN_QT_PACKAGES) + set(__qt_use_no_default_path_for_qt_packages "") + endif() + find_package(@INSTALL_CMAKE_NAMESPACE@@target_private@ "@PROJECT_VERSION@" EXACT + QUIET + CONFIG + PATHS + ${QT_BUILD_CMAKE_PREFIX_PATH} + "${CMAKE_CURRENT_LIST_DIR}/.." + "${_qt_cmake_dir}" + ${_qt_additional_packages_prefix_paths} + ${__qt_use_no_default_path_for_qt_packages} + ) + 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 @@ -68,6 +77,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 diff --git a/cmake/QtModuleConfigPrivate.cmake.in b/cmake/QtModuleConfigPrivate.cmake.in index 94117e6f9d3..31bf64a4783 100644 --- a/cmake/QtModuleConfigPrivate.cmake.in +++ b/cmake/QtModuleConfigPrivate.cmake.in @@ -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@@[email protected]") include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@[email protected]") diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index 401d04bc84e..5d6a16901c0 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -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" diff --git a/cmake/QtPublicWalkLibsHelpers.cmake b/cmake/QtPublicWalkLibsHelpers.cmake index a7bef65fc56..a7c40076921 100644 --- a/cmake/QtPublicWalkLibsHelpers.cmake +++ b/cmake/QtPublicWalkLibsHelpers.cmake @@ -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. diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake index e29d48359db..261592ab84d 100644 --- a/cmake/QtTargetHelpers.cmake +++ b/cmake/QtTargetHelpers.cmake @@ -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. diff --git a/tests/auto/cmake/mockplugins/CMakeLists.txt b/tests/auto/cmake/mockplugins/CMakeLists.txt index 1bde9aedb19..990191285fb 100644 --- a/tests/auto/cmake/mockplugins/CMakeLists.txt +++ b/tests/auto/cmake/mockplugins/CMakeLists.txt @@ -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) diff --git a/tests/auto/cmake/test_private_includes/CMakeLists.txt b/tests/auto/cmake/test_private_includes/CMakeLists.txt index d4ba8ab888b..bab1143f217 100644 --- a/tests/auto/cmake/test_private_includes/CMakeLists.txt +++ b/tests/auto/cmake/test_private_includes/CMakeLists.txt @@ -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) diff --git a/tests/auto/cmake/test_private_targets/CMakeLists.txt b/tests/auto/cmake/test_private_targets/CMakeLists.txt index 72e06b044ce..79c5b5e0918 100644 --- a/tests/auto/cmake/test_private_targets/CMakeLists.txt +++ b/tests/auto/cmake/test_private_targets/CMakeLists.txt @@ -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) diff --git a/tests/auto/cmake/tst_qaddpreroutine/CMakeLists.txt b/tests/auto/cmake/tst_qaddpreroutine/CMakeLists.txt index bc1a6339db8..d03c811b063 100644 --- a/tests/auto/cmake/tst_qaddpreroutine/CMakeLists.txt +++ b/tests/auto/cmake/tst_qaddpreroutine/CMakeLists.txt @@ -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 |