CMake: Allow promoting the Qt libraries to be global targets

User projects can set the QT_PROMOTE_TO_GLOBAL_TARGETS variable to
true so that the various imported targets created by find_package(Qt6)
are promoted to global targets.

This would allow a project to find Qt packages in a subdirectory scope
while using those Qt targets from a different scope.

E.g. it fixes errors like

  CMake Error at CMakeLists.txt:5 (target_link_libraries):
  Error evaluating generator expression:

    $<TARGET_OBJECTS:Qt6::Widgets_resources_1>

  Objects of target "Qt6::Widgets_resources_1" referenced but no such
  target exists.

when trying to use a static Qt from a sibling scope.

Various 3rd party dependency targets (like Atomic or ZLIB) are not
made global due to limitations in CMake, but as long as those targets
are not mentioned directly, it shouldn't cause issues.

The targets are made global in the generated
QtFooAdditionalTargetInfo.cmake file.

To ensure that resource object libraries promoted, the generation
of the file has to be done at the end of the defining scope
where qt_internal_export_additional_targets_file is called,
which is achieved with a deferred finalizer.

Replaced all occurrences of target promotion with a helper function
which allows tracing of all promoted targets by specifying
--log-level=debug to CMake.

Pick-to: 6.2
Fixes: QTBUG-92878
Change-Id: Ic4ec03b0bc383d7e591a58c520c3974fbea746d2
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Alexandru Croitor 2021-05-20 13:38:30 +02:00
parent d829d54a42
commit 561fc8107f
12 changed files with 197 additions and 17 deletions

View File

@ -159,8 +159,7 @@ macro(qt_find_package)
qt_internal_should_not_promote_package_target_to_global(
"${qt_find_package_target_name}" should_not_promote)
if(NOT is_global AND NOT should_not_promote)
set_property(TARGET ${qt_find_package_target_name} PROPERTY
IMPORTED_GLOBAL TRUE)
__qt_internal_promote_target_to_global(${qt_find_package_target_name})
qt_find_package_promote_targets_to_global_scope(
"${qt_find_package_target_name}")
endif()

View File

@ -343,7 +343,7 @@ function(qt_internal_add_plugin target)
qt_path_join(config_install_dir ${QT_CONFIG_INSTALL_DIR} ${path_suffix})
qt_internal_export_additional_targets_file(
TARGETS ${target}
TARGETS ${target} ${plugin_init_target}
EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${target}
CONFIG_INSTALL_DIR "${config_install_dir}")

View File

