diff --git a/cmake/QtPublicPluginHelpers.cmake b/cmake/QtPublicPluginHelpers.cmake index bd0f71c01a3..b0324585ac0 100644 --- a/cmake/QtPublicPluginHelpers.cmake +++ b/cmake/QtPublicPluginHelpers.cmake @@ -39,22 +39,32 @@ endfunction() # # 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. +# All the TARGET_PROPERTY genexes are evaluated in the context of the currently linked target, +# unless the TARGET argument is given. # # The genex is saved into out_var. function(__qt_internal_get_static_plugin_condition_genex plugin_target_unprefixed out_var) + set(options) + set(oneValueArgs TARGET) + set(multiValueArgs) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(plugin_target "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target_unprefixed}") set(plugin_target_versionless "Qt::${plugin_target_unprefixed}") get_target_property(_plugin_type "${plugin_target}" QT_PLUGIN_TYPE) + set(target_infix "") + if(arg_TARGET) + set(target_infix "${arg_TARGET},") + endif() + set(_default_plugins_are_enabled - "$>,0>>") - set(_manual_plugins_genex "$>") - set(_no_plugins_genex "$>") + "$>,0>>") + set(_manual_plugins_genex "$>") + set(_no_plugins_genex "$>") # Plugin genex marker for prl processing. set(_is_plugin_marker_genex "$") @@ -81,7 +91,7 @@ function(__qt_internal_get_static_plugin_condition_genex ">," # Excludes both plugins targeted by EXCLUDE_BY_TYPE and not included in # INCLUDE_BY_TYPE. - "$>>" + "$>>" ">" ) @@ -90,7 +100,7 @@ function(__qt_internal_get_static_plugin_condition_genex "$" + "$" ">" ">" ) @@ -98,7 +108,7 @@ function(__qt_internal_get_static_plugin_condition_genex "$" + "$" ">" ">" ) @@ -106,7 +116,7 @@ function(__qt_internal_get_static_plugin_condition_genex # No point in linking the plugin initialization source file into static libraries. The # initialization symbol will be discarded by the linker when the static lib is linked into an # executable or shared library, because nothing is referencing the global static symbol. - set(type_genex "$") + set(type_genex "$") set(no_static_genex "$>") # Complete condition that defines whether a static plugin is linked @@ -308,6 +318,25 @@ function(__qt_internal_collect_plugin_init_libraries plugin_targets out_var) set("${out_var}" "${plugin_inits_to_link}" PARENT_SCOPE) endfunction() +# Collect a list of genexes to deploy plugin libraries. +function(__qt_internal_collect_plugin_library_files target plugin_targets out_var) + set(library_files "") + + foreach(plugin_target ${plugin_targets}) + set(plugin_target_versioned "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}") + __qt_internal_get_static_plugin_condition_genex( + "${plugin_target}" + plugin_condition + TARGET ${target} + ) + + set(target_genex "$<${plugin_condition}:${plugin_target_versioned}>") + list(APPEND library_files "$<$:$>") + endforeach() + + set("${out_var}" "${library_files}" PARENT_SCOPE) +endfunction() + # Collects all plugin targets discovered by walking the dependencies of ${target}. # # Walks immediate dependencies and their transitive dependencies. @@ -387,6 +416,29 @@ function(__qt_internal_collect_plugin_targets_from_dependencies_of_plugins targe set("${out_var}" "${plugin_targets}" PARENT_SCOPE) endfunction() +function(__qt_internal_generate_plugin_deployment_info target plugin_targets) + 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. @@ -395,6 +447,9 @@ function(__qt_internal_apply_plugin_imports_finalizer_mode target) return() endif() + __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets) + __qt_internal_generate_plugin_deployment_info(${target} "${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+). @@ -408,7 +463,6 @@ 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/Qt6CoreDeploySupport.cmake b/src/corelib/Qt6CoreDeploySupport.cmake index 24ddc47bcbc..7c307d18e0e 100644 --- a/src/corelib/Qt6CoreDeploySupport.cmake +++ b/src/corelib/Qt6CoreDeploySupport.cmake @@ -105,6 +105,108 @@ if(NOT __QT_NO_CREATE_VERSIONLESS_FUNCTIONS) endfunction() endif() +# Copied from QtCMakeHelpers.cmake +function(_qt_internal_re_escape out_var str) + string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${str}") + set(${out_var} ${regex} PARENT_SCOPE) +endfunction() + +function(_qt_internal_generic_deployqt) + set(no_value_options + VERBOSE + ) + set(single_value_options + EXECUTABLE + LIB_DIR + PLUGINS_DIR + ) + set(multi_value_options + ADDITIONAL_EXECUTABLES + ADDITIONAL_LIBRARIES + ADDITIONAL_MODULES + ) + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + if(arg_VERBOSE OR __QT_DEPLOY_VERBOSE) + set(verbose TRUE) + endif() + + # Make input file paths absolute + foreach(var IN ITEMS EXECUTABLE ADDITIONAL_EXECUTABLES ADDITIONAL_LIBRARIES ADDITIONAL_MODULES) + string(PREPEND var arg_) + set(abspaths "") + foreach(path IN LISTS ${var}) + get_filename_component(abspath "${path}" REALPATH BASE_DIR "${QT_DEPLOY_PREFIX}") + list(APPEND abspaths "${abspath}") + endforeach() + set(${var} "${abspaths}") + endforeach() + + # We need to get the runtime dependencies of plugins too. + list(APPEND arg_ADDITIONAL_MODULES ${__QT_DEPLOY_PLUGINS}) + + set(file_args "") + if(arg_EXECUTABLE OR arg_ADDITIONAL_EXECUTABLES) + list(APPEND file_args EXECUTABLES ${arg_EXECUTABLE} ${arg_ADDITIONAL_EXECUTABLES}) + endif() + if(arg_ADDITIONAL_LIBRARIES) + list(APPEND file_args LIBRARIES ${arg_ADDITIONAL_LIBRARIES}) + endif() + if(arg_ADDITIONAL_MODULES) + list(APPEND file_args MODULES ${arg_ADDITIONAL_MODULES}) + endif() + + # Compile a list of regular expressions that represent the Qt installation prefixes. + set(prefix_regexes) + foreach(path IN LISTS __QT_DEPLOY_QT_INSTALL_PREFIX + __QT_DEPLOY_QT_ADDITIONAL_PACKAGES_PREFIX_PATH) + _qt_internal_re_escape(path_rex "${path}") + list(APPEND prefix_regexes "^${path_rex}") + endforeach() + + # Get the runtime dependencies recursively, restricted to Qt's installation prefix. + file(GET_RUNTIME_DEPENDENCIES + ${file_args} + POST_INCLUDE_REGEXES ${prefix_regexes} + POST_EXCLUDE_REGEXES ".*" + 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() + endif() + + # Deploy the Qt libraries. + file(INSTALL ${resolved} + DESTINATION "${QT_DEPLOY_PREFIX}/${arg_LIB_DIR}" + FOLLOW_SYMLINK_CHAIN + ) + + # Deploy the Qt plugins. + foreach(file_path IN LISTS __QT_DEPLOY_PLUGINS) + file(RELATIVE_PATH destination + "${__QT_DEPLOY_QT_INSTALL_PREFIX}/${__QT_DEPLOY_QT_INSTALL_PLUGINS}" + "${file_path}" + ) + get_filename_component(destination "${destination}" DIRECTORY) + string(PREPEND destination "${QT_DEPLOY_PREFIX}/${arg_PLUGINS_DIR}/") + file(INSTALL ${file_path} DESTINATION ${destination}) + endforeach() +endfunction() + # This function is currently in Technical Preview. # Its signature and behavior might change. function(qt6_deploy_runtime_dependencies) @@ -202,6 +304,8 @@ function(qt6_deploy_runtime_dependencies) list(APPEND tool_options --verbose 2) elseif(__QT_DEPLOY_SYSTEM_NAME STREQUAL Darwin) list(APPEND tool_options -verbose=3) + else() + list(APPEND tool_options VERBOSE) endif() endif() @@ -228,10 +332,28 @@ function(qt6_deploy_runtime_dependencies) # for debugging purposes. It may be removed at any time without warning. list(APPEND tool_options ${__qt_deploy_tool_extra_options}) + if(__QT_DEPLOY_TOOL STREQUAL "GRD") + message(STATUS "Running generic Qt deploy tool on ${arg_EXECUTABLE}") + + # Forward the ADDITIONAL_* arguments. + foreach(file_type EXECUTABLES LIBRARIES MODULES) + if("${arg_ADDITIONAL_${file_type}}" STREQUAL "") + continue() + endif() + list(APPEND tool_options ADDITIONAL_${file_type} ${arg_ADDITIONAL_${file_type}}) + endforeach() + + _qt_internal_generic_deployqt( + EXECUTABLE "${arg_EXECUTABLE}" + LIB_DIR "${arg_LIB_DIR}" + PLUGINS_DIR "${arg_PLUGINS_DIR}" + ${tool_options} + ) + return() + endif() + # Both windeployqt and macdeployqt don't differentiate between the different # types of binaries, so we merge the lists and treat them all the same. - # A purely CMake-based implementation would need to treat them differently - # because of how file(GET_RUNTIME_DEPENDENCIES) works. set(additional_binaries ${arg_ADDITIONAL_EXECUTABLES} ${arg_ADDITIONAL_LIBRARIES} diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 1587af13021..3bea0f135bb 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -2437,6 +2437,8 @@ function(_qt_internal_setup_deploy_support) set(safe_target_file "$>") set(__QT_DEPLOY_TOOL "$") + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING) + set(__QT_DEPLOY_TOOL "GRD") else() # Android is handled as a build target, not via this install-based approach. # Therefore, we don't consider androiddeployqt here. @@ -2480,6 +2482,10 @@ 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}\") set(__QT_DEFAULT_MAJOR_VERSION \"${QT_DEFAULT_MAJOR_VERSION}\") +set(__QT_DEPLOY_QT_ADDITIONAL_PACKAGES_PREFIX_PATH \"${QT_ADDITIONAL_PACKAGES_PREFIX_PATH}\") +set(__QT_DEPLOY_QT_INSTALL_PREFIX \"${QT6_INSTALL_PREFIX}\") +set(__QT_DEPLOY_QT_INSTALL_PLUGINS \"${QT6_INSTALL_PLUGINS}\") +set(__QT_DEPLOY_PLUGINS \"\") # Define the CMake commands to be made available during deployment. set(__qt_deploy_support_files @@ -2593,6 +2599,21 @@ function(qt6_generate_deploy_script) message(FATAL_ERROR "CONTENT must be specified") endif() + # Check whether manual finalization is needed. + if(CMAKE_VERSION VERSION_LESS "3.19") + get_target_property(is_immediately_finalized ${arg_TARGET} _qt_is_immediately_finalized) + if(is_immediately_finalized) + message(WARNING + "Deployment of plugins for target '${arg_TARGET}' will not work. " + "Either, upgrade CMake to version 3.19 or newer, or call " + "qt_finalize_target(${arg_TARGET}) after generating the deployment script." + ) + endif() + endif() + + # Mark the target as "to be deployed". + set_property(TARGET ${arg_TARGET} PROPERTY _qt_marked_for_deployment ON) + # 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 @@ -2610,12 +2631,15 @@ function(qt6_generate_deploy_script) set(file_name "${deploy_impl_dir}/deploy_${target_id}_${short_hash}") get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) if(is_multi_config) - string(APPEND file_name "-$") + set(config_infix "-$") + else() + set(config_infix "") endif() - string(APPEND file_name ".cmake") + string(APPEND file_name "${config_infix}.cmake") set(${arg_FILENAME_VARIABLE} "${file_name}" PARENT_SCOPE) set(boiler_plate "include(${QT_DEPLOY_SUPPORT}) +include(\"\${CMAKE_CURRENT_LIST_DIR}/${arg_TARGET}-plugins${config_infix}.cmake\" OPTIONAL) ") list(TRANSFORM arg_CONTENT REPLACE "\\$" "\$") file(GENERATE OUTPUT ${file_name} CONTENT "${boiler_plate}${arg_CONTENT}") @@ -2694,7 +2718,17 @@ qt6_deploy_runtime_dependencies( qt6_deploy_runtime_dependencies( EXECUTABLE $ GENERATE_QT_CONF -)") +) +") + + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND QT6_IS_SHARED_LIBS_BUILD) + qt6_generate_deploy_script(${generate_args} + CONTENT " +qt6_deploy_runtime_dependencies( + EXECUTABLE $ + GENERATE_QT_CONF +) +") elseif(NOT arg_NO_UNSUPPORTED_PLATFORM_ERROR AND NOT QT_INTERNAL_NO_UNSUPPORTED_PLATFORM_ERROR) # Currently we don't deploy runtime dependencies if cross-compiling or using a static Qt. diff --git a/src/corelib/doc/src/cmake/qt_generate_deploy_app_script.qdoc b/src/corelib/doc/src/cmake/qt_generate_deploy_app_script.qdoc index e3e4901512e..8d059f4c867 100644 --- a/src/corelib/doc/src/cmake/qt_generate_deploy_app_script.qdoc +++ b/src/corelib/doc/src/cmake/qt_generate_deploy_app_script.qdoc @@ -48,8 +48,12 @@ which should come after the application's target has been installed using The deployment script will call \l{qt_deploy_runtime_dependencies()} with a suitable set of options for the standard install layout. -Currently, this is only implemented for macOS app bundles built on a macOS -host and Windows executables built on a Windows host. +Currently, this is only implemented for +\list + \li macOS app bundles built on a macOS host, + \li Linux executables built on a Linux host, + \li and Windows executables built on a Windows host. +\endlist Cross-building a Windows executable on a Linux host, as well as similar scenarios, are not currently supported. Calling \c{qt_generate_deploy_app_script()} in such a case will result diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index eaf4fe67f02..5153350eb69 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -328,7 +328,8 @@ set(deploy_args # 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))) +if(WIN32 OR (APPLE AND NOT IOS) + OR (UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING)) _qt_internal_test_expect_pass(${deploy_args}) else() _qt_internal_test_expect_fail(${deploy_args}) diff --git a/tests/auto/cmake/test_widgets_app_deployment/CMakeLists.txt b/tests/auto/cmake/test_widgets_app_deployment/CMakeLists.txt index f1d59da234b..60e3f0af758 100644 --- a/tests/auto/cmake/test_widgets_app_deployment/CMakeLists.txt +++ b/tests/auto/cmake/test_widgets_app_deployment/CMakeLists.txt @@ -12,7 +12,12 @@ qt6_standard_project_setup() function(create_test_executable target) cmake_parse_arguments(arg "" "" "" ${ARGN}) - qt_add_executable(${target} main.cpp) + 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. @@ -34,10 +39,16 @@ function(create_test_executable target) ) 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