diff --git a/cmake/QtPublicPluginHelpers.cmake b/cmake/QtPublicPluginHelpers.cmake index 44ceb92e246..749e40e3f2a 100644 --- a/cmake/QtPublicPluginHelpers.cmake +++ b/cmake/QtPublicPluginHelpers.cmake @@ -413,46 +413,6 @@ function(__qt_internal_collect_plugin_targets_from_dependencies_of_plugins targe set("${out_var}" "${plugin_targets}" PARENT_SCOPE) endfunction() -# Generate plugin information files for deployment -# -# Arguments: -# OUT_PLUGIN_TARGETS - Variable name to store the plugin targets that were collected with -# __qt_internal_collect_plugin_targets_from_dependencies. -function(__qt_internal_generate_plugin_deployment_info target) - set(no_value_options "") - set(single_value_options "OUT_PLUGIN_TARGETS") - set(multi_value_options "") - cmake_parse_arguments(PARSE_ARGV 0 arg - "${no_value_options}" "${single_value_options}" "${multi_value_options}" - ) - - __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets) - if(NOT "${arg_OUT_PLUGIN_TARGETS}" STREQUAL "") - set("${arg_OUT_PLUGIN_TARGETS}" "${plugin_targets}" PARENT_SCOPE) - endif() - - get_target_property(marked_for_deployment ${target} _qt_marked_for_deployment) - if(NOT marked_for_deployment) - return() - endif() - - __qt_internal_collect_plugin_library_files(${target} "${plugin_targets}" plugins_files) - set(plugins_files "$") - - _qt_internal_get_deploy_impl_dir(deploy_impl_dir) - set(file_path "${deploy_impl_dir}/${target}-plugins") - get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) - if(is_multi_config) - string(APPEND file_path "-$") - endif() - string(APPEND file_path ".cmake") - - file(GENERATE - OUTPUT ${file_path} - CONTENT "set(__QT_DEPLOY_PLUGINS ${plugins_files})" - ) -endfunction() - # Main logic of finalizer mode. function(__qt_internal_apply_plugin_imports_finalizer_mode target) # Process a target only once. @@ -461,9 +421,6 @@ function(__qt_internal_apply_plugin_imports_finalizer_mode target) return() endif() - __qt_internal_generate_plugin_deployment_info(${target} - OUT_PLUGIN_TARGETS plugin_targets) - # By default if the project hasn't explicitly opted in or out, use finalizer mode. # The precondition for this is that qt_finalize_target was called (either explicitly by the user # or auto-deferred by CMake 3.19+). @@ -477,6 +434,7 @@ function(__qt_internal_apply_plugin_imports_finalizer_mode target) 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) diff --git a/src/corelib/Qt6CoreConfigExtras.cmake.in b/src/corelib/Qt6CoreConfigExtras.cmake.in index 1f1d0998a83..ce53b89a7b8 100644 --- a/src/corelib/Qt6CoreConfigExtras.cmake.in +++ b/src/corelib/Qt6CoreConfigExtras.cmake.in @@ -24,11 +24,10 @@ set(QT@PROJECT_VERSION_MAJOR@_IS_SHARED_LIBS_BUILD "@BUILD_SHARED_LIBS@") set(QT@PROJECT_VERSION_MAJOR@_DEBUG_POSTFIX "@CMAKE_DEBUG_POSTFIX@") set(_Qt6CTestMacros "${CMAKE_CURRENT_LIST_DIR}/Qt6CTestMacros.cmake") +@qtcore_extra_cmake_code@ _qt_internal_setup_deploy_support() -@qtcore_extra_cmake_code@ - if(ANDROID_PLATFORM) include("${CMAKE_CURRENT_LIST_DIR}/@QT_CMAKE_EXPORT_NAMESPACE@AndroidMacros.cmake") _qt_internal_create_global_android_targets() diff --git a/src/corelib/Qt6CoreDeploySupport.cmake b/src/corelib/Qt6CoreDeploySupport.cmake index 901056bed35..632d7a0f6ba 100644 --- a/src/corelib/Qt6CoreDeploySupport.cmake +++ b/src/corelib/Qt6CoreDeploySupport.cmake @@ -181,6 +181,239 @@ function(_qt_internal_run_deployment_hooks) endforeach() endfunction() +# Return a list of Qt modules from a list of file paths. +# Arguments: +# LIBRARIES - list of file paths, usually shared objects the to be deployed product links +# against. +# +# Example input: /foo/bar/libQt6Core.so.6;/foo/bar/libQt6Gui.so.6;/foo/bar/somethingelse.so +# Output: Core;Gui +function(_qt_internal_get_qt_modules_from_resolved_libs out_var) + set(no_value_options "") + set(single_value_options "") + set(multi_value_options LIBRARIES) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + set(result "") + foreach(lib IN LISTS arg_LIBRARIES) + if(lib MATCHES "${__QT_CMAKE_EXPORT_NAMESPACE}([^/.]+)${__QT_LIBINFIX}\\.[^/]+$") + list(APPEND result "${CMAKE_MATCH_1}") + endif() + endforeach() + + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_json_array_to_cmake_list out_var json_array) + set(result "") + string(JSON json_array_length ERROR_VARIABLE error LENGTH "${json_array}") + if(error STREQUAL "NOTFOUND") + math(EXPR json_array_max_idx "${json_array_length} - 1") + foreach(i RANGE "${json_array_max_idx}") + string(JSON type ERROR_VARIABLE error GET "${json_array}" "${i}") + if(error STREQUAL "NOTFOUND") + list(APPEND result "${type}") + endif() + endforeach() + endif() + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +# Return the plugin types for a Qt module. +# Arguments: +# MODULE +# The Qt module we'd like to know the plugin types for. +# QPA_PLATFORMS_OUT_VAR +# Output variable for the list of supported QPA platforms to be deployed. +# Only filled for the Gui module. +# +# Example input: Gui +# Output: platforms;iconengines;imageformats;... +# +# This function reads the module JSON files from the Qt installation prefix. +function(_qt_internal_get_plugin_types_for_module out_var) + set(no_value_options "") + set(single_value_options + MODULE + QPA_PLATFORMS_OUT_VAR + ) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + if(NOT DEFINED arg_MODULE) + message(FATAL_ERROR "MODULE argument is required.") + endif() + + # Clear output variables to enable usage of early exit. + set("${out_var}" "" PARENT_SCOPE) + if(DEFINED arg_QPA_PLATFORMS_OUT_VAR) + set("${arg_QPA_PLATFORMS_OUT_VAR}" "" PARENT_SCOPE) + endif() + + set(modules_dir + "${__QT_DEPLOY_QT_INSTALL_PREFIX}/${__QT_DEPLOY_QT_INSTALL_DESCRIPTIONSDIR}" + ) + set(modules_file "${modules_dir}/${arg_MODULE}.json") + if(NOT EXISTS "${modules_file}") + return() + endif() + + file(READ "${modules_file}" content) + string(JSON types_array ERROR_VARIABLE error GET "${content}" "plugin_types") + if(NOT error STREQUAL "NOTFOUND") + return() + endif() + _qt_internal_json_array_to_cmake_list(result "${types_array}") + set("${out_var}" "${result}" PARENT_SCOPE) + + # Read .qpa.platforms + if(DEFINED arg_QPA_PLATFORMS_OUT_VAR) + string(JSON qpa_object ERROR_VARIABLE error GET "${content}" "qpa") + if(NOT error STREQUAL "NOTFOUND") + return() + endif() + string(JSON platforms_array ERROR_VARIABLE error GET "${qpa_object}" "platforms") + if(NOT error STREQUAL "NOTFOUND") + return() + endif() + _qt_internal_json_array_to_cmake_list(result "${platforms_array}") + set("${arg_QPA_PLATFORMS_OUT_VAR}" "${result}" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_plugin_file_path_match out_var file_path plugin_short_names) + set(result FALSE) + get_filename_component(file_name "${file_path}" NAME_WE) + foreach(plugin_name IN LISTS plugin_short_names) + get_filename_component(plugin_name "${plugin_name}" NAME_WE) + if(file_name MATCHES "${plugin_name}${__QT_LIBINFIX}$") + set(result TRUE) + break() + endif() + endforeach() + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +# Return the Qt plugins to deploy. +# +# This function iterates over the Qt modules in LIBRARIES and retrieves their plugin types. +# Those plugin types are added to INCLUDE_BY_TYPE. +# The caller may specify further plugin types to include in INCLUDE_BY_TYPE. +# +# Further arguments: +# NO_DEFAULT +# Don't deploy the default plugins. +# EXCLUDE_BY_TYPE +# Plugin types that should not be deployed. +# EXCLUDE +# Plugins that should not be deployed. These are the plugin file name infixes (e.g. qxcb). +# INCLUDE +# Plugins that should be deployed. +function(_qt_internal_get_qt_plugins_to_deploy out_var) + set(no_value_options + NO_DEFAULT + ) + set(single_value_options "") + set(multi_value_options + EXCLUDE + EXCLUDE_BY_TYPE + INCLUDE + INCLUDE_BY_TYPE + LIBRARIES + ) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + # Locate the Qt modules with the resolved libraries and find their plugin types. + _qt_internal_get_qt_modules_from_resolved_libs(modules LIBRARIES ${arg_LIBRARIES}) + set(plugin_types_from_modules "") + set(qpa_platforms "") + foreach(module IN LISTS modules) + set(additional_args "") + if(NOT arg_NO_DEFAULT AND module STREQUAL "Gui") + set(additional_args QPA_PLATFORMS_OUT_VAR qpa_platforms) + endif() + _qt_internal_get_plugin_types_for_module(module_plugin_types + MODULE ${module} + ${additional_args} + ) + if(module_plugin_types) + list(APPEND plugin_types_from_modules "${module_plugin_types}") + endif() + endforeach() + + set(selected_plugin_types ${arg_INCLUDE_BY_TYPE} ${plugin_types_from_modules}) + list(REMOVE_DUPLICATES selected_plugin_types) + list(REMOVE_ITEM selected_plugin_types ${arg_EXCLUDE_BY_TYPE} platforms) + + # Collect all available plugins and plugin types from Qt's installation root. + set(plugins_root_dir "${__QT_DEPLOY_QT_INSTALL_PREFIX}/${__QT_DEPLOY_QT_INSTALL_PLUGINS}") + file(GLOB_RECURSE all_files + LIST_DIRECTORIES TRUE + RELATIVE "${plugins_root_dir}" + "${plugins_root_dir}/*${__QT_DEPLOY_SHARED_LIBRARY_SUFFIX}" + ) + set(available_plugin_types "") + set(available_plugin_files "") + foreach(file_path IN LISTS all_files) + if(IS_DIRECTORY "${plugins_root_dir}/${file_path}") + list(APPEND available_plugin_types "${file_path}") + else() + list(APPEND available_plugin_files "${plugins_root_dir}/${file_path}") + endif() + endforeach() + + set(plugins "") + foreach(plugin_type IN LISTS available_plugin_types) + if(plugin_type IN_LIST selected_plugin_types) + # Add the whole plugin directory content. + set(plugins_dir "${plugins_root_dir}/${plugin_type}") + file(GLOB file_paths LIST_DIRECTORIES FALSE + "${plugins_dir}/*${__QT_DEPLOY_SHARED_LIBRARY_SUFFIX}" + ) + foreach(file_path IN LISTS file_paths) + _qt_internal_plugin_file_path_match(excluded "${file_path}" "${arg_EXCLUDED}") + if(NOT excluded) + list(APPEND plugins "${file_path}") + endif() + endforeach() + endif() + endforeach() + + set(included_plugin_names "${arg_INCLUDE}") + if(NOT qpa_platforms STREQUAL "") + set(qpa_plugin_names "${qpa_platforms}") + list(TRANSFORM qpa_plugin_names PREPEND "q") + list(APPEND included_plugin_names "${qpa_plugin_names}") + endif() + if(NOT included_plugin_names STREQUAL "") + if(DEFINED arg_EXCLUDED) + list(REMOVE_ITEM included_plugin_names ${arg_EXCLUDED}) + endif() + foreach(file_path IN LISTS available_plugin_files) + _qt_internal_plugin_file_path_match(included "${file_path}" "${included_plugin_names}") + if(included) + list(APPEND plugins "${file_path}") + endif() + endforeach() + endif() + + set("${out_var}" "${plugins}" PARENT_SCOPE) +endfunction() + function(_qt_internal_generic_deployqt) set(no_value_options NO_PLUGINS @@ -202,7 +435,13 @@ function(_qt_internal_generic_deployqt) POST_INCLUDE_FILES POST_EXCLUDE_FILES ) - set(multi_value_options ${file_GRD_options}) + set(multi_value_options + ${file_GRD_options} + EXCLUDE_PLUGINS + EXCLUDE_PLUGIN_TYPES + INCLUDE_PLUGINS + INCLUDE_PLUGIN_TYPES + ) cmake_parse_arguments(PARSE_ARGV 0 arg "${no_value_options}" "${single_value_options}" "${multi_value_options}" ) @@ -210,12 +449,6 @@ function(_qt_internal_generic_deployqt) message(FATAL_ERROR "Unparsed arguments: ${arg_UNPARSED_ARGUMENTS}") endif() - if(arg_NO_PLUGINS) - set(plugins "") - else() - set(plugins ${__QT_DEPLOY_PLUGINS}) - endif() - if(arg_VERBOSE OR __QT_DEPLOY_VERBOSE) set(verbose TRUE) endif() @@ -231,54 +464,106 @@ function(_qt_internal_generic_deployqt) set(${var} "${abspaths}") endforeach() - # We need to get the runtime dependencies of plugins too. - if(NOT plugins STREQUAL "") - list(APPEND arg_MODULES ${plugins}) - endif() - - # Forward the arguments that are exactly the same for file(GET_RUNTIME_DEPENDENCIES). - set(file_GRD_args "") - foreach(var IN LISTS file_GRD_options) - if(NOT "${arg_${var}}" STREQUAL "") - list(APPEND file_GRD_args ${var} ${arg_${var}}) - endif() - endforeach() - # Compile a list of regular expressions that represent ignored library directories. if("${arg_POST_EXCLUDE_REGEXES}" STREQUAL "") - set(regexes "") foreach(path IN LISTS QT_DEPLOY_IGNORED_LIB_DIRS) _qt_internal_re_escape(path_rex "${path}") - list(APPEND regexes "^${path_rex}") + list(APPEND arg_POST_EXCLUDE_REGEXES "^${path_rex}") endforeach() - if(regexes) - list(APPEND file_GRD_args POST_EXCLUDE_REGEXES ${regexes}) - endif() endif() - # Get the runtime dependencies recursively. - file(GET_RUNTIME_DEPENDENCIES - ${file_GRD_args} - RESOLVED_DEPENDENCIES_VAR resolved - UNRESOLVED_DEPENDENCIES_VAR unresolved - CONFLICTING_DEPENDENCIES_PREFIX conflicting - ) - if(verbose) - message("file(GET_RUNTIME_DEPENDENCIES ${file_args})") - foreach(file IN LISTS resolved) - message(" resolved: ${file}") - endforeach() - foreach(file IN LISTS unresolved) - message(" unresolved: ${file}") - endforeach() - foreach(file IN LISTS conflicting_FILENAMES) - message(" conflicting: ${file}") - message(" with ${conflicting_${file}}") - endforeach() + # Forward arguments for plugin selection. + set(plugin_selection_args "") + if(DEFINED arg_EXCLUDE_PLUGINS) + list(APPEND plugin_selection_args EXCLUDE ${arg_EXCLUDE_PLUGINS}) endif() + if(DEFINED arg_INCLUDE_PLUGINS) + list(APPEND plugin_selection_args INCLUDE ${arg_INCLUDE_PLUGINS}) + endif() + if(DEFINED arg_EXCLUDE_PLUGIN_TYPES) + list(APPEND plugin_selection_args EXCLUDE_BY_TYPE ${arg_EXCLUDE_PLUGIN_TYPES}) + endif() + if(DEFINED arg_INCLUDE_PLUGIN_TYPES) + list(APPEND plugin_selection_args INCLUDE_BY_TYPE ${arg_INCLUDE_PLUGIN_TYPES}) + endif() + + set(plugins "") + set(runtime_dependencies "") + foreach(pass RANGE 9) + # We need to get the runtime dependencies of plugins too. + if(NOT plugins STREQUAL "") + list(APPEND arg_MODULES ${plugins}) + endif() + + # Forward the arguments that are exactly the same for file(GET_RUNTIME_DEPENDENCIES). + set(file_GRD_args "") + foreach(var IN LISTS file_GRD_options) + if(NOT "${arg_${var}}" STREQUAL "") + list(APPEND file_GRD_args ${var} ${arg_${var}}) + endif() + endforeach() + + # Get the runtime dependencies recursively. + file(GET_RUNTIME_DEPENDENCIES + ${file_GRD_args} + RESOLVED_DEPENDENCIES_VAR resolved + UNRESOLVED_DEPENDENCIES_VAR unresolved + CONFLICTING_DEPENDENCIES_PREFIX conflicting + ) + list(APPEND runtime_dependencies "${resolved}") + if(verbose) + message("pass ${pass}\nfile(GET_RUNTIME_DEPENDENCIES ${file_GRD_args})") + foreach(file IN LISTS resolved) + message(" resolved: ${file}") + endforeach() + foreach(file IN LISTS unresolved) + message(" unresolved: ${file}") + endforeach() + foreach(file IN LISTS conflicting_FILENAMES) + message(" conflicting: ${file}") + message(" with ${conflicting_${file}}") + endforeach() + endif() + + # Discover more Qt plugins from resolved libraries. + if(arg_NO_PLUGINS) + set(more_plugins "") + if(verbose) + message(STATUS "Skipping plugin deployment.") + endif() + else() + _qt_internal_get_qt_plugins_to_deploy(more_plugins + LIBRARIES ${resolved} + ${plugin_selection_args} + ) + if(NOT more_plugins STREQUAL "") + list(REMOVE_ITEM more_plugins ${plugins}) + endif() + endif() + + if(more_plugins STREQUAL "") + # We're done, continue with deployment. + if(verbose) + message(STATUS "No further plugins discovered.") + endif() + break() + endif() + + # Add the newly discovered plugins. Re-run GRD. + if(verbose) + foreach(plugin IN LISTS more_plugins) + message(STATUS "Discovered plugin: ${plugin}") + endforeach() + endif() + list(APPEND plugins "${more_plugins}") + list(REMOVE_DUPLICATES plugins) + set(arg_LIBRARIES "") + set(arg_MODULES "${more_plugins}") + endforeach() # Deploy the Qt libraries. - file(INSTALL ${resolved} + list(REMOVE_DUPLICATES runtime_dependencies) + file(INSTALL ${runtime_dependencies} DESTINATION "${CMAKE_INSTALL_PREFIX}/${arg_LIB_DIR}" FOLLOW_SYMLINK_CHAIN ) @@ -321,7 +606,7 @@ function(_qt_internal_generic_deployqt) qt6_deploy_translations() endif() - _qt_internal_run_deployment_hooks(${ARGV} RESOLVED_DEPENDENCIES ${resolved}) + _qt_internal_run_deployment_hooks(${ARGV} RESOLVED_DEPENDENCIES ${runtime_dependencies}) endfunction() function(qt6_deploy_runtime_dependencies) @@ -546,6 +831,18 @@ function(qt6_deploy_runtime_dependencies) if(arg_NO_PLUGINS) list(APPEND tool_options NO_PLUGINS) endif() + if(DEFINED arg_INCLUDE_PLUGINS) + list(APPEND tool_options INCLUDE_PLUGINS ${arg_INCLUDE_PLUGINS}) + endif() + if(DEFINED arg_INCLUDE_PLUGIN_TYPES) + list(APPEND tool_options INCLUDE_PLUGIN_TYPES ${arg_INCLUDE_PLUGIN_TYPES}) + endif() + if(DEFINED arg_EXCLUDE_PLUGINS) + list(APPEND tool_options EXCLUDE_PLUGINS ${arg_EXCLUDE_PLUGINS}) + endif() + if(DEFINED arg_EXCLUDE_PLUGIN_TYPES) + list(APPEND tool_options EXCLUDE_PLUGIN_TYPES ${arg_EXCLUDE_PLUGIN_TYPES}) + endif() if(arg_NO_TRANSLATIONS) list(APPEND tool_options NO_TRANSLATIONS) endif() diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 3e13f0a778f..51dbbc8c6b2 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -3165,11 +3165,13 @@ endif() # These are internal implementation details. They may be removed at any time. set(__QT_DEPLOY_SYSTEM_NAME \"${CMAKE_SYSTEM_NAME}\") +set(__QT_DEPLOY_SHARED_LIBRARY_SUFFIX \"${CMAKE_SHARED_LIBRARY_SUFFIX}\") set(__QT_DEPLOY_IS_SHARED_LIBS_BUILD \"${QT6_IS_SHARED_LIBS_BUILD}\") set(__QT_DEPLOY_TOOL \"${__QT_DEPLOY_TOOL}\") set(__QT_DEPLOY_IMPL_DIR \"${deploy_impl_dir}\") set(__QT_DEPLOY_VERBOSE \"${QT_ENABLE_VERBOSE_DEPLOYMENT}\") set(__QT_CMAKE_EXPORT_NAMESPACE \"${QT_CMAKE_EXPORT_NAMESPACE}\") +set(__QT_LIBINFIX \"${QT_LIBINFIX}\") set(__QT_DEPLOY_GENERATOR_IS_MULTI_CONFIG \"${is_multi_config}\") set(__QT_DEPLOY_ACTIVE_CONFIG \"$\") set(__QT_NO_CREATE_VERSIONLESS_FUNCTIONS \"${QT_NO_CREATE_VERSIONLESS_FUNCTIONS}\") @@ -3178,11 +3180,11 @@ set(__QT_DEPLOY_QT_ADDITIONAL_PACKAGES_PREFIX_PATH \"${QT_ADDITIONAL_PACKAGES_PR set(__QT_DEPLOY_QT_INSTALL_PREFIX \"${QT6_INSTALL_PREFIX}\") set(__QT_DEPLOY_QT_INSTALL_BINS \"${QT6_INSTALL_BINS}\") set(__QT_DEPLOY_QT_INSTALL_DATA \"${QT6_INSTALL_DATA}\") +set(__QT_DEPLOY_QT_INSTALL_DESCRIPTIONSDIR \"${QT6_INSTALL_DESCRIPTIONSDIR}\") set(__QT_DEPLOY_QT_INSTALL_LIBEXECS \"${QT6_INSTALL_LIBEXECS}\") set(__QT_DEPLOY_QT_INSTALL_PLUGINS \"${QT6_INSTALL_PLUGINS}\") set(__QT_DEPLOY_QT_INSTALL_TRANSLATIONS \"${QT6_INSTALL_TRANSLATIONS}\") set(__QT_DEPLOY_TARGET_QT_PATHS_PATH \"${target_qtpaths_path}\") -set(__QT_DEPLOY_PLUGINS \"\") set(__QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH \"${must_adjust_plugins_rpath}\") set(__QT_DEPLOY_USE_PATCHELF \"${QT_DEPLOY_USE_PATCHELF}\") set(__QT_DEPLOY_PATCHELF_EXECUTABLE \"${QT_DEPLOY_PATCHELF_EXECUTABLE}\") @@ -3631,16 +3633,6 @@ function(qt6_generate_deploy_script) endif() endif() - # Mark the target as "to be deployed". - set_property(TARGET ${arg_TARGET} PROPERTY _qt_marked_for_deployment ON) - - # If the target already was finalized, maybe because it was defined in a subdirectory, generate - # the plugin deployment information here. - get_target_property(is_finalized "${arg_TARGET}" _qt_is_finalized) - if(is_finalized) - __qt_internal_generate_plugin_deployment_info(${arg_TARGET}) - endif() - # Create a file name that will be unique for this target and the combination # of arguments passed to this command. This allows the project to call us # multiple times with different arguments for the same target (e.g. to diff --git a/src/corelib/doc/src/cmake/qt_deploy_runtime_dependencies.qdoc b/src/corelib/doc/src/cmake/qt_deploy_runtime_dependencies.qdoc index 8d5c5700142..384eb268983 100644 --- a/src/corelib/doc/src/cmake/qt_deploy_runtime_dependencies.qdoc +++ b/src/corelib/doc/src/cmake/qt_deploy_runtime_dependencies.qdoc @@ -208,7 +208,7 @@ example \c qjpeg. Qt6::QJpegPlugin target's plugin name is \c qjpeg. \note The arguments \c EXCLUDE_PLUGINS, \c EXCLUDE_PLUGIN_TYPES, \c -INCLUDE_PLUGINS, and \c INCLUDE_PLUGIN_TYPES only work on Windows. +INCLUDE_PLUGINS, and \c INCLUDE_PLUGIN_TYPES only work on Windows and Linux. \section1 Example diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index d3059269fca..7973d812e3f 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -404,15 +404,22 @@ endif() _qt_internal_test_expect_pass(test_qt_extract_metatypes) -set(deploy_args +set(deployment_tests + test_plugin_deployment test_widgets_app_deployment - BINARY "${CMAKE_CTEST_COMMAND}" - BINARY_ARGS "-V" - # Need to explicitly specify a writable install prefix. - BUILD_OPTIONS - -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/test_widgets_app_deployment_installed - NO_RUN_ENVIRONMENT_PLUGIN_PATH ) +foreach(test_name IN LISTS deployment_tests) + set(${test_name}_args + ${test_name} + BINARY "${CMAKE_CTEST_COMMAND}" + BINARY_ARGS "-V" + # Need to explicitly specify a writable install prefix. + BUILD_OPTIONS + -DQT_ENABLE_VERBOSE_DEPLOYMENT=ON + -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/${test_name}_installed + NO_RUN_ENVIRONMENT_PLUGIN_PATH + ) +endforeach() set(is_desktop_linux FALSE) if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING) @@ -423,11 +430,13 @@ endif() # and fail on other platforms, because there is no support for runtime dependency deployment on # those platforms. # With static builds the runtime dependencies are just skipped, but the test should still pass. -if(WIN32 OR (APPLE AND NOT IOS) OR is_desktop_linux) - _qt_internal_test_expect_pass(${deploy_args}) -else() - _qt_internal_test_expect_fail(${deploy_args}) -endif() +foreach(test_name IN LISTS deployment_tests) + if(WIN32 OR (APPLE AND NOT IOS) OR is_desktop_linux) + _qt_internal_test_expect_pass(${${test_name}_args}) + else() + _qt_internal_test_expect_fail(${${test_name}_args}) + endif() +endforeach() _qt_internal_test_expect_pass(test_config_expressions) _qt_internal_test_expect_pass(test_QTP0003) diff --git a/tests/auto/cmake/test_plugin_deployment/CMakeLists.txt b/tests/auto/cmake/test_plugin_deployment/CMakeLists.txt new file mode 100644 index 00000000000..0efbb5519cc --- /dev/null +++ b/tests/auto/cmake/test_plugin_deployment/CMakeLists.txt @@ -0,0 +1,100 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(test_plugin_deployment) +enable_testing() + +find_package(Qt6 REQUIRED COMPONENTS Core Test) +if(NOT UNIX OR APPLE) + find_package(Qt6 REQUIRED COMPONENTS Gui) +endif() + +qt6_standard_project_setup() +add_subdirectory(mylib) + +function(create_test_executable target) + if(CMAKE_VERSION VERSION_LESS "3.19") + qt_add_executable(${target} MANUAL_FINALIZATION main.cpp) + else() + qt_add_executable(${target} main.cpp) + endif() + + set_target_properties(${target} PROPERTIES + # We explicitly don't set WIN32_EXECUTABLE to ensure we see errors from stderr when + # something fails and not having to use DebugView. + + MACOSX_BUNDLE TRUE + ) + target_link_libraries(${target} PRIVATE MyLib Qt::Test) + + install(TARGETS ${target} + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + + # Install MyLib explicitly, because macdeployqt/winddeployqt don't take care of installing + # non-Qt dependencies. + if(APPLE) + install(TARGETS MyLib + LIBRARY DESTINATION "${target}.app/Contents/Frameworks" + ) + elseif(WIN32) + # windeployqt doesn't take care of installing non-Qt dependencies. + install(TARGETS MyLib + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + elseif(UNIX AND NOT QT6_IS_SHARED_LIBS_BUILD) + # In a static build, GRD won't run, and we need to install the DSO manually. + install(TARGETS MyLib + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ) + endif() + + if(NOT UNIX OR APPLE) + # Explicitly link against Qt::Gui, because otherwise, macdeployqt/windeployqt don't deploy + # the Gui module. + target_link_libraries(${target} PRIVATE Qt::Gui) + endif() + + qt_generate_deploy_app_script( + TARGET ${target} + OUTPUT_SCRIPT deploy_script + # Don't fail at configure time on unsupported platforms + NO_UNSUPPORTED_PLATFORM_ERROR + ) + install(SCRIPT ${deploy_script}) + + if(CMAKE_VERSION VERSION_LESS "3.19") + qt_finalize_target(${target}) + endif() + + if(APPLE AND NOT IOS) + set(installed_app_location "${CMAKE_INSTALL_PREFIX}/${target}.app/Contents/MacOS/${target}") + elseif(WIN32) + set(installed_app_location "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/${target}.exe") + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING) + set(installed_app_location "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/${target}") + endif() + + # There's no nice way to get the location of an installed binary, so we need to construct + # the binary install path by hand, somewhat similar to how it's done in + # the implementation of qt_deploy_runtime_dependencies. + # On unsupported deployment platforms, either the install_ test will fail not finding + # the location of the app (because we do not set a installed_app_location value) + # or the run_deployed_ test will fail because we didn't deploy the runtime dependencies. + # When support for additional platforms is added, these locations will have to be augmented. + add_test(install_${target} "${CMAKE_COMMAND}" --install .) + set_tests_properties(install_${target} PROPERTIES FIXTURES_SETUP deploy_step) + add_test(NAME run_deployed_${target} + COMMAND "${installed_app_location}" + # Make sure that we don't use the default working directory which is + # CMAKE_CURRENT_BINARY_DIR because on Windows the loader might pick up dlls + # from the working directory instead of the installed app dir, if the dll is + # missing in the app dir. + WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}") + set_tests_properties(run_deployed_${target} PROPERTIES FIXTURES_REQUIRED deploy_step) +endfunction() + +create_test_executable(App) + diff --git a/tests/auto/cmake/test_plugin_deployment/main.cpp b/tests/auto/cmake/test_plugin_deployment/main.cpp new file mode 100644 index 00000000000..bb9498793d5 --- /dev/null +++ b/tests/auto/cmake/test_plugin_deployment/main.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mylib/mylib.h" + +#include + +class test_plugin_deployment : public QObject +{ + Q_OBJECT +private slots: + void loadsTheRightPlugins(); +}; + +void test_plugin_deployment::loadsTheRightPlugins() +{ + const auto formats = getSupportedImageFormats(); + if (!formats.contains("gif")) { + qDebug() << "supported imageformats: " << formats; + QFAIL("Cannot load the qgif plugin. Plugin deployment failed."); + } +} + +QTEST_MAIN(test_plugin_deployment) + +#include "main.moc" diff --git a/tests/auto/cmake/test_plugin_deployment/mylib/CMakeLists.txt b/tests/auto/cmake/test_plugin_deployment/mylib/CMakeLists.txt new file mode 100644 index 00000000000..641854c6660 --- /dev/null +++ b/tests/auto/cmake/test_plugin_deployment/mylib/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +find_package(Qt6 REQUIRED COMPONENTS Gui) +qt_add_library(MyLib SHARED mylib.cpp) +target_compile_definitions(MyLib PRIVATE BUILD_MYLIB) +target_link_libraries(MyLib PRIVATE Qt6::Gui) diff --git a/tests/auto/cmake/test_plugin_deployment/mylib/mylib.cpp b/tests/auto/cmake/test_plugin_deployment/mylib/mylib.cpp new file mode 100644 index 00000000000..ba4213b5d19 --- /dev/null +++ b/tests/auto/cmake/test_plugin_deployment/mylib/mylib.cpp @@ -0,0 +1,9 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "mylib.h" +#include + +QList getSupportedImageFormats() +{ + return QImageReader::supportedImageFormats(); +} diff --git a/tests/auto/cmake/test_plugin_deployment/mylib/mylib.h b/tests/auto/cmake/test_plugin_deployment/mylib/mylib.h new file mode 100644 index 00000000000..7bf4b401929 --- /dev/null +++ b/tests/auto/cmake/test_plugin_deployment/mylib/mylib.h @@ -0,0 +1,14 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#pragma once + +#include +#include + +#ifdef BUILD_MYLIB +# define MYLIB_EXPORT Q_DECL_EXPORT +#else +# define MYLIB_EXPORT Q_DECL_IMPORT +#endif + +MYLIB_EXPORT QList getSupportedImageFormats();