summaryrefslogtreecommitdiffstats
path: root/cmake/QtPublicPluginHelpers.cmake
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2021-04-28 16:27:19 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2021-05-20 00:55:46 +0200
commit6fcc272ac9dcf1d6d65de1bdf3138722ba63a902 (patch)
tree2f36991a56e28332c61a580753c637c301f829e4 /cmake/QtPublicPluginHelpers.cmake
parent06689a2d7a18882535819ed13ac7248c81330529 (diff)
CMake: Introduce finalizer mode handling of static plugins
Allow linking all plugin initializer object libraries directly into the final target (executable or shared library). The finalizer mode is triggered when the project adds a call to qt_import_plugins, as well when the project has an explicit call to qt_finalize_executable or when it is defer called by CMake 3.19+. Otherwise the old non-finalizer mode is used, where each plugin initializer object library is propagated via the usage requirements of its associated module. A user can explicitly opt in or out of the new mode by calling qt_enable_import_plugins_finalizer_mode(target TRUE/FALSE) The implementation, at configure time, recursively collects all dependencies of the target to extract a list of used Qt modules. From each module we extract its list of associated plugins and their genex conditions. These genexes are used to conditionally link the plugins and the initializers. Renamed QT_PLUGINS property to _qt_plugins, so we can safely query the property even on INTERFACE libraries with lower CMake versions. QT_PLUGINS is kept for backwards compatibility with projects already using it, but should be removed in Qt 7. The upside of the finalizer mode is that it avoids creating link cycles (e.g. Gui -> SvgPlugin -> Gui case) which causes CMake to duplicate the library on the link line, slowing down link time as well as possibly breaking link order dependencies. The downside is that finalizer mode can't cope with generator expressions at the moment. So if a Qt module target is wrapped in a generator expression, it's plugins will not be detected and thus linked. Task-number: QTBUG-80863 Task-number: QTBUG-92933 Change-Id: Ic40c8ae5807a154ed18fcac18b25f00864c8f143 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'cmake/QtPublicPluginHelpers.cmake')
-rw-r--r--cmake/QtPublicPluginHelpers.cmake182
1 files changed, 180 insertions, 2 deletions
diff --git a/cmake/QtPublicPluginHelpers.cmake b/cmake/QtPublicPluginHelpers.cmake
index 34790fe6a3..35026fa395 100644
--- a/cmake/QtPublicPluginHelpers.cmake
+++ b/cmake/QtPublicPluginHelpers.cmake
@@ -32,6 +32,13 @@ function(__qt_internal_plugin_has_class_name plugin_target out_var)
set(${out_var} "${classname}" PARENT_SCOPE)
endfunction()
+# Constructs a generator expression which decides whether a plugin will be used.
+#
+# The conditions are based on the various properties set in qt_import_plugins.
+
+# All the TARGET_PROPERTY genexes are evaluated in the context of the currently linked target.
+#
+# The genex is saved into out_var.
function(__qt_internal_get_static_plugin_condition_genex
plugin_target_unprefixed
out_var)
@@ -49,9 +56,10 @@ function(__qt_internal_get_static_plugin_condition_genex
# Plugin genex marker for prl processing.
set(_is_plugin_marker_genex "$<BOOL:QT_IS_PLUGIN_GENEX>")
- # The code in here uses the properties defined in qt_import_plugins (Qt6CoreMacros.cmake)
set(_plugin_is_default "$<TARGET_PROPERTY:${plugin_target},QT_DEFAULT_PLUGIN>")
+ # The code in here uses the properties defined in qt_import_plugins (Qt6CoreMacros.cmake)
+
# INCLUDE
set(_plugin_is_whitelisted "$<IN_LIST:${plugin_target},${_manual_plugins_genex}>")
set(_plugin_versionless_is_whitelisted
@@ -120,14 +128,23 @@ function(__qt_internal_get_static_plugin_condition_genex
set(${out_var} "${_plugin_condition}" PARENT_SCOPE)
endfunction()
+# Wraps the genex condition to evaluate to true only when using the regular plugin importing mode
+# (not finalizer mode).
+function(__qt_internal_get_plugin_condition_regular_mode plugin_condition out_var)
+ set(not_finalizer_mode "$<NOT:$<BOOL:$<TARGET_PROPERTY:_qt_static_plugins_use_finalizer_mode>>>")
+ set(full_plugin_condition "$<AND:${plugin_condition},${not_finalizer_mode}>")
+ set(${out_var} "${full_plugin_condition}" PARENT_SCOPE)
+endfunction()
+
# Link plugin via usage requirements of associated Qt module.
function(__qt_internal_add_static_plugin_linkage plugin_target qt_module_target)
__qt_internal_get_static_plugin_condition_genex("${plugin_target}" plugin_condition)
+ __qt_internal_get_plugin_condition_regular_mode("${plugin_condition}" full_plugin_condition)
set(plugin_target "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
# If this condition is true, we link against the plug-in
- set(plugin_genex "$<${plugin_condition}:${plugin_target}>")
+ set(plugin_genex "$<${full_plugin_condition}:${plugin_target}>")
target_link_libraries(${qt_module_target} INTERFACE "${plugin_genex}")
endfunction()
@@ -205,3 +222,164 @@ function(__qt_internal_add_static_plugin_init_object_library
set(${out_var_plugin_init_target} "${plugin_init_target}" PARENT_SCOPE)
endfunction()
+# Collect a list of genexes to link plugin libraries.
+function(__qt_internal_collect_plugin_libraries plugin_targets out_var)
+ set(plugins_to_link "")
+
+ foreach(plugin_target ${plugin_targets})
+ __qt_internal_get_static_plugin_condition_genex(
+ "${plugin_target}"
+ plugin_condition)
+
+ set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
+
+ list(APPEND plugins_to_link "$<${plugin_condition}:${plugin_target_versioned}>")
+ endforeach()
+
+ set("${out_var}" "${plugins_to_link}" PARENT_SCOPE)
+endfunction()
+
+# Collect a list of genexes to link plugin initializer object libraries.
+#
+# The object libraries are only linked if the associated plugins are linked.
+function(__qt_internal_collect_plugin_init_libraries plugin_targets out_var)
+ set(plugin_inits_to_link "")
+
+ foreach(plugin_target ${plugin_targets})
+ __qt_internal_get_static_plugin_condition_genex(
+ "${plugin_target}"
+ plugin_condition)
+
+ __qt_internal_get_static_plugin_init_target_name("${plugin_target}" plugin_init_target)
+ set(plugin_init_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_init_target}")
+
+ list(APPEND plugin_inits_to_link "$<${plugin_condition}:${plugin_init_target_versioned}>")
+ endforeach()
+
+ set("${out_var}" "${plugin_inits_to_link}" PARENT_SCOPE)
+endfunction()
+
+# Collects all plugin targets discovered by walking the dependencies of ${target}.
+#
+# Walks immediate dependencies and their transitive dependencies.
+# Plugins are collected by inspecting the _qt_plugins property found on any dependency Qt target.
+function(__qt_internal_collect_plugin_targets_from_dependencies target out_var)
+ set(dep_targets "")
+
+ __qt_internal_collect_all_target_dependencies("${target}" dep_targets)
+
+ set(plugin_targets "")
+ foreach(dep_target ${dep_targets})
+ get_target_property(plugins ${dep_target} _qt_plugins)
+ if(plugins)
+ list(APPEND plugin_targets ${plugins})
+ endif()
+ endforeach()
+
+ # Plugins that are specified via qt_import_plugin's INCLUDE or INCLUDE_BY_TYPE can have
+ # dependencies on Qt modules. These modules in turn might bring in more default plugins to link
+ # So it's recursive. Do only one pass for now. Try to extract the included static plugins, walk
+ # their public and private dependencies, check if any of them are Qt modules that provide more
+ # plugins and extract the target names of those plugins.
+ __qt_internal_collect_plugin_targets_from_dependencies_of_plugins(
+ "${target}" recursive_plugin_targets)
+ if(recursive_plugin_targets)
+ list(APPEND plugin_targets ${recursive_plugin_targets})
+ endif()
+ list(REMOVE_DUPLICATES plugin_targets)
+
+ set("${out_var}" "${plugin_targets}" PARENT_SCOPE)
+endfunction()
+
+# Helper to collect plugin targets from encountered module dependencies as a result of walking
+# dependencies. These module dependencies might expose additional plugins.
+function(__qt_internal_collect_plugin_targets_from_dependencies_of_plugins target out_var)
+ set(assigned_plugins_overall "")
+
+ get_target_property(assigned_qt_plugins "${target}" QT_PLUGINS)
+
+ if(assigned_qt_plugins)
+ foreach(assigned_qt_plugin ${assigned_qt_plugins})
+ if(TARGET "${assigned_qt_plugin}")
+ list(APPEND assigned_plugins_overall "${assigned_qt_plugin}")
+ endif()
+ endforeach()
+ endif()
+
+ get_target_property(assigned_qt_plugins_by_type "${target}" _qt_plugins_by_type)
+
+ if(assigned_qt_plugins_by_type)
+ foreach(assigned_qt_plugin ${assigned_qt_plugins_by_type})
+ if(TARGET "${assigned_qt_plugin}")
+ list(APPEND assigned_plugins_overall "${assigned_qt_plugin}")
+ endif()
+ endforeach()
+ endif()
+
+ set(plugin_targets "")
+ foreach(target ${assigned_plugins_overall})
+ __qt_internal_walk_libs(
+ "${target}"
+ dep_targets
+ _discarded_out_var
+ "qt_private_link_library_targets"
+ "collect_targets")
+
+ foreach(dep_target ${dep_targets})
+ get_target_property(plugins ${dep_target} _qt_plugins)
+ if(plugins)
+ list(APPEND plugin_targets ${plugins})
+ endif()
+ endforeach()
+ endforeach()
+
+ list(REMOVE_DUPLICATES plugin_targets)
+
+ set("${out_var}" "${plugin_targets}" PARENT_SCOPE)
+endfunction()
+
+# Helper to check if the finalizer mode of plugin importing should be used.
+# If true or unset, use finalizer mode.
+# If false, use regular mode (usage requirement propagation via associated Qt module)
+function(__qt_internal_get_plugin_imports_finalizer_mode target out_var)
+ get_target_property(value ${target} _qt_static_plugins_use_finalizer_mode)
+ if("${value}" STREQUAL "value-NOTFOUND")
+ set(value "")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+# Main logic of finalizer mode.
+function(__qt_internal_apply_plugin_imports_finalizer_mode target)
+ # Nothing to do in a shared build.
+ if(QT6_IS_SHARED_LIBS_BUILD)
+ return()
+ endif()
+
+ # Process a target only once.
+ get_target_property(processed ${target} _qt_plugin_finalizer_imports_processed)
+ if(processed)
+ return()
+ endif()
+
+ __qt_internal_get_plugin_imports_finalizer_mode(${target} use_finalizer_mode)
+
+ # By default if the project hasn't explicitly opted in or out, use finalizer mode.
+ if("${use_finalizer_mode}" STREQUAL "")
+ qt_enable_import_plugins_finalizer_mode(${target} TRUE)
+ endif()
+
+ # Check if the project chose a mode explicitly.
+ __qt_internal_get_plugin_imports_finalizer_mode(${target} use_finalizer_mode)
+ if(NOT use_finalizer_mode)
+ return()
+ endif()
+
+ __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets)
+ __qt_internal_collect_plugin_init_libraries("${plugin_targets}" init_libraries)
+ __qt_internal_collect_plugin_libraries("${plugin_targets}" plugin_libraries)
+
+ target_link_libraries(${target} PRIVATE "${plugin_libraries}" "${init_libraries}")
+
+ set_target_properties(${target} PROPERTIES _qt_plugin_finalizer_imports_processed TRUE)
+endfunction()