cmake: Link static plugins even in shared Qt builds

It's perfectly possible to create static plugins in an otherwise shared
Qt build, but the logic to import these plugins into applications was
assuming a fully static Qt build. We now handle this more granularly.

This works for in-source tools and tests as well, which don't go through
the same CMake machinery for plugins as user projects do. The only case
that does not currently work is in-source examples, as they don't share
any of the plugin machinery with neither Qt internal tools/tests or user
project, but that's a bigger architectural issue that goes beyond this
change.

Change-Id: Ie00a97b02ac38ec4affadc447a3bfd0ec7d9c69a
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Tor Arne Vestbø 2022-07-18 14:20:37 +02:00
parent 5a4e5c62af
commit f68e2c92cc
3 changed files with 146 additions and 136 deletions

View File

@ -193,93 +193,92 @@ function(qt_internal_add_executable name)
add_dependencies("${name}" qpa_default_plugins) add_dependencies("${name}" qpa_default_plugins)
endif() endif()
if(NOT BUILD_SHARED_LIBS) # For static plugins, we need to explicitly link to plugins we want to be
# For static builds, we need to explicitly link to plugins we want to be # loaded with the executable. User projects get that automatically, but
# loaded with the executable. User projects get that automatically, but # for tools built as part of Qt, we can't use that mechanism because it
# for tools built as part of Qt, we can't use that mechanism because it # would pollute the targets we export as part of an install and lead to
# would pollute the targets we export as part of an install and lead to # circular dependencies. The logic here is a simpler equivalent of the
# circular dependencies. The logic here is a simpler equivalent of the # more dynamic logic in QtPlugins.cmake.in, but restricted to only
# more dynamic logic in QtPlugins.cmake.in, but restricted to only # adding plugins that are provided by the same module as the module
# adding plugins that are provided by the same module as the module # libraries the executable links to.
# libraries the executable links to. set(libs
set(libs ${arg_LIBRARIES}
${arg_LIBRARIES} ${arg_PUBLIC_LIBRARIES}
${arg_PUBLIC_LIBRARIES} ${extra_libraries}
${extra_libraries} Qt::PlatformCommonInternal
Qt::PlatformCommonInternal )
)
set(deduped_libs "") set(deduped_libs "")
foreach(lib IN LISTS libs) foreach(lib IN LISTS libs)
if(NOT TARGET "${lib}") if(NOT TARGET "${lib}")
continue() continue()
endif()
# Normalize module by stripping any leading "Qt::", because properties are set on the
# versioned target (either Gui when building the module, or Qt6::Gui when it's
# imported).
if(lib MATCHES "Qt::([-_A-Za-z0-9]+)")
set(new_lib "${QT_CMAKE_EXPORT_NAMESPACE}::${CMAKE_MATCH_1}")
if(TARGET "${new_lib}")
set(lib "${new_lib}")
endif() endif()
endif()
# Normalize module by stripping any leading "Qt::", because properties are set on the # Unalias the target.
# versioned target (either Gui when building the module, or Qt6::Gui when it's get_target_property(aliased_target ${lib} ALIASED_TARGET)
# imported). if(aliased_target)
if(lib MATCHES "Qt::([-_A-Za-z0-9]+)") set(lib ${aliased_target})
set(new_lib "${QT_CMAKE_EXPORT_NAMESPACE}::${CMAKE_MATCH_1}") endif()
if(TARGET "${new_lib}")
set(lib "${new_lib}")
endif()
endif()
# Unalias the target. list(APPEND deduped_libs "${lib}")
get_target_property(aliased_target ${lib} ALIASED_TARGET) endforeach()
if(aliased_target)
set(lib ${aliased_target})
endif()
list(APPEND deduped_libs "${lib}") list(REMOVE_DUPLICATES deduped_libs)
endforeach()
list(REMOVE_DUPLICATES deduped_libs) foreach(lib IN LISTS deduped_libs)
string(MAKE_C_IDENTIFIER "${name}_plugin_imports_${lib}" out_file)
string(APPEND out_file .cpp)
foreach(lib IN LISTS deduped_libs) # Initialize plugins that are built in the same repository as the Qt module 'lib'.
string(MAKE_C_IDENTIFIER "${name}_plugin_imports_${lib}" out_file) set(class_names_regular
string(APPEND out_file .cpp) "$<GENEX_EVAL:$<TARGET_PROPERTY:${lib},_qt_initial_repo_plugin_class_names>>")
# Initialize plugins that are built in the same repository as the Qt module 'lib'. # Initialize plugins that are built in the current Qt repository, but are associated
set(class_names_regular # with a Qt module from a different repository (qtsvg's QSvgPlugin associated with
"$<GENEX_EVAL:$<TARGET_PROPERTY:${lib},_qt_initial_repo_plugin_class_names>>") # qtbase's QtGui).
string(MAKE_C_IDENTIFIER "${PROJECT_NAME}" current_project_name)
set(prop_prefix "_qt_repo_${current_project_name}")
set(class_names_current_project
"$<GENEX_EVAL:$<TARGET_PROPERTY:${lib},${prop_prefix}_plugin_class_names>>")
# Initialize plugins that are built in the current Qt repository, but are associated # Only add separator if first list is not empty, so we don't trigger the file generation
# with a Qt module from a different repository (qtsvg's QSvgPlugin associated with # when all lists are empty.
# qtbase's QtGui). set(class_names_separator "$<$<NOT:$<STREQUAL:${class_names_regular},>>:;>" )
string(MAKE_C_IDENTIFIER "${PROJECT_NAME}" current_project_name) set(class_names
set(prop_prefix "_qt_repo_${current_project_name}") "${class_names_regular}${class_names_separator}${class_names_current_project}")
set(class_names_current_project
"$<GENEX_EVAL:$<TARGET_PROPERTY:${lib},${prop_prefix}_plugin_class_names>>")
# Only add separator if first list is not empty, so we don't trigger the file generation set(out_file_path "${CMAKE_CURRENT_BINARY_DIR}/${out_file}")
# when all lists are empty.
set(class_names_separator "$<$<NOT:$<STREQUAL:${class_names_regular},>>:;>" )
set(class_names
"${class_names_regular}${class_names_separator}${class_names_current_project}")
set(out_file_path "${CMAKE_CURRENT_BINARY_DIR}/${out_file}") file(GENERATE OUTPUT "${out_file_path}" CONTENT
file(GENERATE OUTPUT "${out_file_path}" CONTENT
"// This file is auto-generated. Do not edit. "// This file is auto-generated. Do not edit.
#include <QtPlugin> #include <QtPlugin>
Q_IMPORT_PLUGIN($<JOIN:${class_names},)\nQ_IMPORT_PLUGIN(>) Q_IMPORT_PLUGIN($<JOIN:${class_names},)\nQ_IMPORT_PLUGIN(>)
" "
CONDITION "$<NOT:$<STREQUAL:${class_names},>>" CONDITION "$<NOT:$<STREQUAL:${class_names},>>"
) )
# CMake versions earlier than 3.18.0 can't find the generated file for some reason, # CMake versions earlier than 3.18.0 can't find the generated file for some reason,
# failing at generation phase. # failing at generation phase.
# Explicitly marking the file as GENERATED fixes the issue. # Explicitly marking the file as GENERATED fixes the issue.
set_source_files_properties("${out_file_path}" PROPERTIES GENERATED TRUE) set_source_files_properties("${out_file_path}" PROPERTIES GENERATED TRUE)
target_sources(${name} PRIVATE
"$<$<NOT:$<STREQUAL:${class_names},>>:${out_file_path}>"
)
target_link_libraries(${name} PRIVATE
"$<TARGET_PROPERTY:${lib},_qt_initial_repo_plugins>"
"$<TARGET_PROPERTY:${lib},${prop_prefix}_plugins>")
endforeach()
target_sources(${name} PRIVATE
"$<$<NOT:$<STREQUAL:${class_names},>>:${out_file_path}>"
)
target_link_libraries(${name} PRIVATE
"$<TARGET_PROPERTY:${lib},_qt_initial_repo_plugins>"
"$<TARGET_PROPERTY:${lib},${prop_prefix}_plugins>")
endforeach()
endif()
endfunction() endfunction()

