From 91c65dd80cdd2de666448c14202c0c63718152b6 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 11 May 2021 11:19:19 +0200 Subject: [PATCH] CMake: Build plugin initializers as object libs Instead of compiling the plugin initializers as part of a user project, pre-compile them as object libraries while building Qt. The installed object libraries can then be used with target_sources(qt_module INTERFACE $) so that they are linked into the final executable or shared library via qt module usage requirement propagation. This reduces the build times of user projects. The link line placement of the object files should be correct for all linux-y linkers because the only dependency for the object files is Core and the Gui -> plugin -> Gui -> Core cycle does not hamper that from empirical observations. As a consequence of the recent change not to link plugin initialization object files into static libraries, as well not having to compile the files in user projects, we can get rid of the _qt_internal_disable_static_default_plugins calls in various places. A side note. Consider a user static library (L) that links to a Qt static library (Gui) which provides plugins (platform plugins). If there is an executable (E) that links to (L), with no direct dependency to any other Qt module and the intention is that the executable will automatically get the platform plugin linked, then (L) needs to link PUBLIC-ly to (Gui) so that the plugin usage requirements are propagated successfully. This is a limitation of using target_sources(qt_module INTERFACE $) which will propagate object files across static libraries only if qt_module is linked publicly. One could try to use target_link_libraries(qt_module INTERFACE $) which preserves the linker arguments across static libs even if qt_module is linked privately, but unfortunately CMake will lose dependency information on Core, which means the object files might be placed in the wrong place on the link line. As far as I know this is a limitation of CMake that can't be worked around at the moment. Note this behavior was present before this change as well. Task-number: QTBUG-80863 Task-number: QTBUG-92933 Change-Id: Ia99e8aa3d32d6197cacd6162515ac808f2c6c53f Reviewed-by: Qt CI Bot Reviewed-by: Joerg Bornemann --- cmake/Qt3rdPartyLibraryHelpers.cmake | 10 --- cmake/QtModuleHelpers.cmake | 5 -- cmake/QtPluginHelpers.cmake | 14 ++-- cmake/QtPublicPluginHelpers.cmake | 98 ++++++++++++++++++++-------- src/corelib/Qt6CoreMacros.cmake | 3 - 5 files changed, 80 insertions(+), 50 deletions(-) diff --git a/cmake/Qt3rdPartyLibraryHelpers.cmake b/cmake/Qt3rdPartyLibraryHelpers.cmake index 27d615b2b4..7cfa943567 100644 --- a/cmake/Qt3rdPartyLibraryHelpers.cmake +++ b/cmake/Qt3rdPartyLibraryHelpers.cmake @@ -49,11 +49,6 @@ function(qt_internal_add_cmake_library target) endif() qt_skip_warnings_are_errors_when_repo_unclean("${target}") - # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables. - if(is_static_lib) - _qt_internal_disable_static_default_plugins("${target}") - endif() - if (arg_INSTALL_DIRECTORY) set(install_arguments ARCHIVE_INSTALL_DIRECTORY ${arg_ARCHIVE_INSTALL_DIRECTORY} @@ -145,11 +140,6 @@ function(qt_internal_add_3rdparty_library target) qt_internal_add_target_aliases(${target}) _qt_internal_apply_strict_cpp(${target}) - # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables. - if(is_static_lib) - _qt_internal_disable_static_default_plugins("${target}") - endif() - if (ANDROID) qt_android_apply_arch_suffix("${target}") endif() diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index 4cb212c169..4d976173df 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -144,11 +144,6 @@ function(qt_internal_add_module target) qt_skip_warnings_are_errors_when_repo_unclean("${target}") _qt_internal_apply_strict_cpp("${target}") - # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables. - if(is_static_lib) - _qt_internal_disable_static_default_plugins("${target}") - endif() - # Add _private target to link against the private headers: if(NOT ${arg_NO_PRIVATE_MODULE}) set(target_private "${target}Private") diff --git a/cmake/QtPluginHelpers.cmake b/cmake/QtPluginHelpers.cmake index 2368531d99..c4e0df4415 100644 --- a/cmake/QtPluginHelpers.cmake +++ b/cmake/QtPluginHelpers.cmake @@ -119,10 +119,6 @@ function(qt_internal_add_plugin target) qt_skip_warnings_are_errors_when_repo_unclean("${target}") _qt_internal_apply_strict_cpp("${target}") - # Disable linking of plugins against other plugins during static regular and - # super builds. The latter causes cyclic dependencies otherwise. - _qt_internal_disable_static_default_plugins("${target}") - set_target_properties("${target}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${output_directory}" RUNTIME_OUTPUT_DIRECTORY "${output_directory}" @@ -322,8 +318,13 @@ function(qt_internal_add_plugin target) endforeach() qt_register_target_dependencies("${target}" "${arg_PUBLIC_LIBRARIES}" "${qt_libs_private}") + set(plugin_init_target "") if (NOT BUILD_SHARED_LIBS) qt_generate_plugin_pri_file("${target}" pri_file) + + if(qt_module_target) + __qt_internal_add_static_plugin_init_object_library("${target}" plugin_init_target) + endif() endif() if (NOT arg_SKIP_INSTALL) @@ -372,10 +373,13 @@ function(qt_internal_add_plugin target) # Make the export name of plugins be consistent with modules, so that # qt_add_resource adds its additional targets to the same export set in a static Qt build. set(export_name "${INSTALL_CMAKE_NAMESPACE}${target}Targets") - qt_install(TARGETS "${target}" + qt_install(TARGETS + "${target}" + ${plugin_init_target} EXPORT ${export_name} RUNTIME DESTINATION "${install_directory}" LIBRARY DESTINATION "${install_directory}" + OBJECTS DESTINATION "${install_directory}" ARCHIVE DESTINATION "${archive_install_directory}" ) qt_install(EXPORT ${export_name} diff --git a/cmake/QtPublicPluginHelpers.cmake b/cmake/QtPublicPluginHelpers.cmake index 39143cecc3..34790fe6a3 100644 --- a/cmake/QtPublicPluginHelpers.cmake +++ b/cmake/QtPublicPluginHelpers.cmake @@ -1,3 +1,5 @@ +# Gets the qt plugin type of the given plugin into out_var_plugin_type. +# Also sets out_var_has_plugin_type to TRUE or FALSE depending on whether the plugin type was found. function(__qt_internal_plugin_get_plugin_type plugin_target out_var_has_plugin_type @@ -17,8 +19,8 @@ function(__qt_internal_plugin_get_plugin_type set(${out_var_has_plugin_type} "${has_plugin_type}" PARENT_SCOPE) endfunction() +# Gets the qt plugin class name of the given target into out_var. function(__qt_internal_plugin_has_class_name plugin_target out_var) - set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}") get_target_property(classname "${plugin_target_versioned}" QT_PLUGIN_CLASS_NAME) @@ -118,6 +120,7 @@ function(__qt_internal_get_static_plugin_condition_genex set(${out_var} "${_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) @@ -128,36 +131,77 @@ function(__qt_internal_add_static_plugin_linkage plugin_target qt_module_target) target_link_libraries(${qt_module_target} INTERFACE "${plugin_genex}") endfunction() +# Generates C++ import macro source code for given plugin +function(__qt_internal_get_plugin_import_macro plugin_target out_var) + set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}") + get_target_property(class_name "${plugin_target_versioned}" QT_PLUGIN_CLASS_NAME) + set(${out_var} "Q_IMPORT_PLUGIN(${class_name})" PARENT_SCOPE) +endfunction() + +function(__qt_internal_get_plugin_include_prelude out_var) + set(${out_var} "#include \n" PARENT_SCOPE) +endfunction() + +# Set up plugin initialization via usage requirements of associated Qt module. +# +# Adds the plugin init object library as an INTERFACE source of the plugin target. +# This is similar to how it was done before, except instead of generating a C++ file and compiling +# it as part of the user project, we just specify the pre-compiled object file as an INTERFACE +# source so that user projects don't have to compile it. User project builds will thus be shorter. function(__qt_internal_add_static_plugin_import_macro plugin_target qt_module_target qt_module_unprefixed) - set(_generated_qt_plugin_file_name - "${CMAKE_CURRENT_BINARY_DIR}/.qt_plugins/qt_${qt_module_unprefixed}_${plugin_target}.cpp") - set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}") - get_target_property(class_name "${plugin_target_versioned}" QT_PLUGIN_CLASS_NAME) + __qt_internal_get_static_plugin_init_target_name("${plugin_target}" plugin_init_target) + set(objs_genex "$") - set(_generated_qt_plugin_file_content "#include \nQ_IMPORT_PLUGIN(${class_name})") - - # Generate a source file to import that plug-in. Be careful not to - # update the timestamp of the generated file if we are not going to - # change anything. Otherwise we will trigger CMake's autogen to re-run - # and executables will then need to at least relink. - set(need_write TRUE) - if(EXISTS ${_generated_qt_plugin_file_name}) - file(READ ${_generated_qt_plugin_file_name} old_contents) - if(old_contents STREQUAL "${_generated_qt_plugin_file_content}") - set(need_write FALSE) - endif() - endif() - if(need_write) - file(WRITE "${_generated_qt_plugin_file_name}" - "${_generated_qt_plugin_file_content}") - endif() - - __qt_internal_get_static_plugin_condition_genex("${plugin_target}" plugin_condition) - - target_sources(${qt_module_target} INTERFACE - "$<${plugin_condition}:${_generated_qt_plugin_file_name}>") + target_sources(${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target} INTERFACE + "${objs_genex}") endfunction() + +# Get target name of object library which is used to initialize a qt plugin. +function(__qt_internal_get_static_plugin_init_target_name plugin_target out_var) + # Keep the target name short, so we don't hit long path issues on Windows. + set(plugin_init_target "${plugin_target}_init") + + set(${out_var} "${plugin_init_target}" PARENT_SCOPE) +endfunction() + +# Create an object library that initializes a static qt plugin. +# +# The object library contains a single generated C++ file that calls Q_IMPORT_PLUGIN(plugin_class). +# The object library is exported as part of the Qt build and consumed by user applications +# that link to qt plugins. +# +# The created target name is assigned to 'out_var_plugin_init_target'. +function(__qt_internal_add_static_plugin_init_object_library + plugin_target + out_var_plugin_init_target) + + __qt_internal_get_plugin_import_macro(${plugin_target} import_macro) + __qt_internal_get_plugin_include_prelude(include_prelude) + set(import_content "${include_prelude}${import_macro}") + + string(MAKE_C_IDENTIFIER "${plugin_target}" plugin_target_identifier) + set(generated_qt_plugin_file_name + "${CMAKE_CURRENT_BINARY_DIR}/${plugin_target_identifier}_init.cpp") + + file(GENERATE + OUTPUT "${generated_qt_plugin_file_name}" + CONTENT "${import_content}" + ) + + __qt_internal_get_static_plugin_init_target_name("${plugin_target}" plugin_init_target) + + add_library("${plugin_init_target}" OBJECT "${generated_qt_plugin_file_name}") + target_link_libraries(${plugin_init_target} + PRIVATE + + # Core provides the symbols needed by Q_IMPORT_PLUGIN. + ${QT_CMAKE_EXPORT_NAMESPACE}::Core + ) + + set(${out_var_plugin_init_target} "${plugin_init_target}" PARENT_SCOPE) +endfunction() + diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 6e825c2a63..01426cd8ea 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -1373,9 +1373,6 @@ function(__qt_propagate_generated_resource target resource_name generated_source target_link_libraries(${target} INTERFACE ${resource_target}) endif() set(${output_generated_target} "${resource_target}" PARENT_SCOPE) - - # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables. - _qt_internal_disable_static_default_plugins("${resource_target}") else() set(${output_generated_target} "" PARENT_SCOPE) target_sources(${target} PRIVATE ${generated_source_code})