@ -114,3 +114,17 @@ function(__qt_internal_collect_resource_objects_recursively out_var target initi
endforeach()
set(${out_var} "${resource_targets}" PARENT_SCOPE)
endfunction()
function(__qt_internal_promote_target_to_global target)
get_property(is_global TARGET ${target} PROPERTY IMPORTED_GLOBAL)
if(NOT is_global)
message(DEBUG "Promoting target to global: '${target}'")
set_property(TARGET ${target} PROPERTY IMPORTED_GLOBAL TRUE)
endif()
endfunction()
function(__qt_internal_promote_target_to_global_checked target)
if(QT_PROMOTE_TO_GLOBAL_TARGETS)
__qt_internal_promote_target_to_global(${target})
endif()
endfunction()

View File

@ -222,14 +222,13 @@ function(__qt_internal_walk_libs
endif()
get_property(is_imported TARGET ${lib_target_unaliased} PROPERTY IMPORTED)
get_property(is_global TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL)
# Allow opting out of promotion. This is useful in certain corner cases
# like with WrapLibClang and Threads in qttools.
qt_internal_should_not_promote_package_target_to_global(
"${lib_target_unaliased}" should_not_promote)
if(NOT is_global AND is_imported AND NOT should_not_promote)
set_property(TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL TRUE)
if(is_imported AND NOT should_not_promote)
__qt_internal_promote_target_to_global(${lib_target_unaliased})
endif()
endif()
elseif("${lib_target}" MATCHES "^Qt::(.*)")

View File

@ -21,6 +21,10 @@ function(qt_internal_add_resource target resourceName)
EXPORT "${INSTALL_CMAKE_NAMESPACE}${target}Targets"
DESTINATION "${INSTALL_LIBDIR}"
)
qt_internal_add_targets_to_additional_targets_export_file(
TARGETS ${out_targets}
EXPORT_NAME_PREFIX "${INSTALL_CMAKE_NAMESPACE}${target}"
)
qt_internal_record_rcc_object_files("${target}" "${out_targets}"
INSTALL_DIRECTORY "${INSTALL_LIBDIR}")

View File

@ -74,6 +74,9 @@ function(qt_watch_current_list_dir variable access value current_list_file stack
qt_finalize_plugin(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
elseif(func STREQUAL "qt_internal_finalize_app")
qt_internal_finalize_app(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
elseif(func STREQUAL "qt_internal_export_additional_targets_file_finalizer")
qt_internal_export_additional_targets_file_finalizer(
${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
else()
message(FATAL_ERROR "qt_watch_current_list_dir doesn't know about ${func}. Consider adding it.")
endif()

View File

@ -264,9 +264,26 @@ function(qt_internal_check_directory_or_type name dir type default result_var)
endif()
endfunction()
macro(qt_internal_get_export_additional_targets_keywords option_args single_args multi_args)
set(${option_args}
)
set(${single_args}
EXPORT_NAME_PREFIX
)
set(${multi_args}
TARGETS
TARGET_EXPORT_NAMES
)
endmacro()
# Create a Qt*AdditionalTargetInfo.cmake file that is included by Qt*Config.cmake
# and sets IMPORTED_*_<CONFIG> properties on the exported targets.
#
# The file also makes the targets global if the QT_PROMOTE_TO_GLOBAL_TARGETS property is set in the
# consuming project.
# Only the specified TARGETS are made global. Transitive 3rd party targets are not made global, due
# to limitations in CMake. See https://gitlab.kitware.com/cmake/cmake/-/issues/22291
#
# EXPORT_NAME_PREFIX:
# The portion of the file name before AdditionalTargetInfo.cmake
# CONFIG_INSTALL_DIR:
@ -283,14 +300,75 @@ endfunction()
# TARGET_EXPORT_NAMES = Qt6::qmljs
#
function(qt_internal_export_additional_targets_file)
cmake_parse_arguments(arg "" "EXPORT_NAME_PREFIX;CONFIG_INSTALL_DIR"
"TARGETS;TARGET_EXPORT_NAMES" ${ARGN})
qt_internal_get_export_additional_targets_keywords(option_args single_args multi_args)
cmake_parse_arguments(arg
"${option_args}"
"${single_args};CONFIG_INSTALL_DIR"
"${multi_args}"
${ARGN})
qt_internal_append_export_additional_targets()
set_property(GLOBAL APPEND PROPERTY _qt_export_additional_targets_ids "${id}")
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_targets_export_name_prefix_${id} "${arg_EXPORT_NAME_PREFIX}")
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_targets_config_install_dir_${id} "${arg_CONFIG_INSTALL_DIR}")
qt_add_list_file_finalizer(qt_internal_export_additional_targets_file_finalizer)
endfunction()
function(qt_internal_get_export_additional_targets_id export_name out_var)
string(MAKE_C_IDENTIFIER "${export_name}" id)
set(${out_var} "${id}" PARENT_SCOPE)
endfunction()
# Uses outer-scope variables to keep the implementation less verbose.
macro(qt_internal_append_export_additional_targets)
qt_internal_validate_export_additional_targets(
EXPORT_NAME_PREFIX "${arg_EXPORT_NAME_PREFIX}"
TARGETS ${arg_TARGETS}
TARGET_EXPORT_NAMES ${arg_TARGET_EXPORT_NAMES})
qt_internal_get_export_additional_targets_id("${arg_EXPORT_NAME_PREFIX}" id)
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_targets_${id} "${arg_TARGETS}")
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_target_export_names_${id} "${arg_TARGET_EXPORT_NAMES}")
endmacro()
# Can be called to add additional targets to the file after the initial setup call.
# Used for resources.
function(qt_internal_add_targets_to_additional_targets_export_file)
qt_internal_get_export_additional_targets_keywords(option_args single_args multi_args)
cmake_parse_arguments(arg
"${option_args}"
"${single_args}"
"${multi_args}"
${ARGN})
qt_internal_append_export_additional_targets()
endfunction()
function(qt_internal_validate_export_additional_targets)
qt_internal_get_export_additional_targets_keywords(option_args single_args multi_args)
cmake_parse_arguments(arg
"${option_args}"
"${single_args}"
"${multi_args}"
${ARGN})
if(NOT arg_EXPORT_NAME_PREFIX)
message(FATAL_ERROR "qt_internal_validate_export_additional_targets: "
"Missing EXPORT_NAME_PREFIX argument.")
endif()
list(LENGTH arg_TARGETS num_TARGETS)
list(LENGTH arg_TARGET_EXPORT_NAMES num_TARGET_EXPORT_NAMES)
if(num_TARGET_EXPORT_NAMES GREATER 0)
if(NOT num_TARGETS EQUAL num_TARGET_EXPORT_NAMES)
message(FATAL_ERROR "qt_internal_export_additional_targets_file: "
message(FATAL_ERROR "qt_internal_validate_export_additional_targets: "
"TARGET_EXPORT_NAMES is set but has ${num_TARGET_EXPORT_NAMES} elements while "
"TARGETS has ${num_TARGETS} elements. "
"They must contain the same number of elements.")
@ -299,6 +377,34 @@ function(qt_internal_export_additional_targets_file)
set(arg_TARGET_EXPORT_NAMES ${arg_TARGETS})
endif()
set(arg_TARGETS "${arg_TARGETS}" PARENT_SCOPE)
set(arg_TARGET_EXPORT_NAMES "${arg_TARGET_EXPORT_NAMES}" PARENT_SCOPE)
endfunction()
# The finalizer might be called multiple times in the same scope, but only the first one will
# process all the ids.
function(qt_internal_export_additional_targets_file_finalizer)
get_property(ids GLOBAL PROPERTY _qt_export_additional_targets_ids)
foreach(id ${ids})
qt_internal_export_additional_targets_file_handler("${id}")
endforeach()
set_property(GLOBAL PROPERTY _qt_export_additional_targets_ids "")
endfunction()
function(qt_internal_export_additional_targets_file_handler id)
get_property(arg_EXPORT_NAME_PREFIX GLOBAL PROPERTY
_qt_export_additional_targets_export_name_prefix_${id})
get_property(arg_CONFIG_INSTALL_DIR GLOBAL PROPERTY
_qt_export_additional_targets_config_install_dir_${id})
get_property(arg_TARGETS GLOBAL PROPERTY
_qt_export_additional_targets_${id})
get_property(arg_TARGET_EXPORT_NAMES GLOBAL PROPERTY
_qt_export_additional_target_export_names_${id})
list(LENGTH arg_TARGETS num_TARGETS)
# Determine the release configurations we're currently building
if(QT_GENERATOR_IS_MULTI_CONFIG)
set(active_configurations ${CMAKE_CONFIGURATION_TYPES})
@ -337,18 +443,33 @@ if(NOT DEFINED QT_DEFAULT_IMPORT_CONFIGURATION)
set(QT_DEFAULT_IMPORT_CONFIGURATION ${uc_default_cfg})
endif()
")
math(EXPR n "${num_TARGETS} - 1")
foreach(i RANGE ${n})
list(GET arg_TARGETS ${i} target)
list(GET arg_TARGET_EXPORT_NAMES ${i} target_export_name)
get_target_property(target_type ${target} TYPE)
if(target_type STREQUAL "INTERFACE_LIBRARY")
continue()
endif()
set(full_target ${target_export_name})
if(NOT full_target MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::")
string(PREPEND full_target "${QT_CMAKE_EXPORT_NAMESPACE}::")
endif()
# Tools are already made global unconditionally in QtFooToolsConfig.cmake.
# And the
get_target_property(target_type ${target} TYPE)
if(NOT target_type STREQUAL "EXECUTABLE")
string(APPEND content
"__qt_internal_promote_target_to_global_checked(${full_target})\n")
endif()
# INTERFACE libraries don't have IMPORTED_LOCATION-like properties.
# OBJECT libraries have properties like IMPORTED_OBJECTS instead.
# Skip the rest of the procesing for those.
if(target_type STREQUAL "INTERFACE_LIBRARY" OR target_type STREQUAL "OBJECT_LIBRARY")
continue()
endif()
# FIXME: Don't add IMPORTED_SOLIB and IMPORTED_SONAME properties for executables.
set(properties_retrieved TRUE)
if(NOT "${uc_release_cfg}" STREQUAL "")
string(APPEND content "get_target_property(_qt_imported_location ${full_target} IMPORTED_LOCATION_${uc_release_cfg})\n")

View File

@ -321,10 +321,7 @@ function(qt_export_tools module_name)
endif()
set(extra_cmake_statements "${extra_cmake_statements}
if (NOT QT_NO_CREATE_TARGETS)
get_property(is_global TARGET ${INSTALL_CMAKE_NAMESPACE}::${tool_name} PROPERTY IMPORTED_GLOBAL)
if(NOT is_global)
set_property(TARGET ${INSTALL_CMAKE_NAMESPACE}::${tool_name} PROPERTY IMPORTED_GLOBAL TRUE)
endif()
__qt_internal_promote_target_to_global(${INSTALL_CMAKE_NAMESPACE}::${tool_name})
endif()
")
list(APPEND tool_targets "${QT_CMAKE_EXPORT_NAMESPACE}::${tool_name}")

View File

@ -247,6 +247,10 @@ set_tests_properties(test_import_plugins PROPERTIES FIXTURES_REQUIRED build_mock
_qt_internal_test_expect_pass(test_versionless_targets)
if(NOT NO_GUI)
_qt_internal_test_expect_pass(test_global_promotion)
endif()
_qt_internal_test_expect_pass(test_add_resources_binary_generated
BINARY test_add_resources_binary_generated)

View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14)
project(global_promotion)
add_subdirectory(subdir_with_local_qt)
add_subdirectory(subdir_with_global_qt)
set(file_path "${CMAKE_CURRENT_BINARY_DIR}/main.cpp")
file(GENERATE OUTPUT "${file_path}" CONTENT "int main() { return 0; }")
add_executable(exe main.cpp)
# The Qt targets found in the 2nd child directory scope should be available in this scope.
target_link_libraries(exe PRIVATE lib_global_qt)

View File

@ -0,0 +1,14 @@
message(STATUS "Entered subdir_with_global_qt subdirectory")
set(file_path "${CMAKE_CURRENT_BINARY_DIR}/lib.cpp")
file(GENERATE OUTPUT "${file_path}" CONTENT "int foo() { return 42; }")
add_library(lib_global_qt STATIC "${file_path}")
# These Qt targets will be available in all scopes of the project.
# The previous local targets are simply shadowed.
set(QT_PROMOTE_TO_GLOBAL_TARGETS ON)
find_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(lib_global_qt PRIVATE Qt6::Gui)
message(STATUS "Exiting subdir_with_global_qt subdirectory")

View File

@ -0,0 +1,12 @@
message(STATUS "Entered subdir_with_local_qt subdirectory")
set(file_path "${CMAKE_CURRENT_BINARY_DIR}/lib.cpp")
file(GENERATE OUTPUT "${file_path}" CONTENT "int foo() { return 42; }")
add_library(lib_local_qt STATIC "${file_path}")
# These Qt targets will be local to this directory scope.
find_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(lib_local_qt PRIVATE Qt6::Gui)
message(STATUS "Exiting subdir_with_local_qt subdirectory")