View File

@ -203,70 +203,74 @@ function(qt_internal_add_plugin target)
endif() endif()
get_target_property(is_imported_qt_module ${qt_module_target} IMPORTED) get_target_property(is_imported_qt_module ${qt_module_target} IMPORTED)
# Associate plugin with its Qt module when both are both built in the same repository. set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${target}")
# Check that by comparing the PROJECT_NAME of each. get_target_property(type "${plugin_target_versioned}" TYPE)
# This covers auto-linking of the majority of plugins to executables and in-tree tests. if(type STREQUAL STATIC_LIBRARY)
# Linking of plugins in standalone tests (when the Qt module will be an imported target) # Associate plugin with its Qt module when both are both built in the same repository.
# is handled instead by the complicated genex logic in QtModulePlugins.cmake.in. # Check that by comparing the PROJECT_NAME of each.
set(is_plugin_and_module_in_same_project FALSE) # This covers auto-linking of the majority of plugins to executables and in-tree tests.
if(NOT is_imported_qt_module) # Linking of plugins in standalone tests (when the Qt module will be an imported target)
# This QT_PLUGINS assignment is only used by QtPostProcessHelpers to decide if a # is handled instead by the complicated genex logic in QtModulePlugins.cmake.in.
# QtModulePlugins.cmake file should be generated (which only happens in static builds). set(is_plugin_and_module_in_same_project FALSE)
set_property(TARGET "${qt_module_target}" APPEND PROPERTY QT_PLUGINS "${target}") if(NOT is_imported_qt_module)
# This QT_PLUGINS assignment is only used by QtPostProcessHelpers to decide if a
# QtModulePlugins.cmake file should be generated (which only happens in static builds).
set_property(TARGET "${qt_module_target}" APPEND PROPERTY QT_PLUGINS "${target}")
get_target_property(module_source_dir ${qt_module_target} SOURCE_DIR) get_target_property(module_source_dir ${qt_module_target} SOURCE_DIR)
get_directory_property(module_project_name get_directory_property(module_project_name
DIRECTORY ${module_source_dir} DIRECTORY ${module_source_dir}
DEFINITION PROJECT_NAME DEFINITION PROJECT_NAME
) )
if(module_project_name STREQUAL PROJECT_NAME) if(module_project_name STREQUAL PROJECT_NAME)
set(is_plugin_and_module_in_same_project TRUE) set(is_plugin_and_module_in_same_project TRUE)
endif()
# When linking static plugins with the special logic in qt_internal_add_executable,
# make sure to skip non-default plugins.
if(is_plugin_and_module_in_same_project AND _default_plugin)
set_property(TARGET ${qt_module_target} APPEND PROPERTY
_qt_initial_repo_plugins
"${target}")
set_property(TARGET ${qt_module_target} APPEND PROPERTY
_qt_initial_repo_plugin_class_names
"$<TARGET_PROPERTY:${target},QT_PLUGIN_CLASS_NAME>"
)
endif()
endif() endif()
# When linking static plugins with the special logic in qt_internal_add_executable, # Associate plugin with its Qt module when the plugin is built in the current repository
# make sure to skip non-default plugins. # but the module is built in a different repository (qtsvg's QSvgPlugin associated with
if(is_plugin_and_module_in_same_project AND _default_plugin) # qtbase's QtGui).
# The association is done in a separate property, to ensure that reconfiguring in-tree tests
# in qtbase doesn't accidentally cause linking to a plugin from a previously built qtsvg.
# Needed for in-tree tests like in qtsvg, qtimageformats.
# This is done for each Qt module regardless if it's an imported target or not, to handle
# both per-repo and top-level builds (in per-repo build of qtsvg QtGui is imported, in a
# top-level build Gui is not imported, but in both cases qtsvg tests need to link to
# QSvgPlugin).
#
# TODO: Top-level in-tree tests and qdeclarative per-repo in-tree tests that depend on
# static Qml plugins won't work due to the requirement of running qmlimportscanner
# at configure time, but qmlimportscanner is not built at that point. Moving the
# execution of qmlimportscanner to build time is non-trivial because qmlimportscanner
# not only generates a cpp file to compile but also outputs a list of static plugins
# that should be linked and there is no straightforward way to tell CMake to link
# against a list of libraries that was discovered at build time (apart from
# response files, which apparently might not work on all platforms).
# qmake doesn't have this problem because each project is configured separately so
# qmlimportscanner is always built by the time it needs to be run for a test.
if(NOT is_plugin_and_module_in_same_project AND _default_plugin)
string(MAKE_C_IDENTIFIER "${PROJECT_NAME}" current_project_name)
set(prop_prefix "_qt_repo_${current_project_name}")
set_property(TARGET ${qt_module_target} APPEND PROPERTY set_property(TARGET ${qt_module_target} APPEND PROPERTY
_qt_initial_repo_plugins ${prop_prefix}_plugins "${target}")
"${target}")
set_property(TARGET ${qt_module_target} APPEND PROPERTY set_property(TARGET ${qt_module_target} APPEND PROPERTY
_qt_initial_repo_plugin_class_names ${prop_prefix}_plugin_class_names
"$<TARGET_PROPERTY:${target},QT_PLUGIN_CLASS_NAME>" "$<TARGET_PROPERTY:${target},QT_PLUGIN_CLASS_NAME>"
) )
endif() endif()
endif() endif()
# Associate plugin with its Qt module when the plugin is built in the current repository
# but the module is built in a different repository (qtsvg's QSvgPlugin associated with
# qtbase's QtGui).
# The association is done in a separate property, to ensure that reconfiguring in-tree tests
# in qtbase doesn't accidentally cause linking to a plugin from a previously built qtsvg.
# Needed for in-tree tests like in qtsvg, qtimageformats.
# This is done for each Qt module regardless if it's an imported target or not, to handle
# both per-repo and top-level builds (in per-repo build of qtsvg QtGui is imported, in a
# top-level build Gui is not imported, but in both cases qtsvg tests need to link to
# QSvgPlugin).
#
# TODO: Top-level in-tree tests and qdeclarative per-repo in-tree tests that depend on
# static Qml plugins won't work due to the requirement of running qmlimportscanner
# at configure time, but qmlimportscanner is not built at that point. Moving the
# execution of qmlimportscanner to build time is non-trivial because qmlimportscanner
# not only generates a cpp file to compile but also outputs a list of static plugins
# that should be linked and there is no straightforward way to tell CMake to link
# against a list of libraries that was discovered at build time (apart from
# response files, which apparently might not work on all platforms).
# qmake doesn't have this problem because each project is configured separately so
# qmlimportscanner is always built by the time it needs to be run for a test.
if(NOT is_plugin_and_module_in_same_project AND _default_plugin)
string(MAKE_C_IDENTIFIER "${PROJECT_NAME}" current_project_name)
set(prop_prefix "_qt_repo_${current_project_name}")
set_property(TARGET ${qt_module_target} APPEND PROPERTY
${prop_prefix}_plugins "${target}")
set_property(TARGET ${qt_module_target} APPEND PROPERTY
${prop_prefix}_plugin_class_names
"$<TARGET_PROPERTY:${target},QT_PLUGIN_CLASS_NAME>"
)
endif()
endif() endif()
# Change the configuration file install location for qml plugins into the Qml package location. # Change the configuration file install location for qml plugins into the Qml package location.

