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:
parent
d829d54a42
commit
561fc8107f
@ -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()
|
||||
|
@ -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}")
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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::(.*)")
|
||||
|
@ -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}")
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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}")
|
||||
|
@ -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)
|
||||
|
||||
|
13
tests/auto/cmake/test_global_promotion/CMakeLists.txt
Normal file
13
tests/auto/cmake/test_global_promotion/CMakeLists.txt
Normal 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)
|
@ -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")
|
@ -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")
|
Loading…
Reference in New Issue
Block a user