From 326b91ea788b013512ae911c51cc19497d88916d Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 6 Aug 2021 12:59:29 +0200 Subject: [PATCH] Fix missing QT..run_depends entries in module .pri files The .pri files for public Qt modules were missing run_depends entries. Those entries are used by qmake when linking plugins of a static Qt build. In shared builds, run_depends entries are used to decide whether to add the -rpath-link argument to the linker. In that matter, run_depends entries serve a similar purpose as CMake's IMPORTED_LINK_DEPENDENT_LIBRARIES. In Qt5, run_depends entries consist of the qmake values QT_PRIVATE and QT_FOR_PRIVATE. QT_PRIVATE contains the Qt modules that are privately linked to the public module, and QT_FOR_PRIVATE are the dependencies of the private module. That loosely translates to LINK_LIBRARIES of the public module and INTERFACE_LINK_LIBRARIES of the private module. Private modules whose public counterparts are already public dependencies don't have to be added to run_depends. Pick-to: 6.2 Fixes: QTBUG-84310 Change-Id: If7025c98ea462a2d5a143202df3d98d88e4fee08 Reviewed-by: Alexandru Croitor --- cmake/QtPriHelpers.cmake | 135 ++++++++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 17 deletions(-) diff --git a/cmake/QtPriHelpers.cmake b/cmake/QtPriHelpers.cmake index 75e21c5ce2..3076a314c9 100644 --- a/cmake/QtPriHelpers.cmake +++ b/cmake/QtPriHelpers.cmake @@ -90,9 +90,37 @@ set(QMAKE_DEPENDS_${uclib}_LD, ${deps}) endfunction() # Retrieves the public Qt module dependencies of the given Qt module or Qt Private module. +# The returned dependencies are "config module names", not target names. +# +# PRIVATE (OPTIONAL): +# Retrieve private dependencies only. Dependencies that appear in both, LINK_LIBRARIES and +# INTERFACE_LINK_LIBRARIES are discarded. +# function(qt_get_direct_module_dependencies target out_var) + cmake_parse_arguments(arg "PRIVATE" "" "" ${ARGN}) set(dependencies "") - get_target_property(libs ${target} INTERFACE_LINK_LIBRARIES) + if(arg_PRIVATE) + get_target_property(libs ${target} LINK_LIBRARIES) + get_target_property(public_libs ${target} INTERFACE_LINK_LIBRARIES) + list(REMOVE_DUPLICATES libs) + list(REMOVE_DUPLICATES public_libs) + + # Remove all Qt::Foo and Qt6::Foo from libs that also appear in public_libs. + set(libs_to_remove "") + foreach(lib IN LISTS public_libs) + list(APPEND libs_to_remove "${lib}") + if(lib MATCHES "^Qt::(.*)") + list(APPEND libs_to_remove "${QT_CMAKE_EXPORT_NAMESPACE}::${CMAKE_MATCH_1}") + elseif(lib MATCHES "^{QT_CMAKE_EXPORT_NAMESPACE}::(.*)") + list(APPEND libs_to_remove "Qt::${CMAKE_MATCH_1}") + endif() + endforeach() + + list(REMOVE_DUPLICATES libs_to_remove) + list(REMOVE_ITEM libs ${libs_to_remove}) + else() + get_target_property(libs ${target} INTERFACE_LINK_LIBRARIES) + endif() if(NOT libs) set(libs "") endif() @@ -166,6 +194,60 @@ function(qt_internal_map_targets_to_qmake_libs out_var) set(${out_var} "${result}" PARENT_SCOPE) endfunction() +# Retrieve the runtime dependencies of module ${target}. +# The runtime dependencies are what in CMake is called IMPORTED_LINK_DEPENDENT_LIBRARIES. +# This function returns the dependencies in out_var, separated by space. +# +# PUBLIC_DEPENDENCIES: +# List of the module's public dependencies. +# The dependencies are expected to be config module names. +# +# PUBLIC (OPTIONAL): +# Specifies that target is a public module. +# If not specified, a private module is assumed. +# +function(qt_internal_get_module_run_dependencies out_var target) + cmake_parse_arguments(arg "PUBLIC" "" "PUBLIC_DEPENDENCIES" ${ARGN}) + + # Private dependencies of the module are runtime dependencies. + qt_get_direct_module_dependencies(${target} run_dependencies PRIVATE) + + # If ${target} is a public module then public dependencies + # of the private module are also runtime dependencies. + if(arg_PUBLIC AND TARGET ${target}Private) + qt_get_direct_module_dependencies(${target}Private qt_for_private) + + # FooPrivate depends on Foo, but we must not record this dependency in run_depends. + get_target_property(config_module_name ${target} _qt_config_module_name) + list(REMOVE_ITEM qt_for_private ${config_module_name}) + + list(APPEND run_dependencies ${qt_for_private}) + endif() + + list(REMOVE_DUPLICATES run_dependencies) + + # If foo-private is a private dependency and foo is a public dependency, + # we don't have to add foo-private as runtime dependency. + set(deps_to_remove "") + foreach(dep IN LISTS run_dependencies) + if(NOT dep MATCHES "(.*)_private$") + continue() + endif() + + # Is foo a public dependency? + list(FIND arg_PUBLIC_DEPENDENCIES "${CMAKE_MATCH_1}" idx) + if(idx GREATER -1) + list(APPEND deps_to_remove "${dep}") + endif() + endforeach() + if(NOT "${deps_to_remove}" STREQUAL "") + list(REMOVE_ITEM run_dependencies ${deps_to_remove}) + endif() + + list(JOIN run_dependencies " " run_dependencies) + set("${out_var}" "${run_dependencies}" PARENT_SCOPE) +endfunction() + # Generates module .pri files for consumption by qmake function(qt_generate_module_pri_file target) set(flags INTERNAL_MODULE HEADER_MODULE NO_PRIVATE_MODULE) @@ -299,8 +381,8 @@ $$QT_MODULE_INCLUDE_BASE/${module_versioned_inner_include_dir}") "\nQT.${config_module_name}.plugin_types = ${module_plugin_types}") endif() - qt_get_direct_module_dependencies(${target} public_module_dependencies) - list(JOIN public_module_dependencies " " public_module_dependencies) + qt_get_direct_module_dependencies(${target} public_module_dependencies_list) + list(JOIN public_module_dependencies_list " " public_module_dependencies) set(public_module_dependencies "${module_depends} ${public_module_dependencies}") qt_path_join(pri_file_name "${target_path}" "qt_lib_${config_module_name}.pri") @@ -327,10 +409,12 @@ $$QT_MODULE_INCLUDE_BASE/${module_versioned_inner_include_dir}") qt_internal_map_targets_to_qmake_libs(module_uses ${dep_targets}) list(JOIN module_uses " " joined_module_uses) - file(GENERATE - OUTPUT "${pri_file_name}" - CONTENT - "QT.${config_module_name}.VERSION = ${PROJECT_VERSION} + # Retrieve the public module's runtime dependencies. + qt_internal_get_module_run_dependencies(public_module_run_dependencies ${target} + PUBLIC + PUBLIC_DEPENDENCIES "${public_module_dependencies_list}") + + set(content "QT.${config_module_name}.VERSION = ${PROJECT_VERSION} QT.${config_module_name}.name = ${module} QT.${config_module_name}.module = ${module_name_in_pri} QT.${config_module_name}.libs = $$QT_MODULE_LIB_BASE @@ -339,7 +423,13 @@ QT.${config_module_name}.includes = ${public_module_includes} QT.${config_module_name}.frameworks = ${public_module_frameworks} QT.${config_module_name}.bins = $$QT_MODULE_BIN_BASE${module_plugin_types_assignment} QT.${config_module_name}.depends = ${public_module_dependencies} -QT.${config_module_name}.uses = ${joined_module_uses} +") + if(NOT "${public_module_run_dependencies}" STREQUAL "") + string(APPEND content + "QT.${config_module_name}.run_depends = ${public_module_run_dependencies}\n") + endif() + string(APPEND content + "QT.${config_module_name}.uses = ${joined_module_uses} QT.${config_module_name}.module_config = ${joined_module_internal_config}${module_build_config} QT.${config_module_name}.DEFINES = ${joined_target_defines} QT.${config_module_name}.enabled_features = ${enabled_features} @@ -347,8 +437,8 @@ QT.${config_module_name}.disabled_features = ${disabled_features}${extra_assignm QT_CONFIG += ${enabled_features} QT_MODULES += ${config_module_name_base} ${module_pri_extra_content} -" - ) +") + file(GENERATE OUTPUT "${pri_file_name}" CONTENT "${content}") if (NOT arg_NO_PRIVATE_MODULE AND NOT arg_INTERNAL_MODULE) set(pri_data_cmake_file "qt_lib_${config_module_name}_private.cmake") @@ -372,22 +462,33 @@ ${module_pri_extra_content} qt_internal_map_targets_to_qmake_libs(private_module_uses ${dep_targets}) list(JOIN private_module_uses " " joined_private_module_uses) + # Retrieve the private module's runtime dependencies. + qt_internal_get_module_run_dependencies(private_module_run_dependencies ${target}Private + PUBLIC_DEPENDENCIES "${dep_targets}") + # Generate a preliminary qt_lib_XXX_private.pri file - file(GENERATE - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${private_pri_file_name}" - CONTENT - "QT.${config_module_name}_private.VERSION = ${PROJECT_VERSION} + set(content + "QT.${config_module_name}_private.VERSION = ${PROJECT_VERSION} QT.${config_module_name}_private.name = ${module} QT.${config_module_name}_private.module = QT.${config_module_name}_private.libs = $$QT_MODULE_LIB_BASE QT.${config_module_name}_private.includes = ${private_module_includes} QT.${config_module_name}_private.frameworks = ${private_module_frameworks} QT.${config_module_name}_private.depends = ${private_module_dependencies} -QT.${config_module_name}_private.uses = ${joined_private_module_uses} +") + if(NOT "${private_module_run_dependencies}" STREQUAL "") + string(APPEND content + "QT.${config_module_name}_private.run_depends = ${private_module_run_dependencies} +") + endif() + string(APPEND content + "QT.${config_module_name}_private.uses = ${joined_private_module_uses} QT.${config_module_name}_private.module_config = ${joined_module_internal_config} QT.${config_module_name}_private.enabled_features = ${enabled_private_features} -QT.${config_module_name}_private.disabled_features = ${disabled_private_features}" - ) +QT.${config_module_name}_private.disabled_features = ${disabled_private_features}") + file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${private_pri_file_name}" + CONTENT "${content}") if(QT_GENERATOR_IS_MULTI_CONFIG) set(configs ${CMAKE_CONFIGURATION_TYPES})