View File

@ -263,12 +263,16 @@ function(__qt_internal_collect_plugin_libraries plugin_targets out_var)
set(plugins_to_link "") set(plugins_to_link "")
foreach(plugin_target ${plugin_targets}) foreach(plugin_target ${plugin_targets})
set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
get_target_property(type "${plugin_target_versioned}" TYPE)
if(NOT type STREQUAL STATIC_LIBRARY)
continue()
endif()
__qt_internal_get_static_plugin_condition_genex( __qt_internal_get_static_plugin_condition_genex(
"${plugin_target}" "${plugin_target}"
plugin_condition) plugin_condition)
set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
list(APPEND plugins_to_link "$<${plugin_condition}:${plugin_target_versioned}>") list(APPEND plugins_to_link "$<${plugin_condition}:${plugin_target_versioned}>")
endforeach() endforeach()
@ -282,6 +286,12 @@ function(__qt_internal_collect_plugin_init_libraries plugin_targets out_var)
set(plugin_inits_to_link "") set(plugin_inits_to_link "")
foreach(plugin_target ${plugin_targets}) foreach(plugin_target ${plugin_targets})
set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
get_target_property(type "${plugin_target_versioned}" TYPE)
if(NOT type STREQUAL STATIC_LIBRARY)
continue()
endif()
__qt_internal_get_static_plugin_condition_genex( __qt_internal_get_static_plugin_condition_genex(
"${plugin_target}" "${plugin_target}"
plugin_condition) plugin_condition)
@ -376,11 +386,6 @@ endfunction()
# Main logic of finalizer mode. # Main logic of finalizer mode.
function(__qt_internal_apply_plugin_imports_finalizer_mode target) 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. # Process a target only once.
get_target_property(processed ${target} _qt_plugin_finalizer_imports_processed) get_target_property(processed ${target} _qt_plugin_finalizer_imports_processed)
if(processed) if(processed)
@ -461,8 +466,10 @@ function(__qt_internal_include_plugin_packages target)
list(APPEND "QT_ALL_PLUGINS_FOUND_BY_FIND_PACKAGE_${__plugin_type}" "${plugin_target}") list(APPEND "QT_ALL_PLUGINS_FOUND_BY_FIND_PACKAGE_${__plugin_type}" "${plugin_target}")
# Auto-linkage should be set up only for static library builds. # Auto-linkage should be set up only for static plugins.
if(NOT QT6_IS_SHARED_LIBS_BUILD) set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}")
get_target_property(type "${plugin_target_versioned}" TYPE)
if(type STREQUAL STATIC_LIBRARY)
__qt_internal_add_static_plugin_linkage("${plugin_target}" "${_module_target}") __qt_internal_add_static_plugin_linkage("${plugin_target}" "${_module_target}")
__qt_internal_add_static_plugin_import_macro( __qt_internal_add_static_plugin_import_macro(
"${plugin_target}" ${_module_target} "${target}") "${plugin_target}" ${_module_target} "${target}")