From 3ee79be6280119afd5d1fb98df4320f0b105aa47 Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Tue, 30 Nov 2021 10:29:34 +1100 Subject: [PATCH] Add CMake deployment support Task-number: QTBUG-98545 Change-Id: I581c1173cdfc92c09fd2cf0bbe7ec6bc8d52b868 Reviewed-by: Qt CI Bot Reviewed-by: Joerg Bornemann --- src/corelib/CMakeLists.txt | 2 + src/corelib/Qt6CoreConfigExtras.cmake.in | 2 + src/corelib/Qt6CoreDeploySupport.cmake | 242 +++++++++++++++++ src/corelib/Qt6CoreMacros.cmake | 315 +++++++++++++++++++++++ 4 files changed, 561 insertions(+) create mode 100644 src/corelib/Qt6CoreDeploySupport.cmake diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 8bb040ca96..915a7ae08c 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -273,9 +273,11 @@ qt_internal_add_module(Core EXTRA_CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CTestMacros.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreConfigureFileTemplate.in" + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreDeploySupport.cmake" ${corelib_extra_cmake_files} # special case end ) +_qt_internal_setup_deploy_support() set(corelib_no_pch_sources compat/removed_api.cpp diff --git a/src/corelib/Qt6CoreConfigExtras.cmake.in b/src/corelib/Qt6CoreConfigExtras.cmake.in index 6ceb4b201f..d7ce721bbf 100644 --- a/src/corelib/Qt6CoreConfigExtras.cmake.in +++ b/src/corelib/Qt6CoreConfigExtras.cmake.in @@ -42,6 +42,8 @@ set(QT@PROJECT_VERSION_MAJOR@_IS_SHARED_LIBS_BUILD "@BUILD_SHARED_LIBS@") get_filename_component(_Qt6CoreConfigDir ${CMAKE_CURRENT_LIST_FILE} PATH) set(_Qt6CTestMacros "${_Qt6CoreConfigDir}/Qt6CTestMacros.cmake") +_qt_internal_setup_deploy_support() + @qtcore_extra_cmake_code@ if(ANDROID_PLATFORM) diff --git a/src/corelib/Qt6CoreDeploySupport.cmake b/src/corelib/Qt6CoreDeploySupport.cmake new file mode 100644 index 0000000000..eb2f92f3ce --- /dev/null +++ b/src/corelib/Qt6CoreDeploySupport.cmake @@ -0,0 +1,242 @@ +# NOTE: This code should only ever be executed in script mode. It expects to be +# used either as part of an install(CODE) call or called by a script +# invoked via cmake -P as a POST_BUILD step. + +cmake_minimum_required(VERSION 3.16...3.21) + +function(qt_deploy_qt_conf file_to_write) + set(no_value_options "") + set(single_value_options + PREFIX + DOC_DIR + HEADERS_DIR + LIB_DIR + LIBEXEC_DIR + BIN_DIR + PLUGINS_DIR + QML_DIR + ARCHDATA_DIR + DATA_DIR + TRANSLATIONS_DIR + EXAMPLES_DIR + TESTS_DIR + SETTINGS_DIR + ) + 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 "Unparsed arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + # Only write out locations that differ from the defaults + set(contents "[Paths]\n") + if(arg_PREFIX) + string(APPEND contents "Prefix = ${arg_PREFIX}\n") + endif() + if(arg_DOC_DIR AND NOT arg_DOC_DIR STREQUAL "doc") + string(APPEND contents "Documentation = ${arg_DOC_DIR}\n") + endif() + if(arg_HEADERS_DIR AND NOT arg_HEADERS_DIR STREQUAL "include") + string(APPEND contents "Headers = ${arg_HEADERS_DIR}\n") + endif() + if(arg_LIB_DIR AND NOT arg_LIB_DIR STREQUAL "lib") + string(APPEND contents "Libraries = ${arg_LIB_DIR}\n") + endif() + + # This one is special, the default is platform-specific + if(arg_LIBEXEC_DIR AND + ((WIN32 AND NOT arg_LIBEXEC_DIR STREQUAL "bin") OR + (NOT WIN32 AND NOT arg_LIBEXEC_DIR STREQUAL "libexec"))) + string(APPEND contents "LibraryExecutables = ${arg_LIBEXEC_DIR}\n") + endif() + + if(arg_BIN_DIR AND NOT arg_BIN_DIR STREQUAL "bin") + string(APPEND contents "Binaries = ${arg_BIN_DIR}\n") + endif() + if(arg_PLUGINS_DIR AND NOT arg_PLUGINS_DIR STREQUAL "plugins") + string(APPEND contents "Plugins = ${arg_PLUGINS_DIR}\n") + endif() + if(arg_QML_DIR AND NOT arg_QML_DIR STREQUAL "qml") + string(APPEND contents "QmlImports = ${arg_QML_DIR}\n") + endif() + if(arg_ARCHDATA_DIR AND NOT arg_ARCHDATA_DIR STREQUAL ".") + string(APPEND contents "ArchData = ${arg_ARCHDATA_DIR}\n") + endif() + if(arg_DATA_DIR AND NOT arg_DATA_DIR STREQUAL ".") + string(APPEND contents "Data = ${arg_DATA_DIR}\n") + endif() + if(arg_TRANSLATIONS_DIR AND NOT arg_TRANSLATIONS_DIR STREQUAL "translations") + string(APPEND contents "Translations = ${arg_TRANSLATIONS_DIR}\n") + endif() + if(arg_EXAMPLES_DIR AND NOT arg_EXAMPLES_DIR STREQUAL "examples") + string(APPEND contents "Examples = ${arg_EXAMPLES_DIR}\n") + endif() + if(arg_TESTS_DIR AND NOT arg_TESTS_DIR STREQUAL "tests") + string(APPEND contents "Tests = ${arg_TESTS_DIR}\n") + endif() + if(arg_SETTINGS_DIR AND NOT arg_SETTINGS_DIR STREQUAL ".") + string(APPEND contents "Settings = ${arg_SETTINGS_DIR}\n") + endif() + + message(STATUS "Writing ${file_to_write}") + file(WRITE "${file_to_write}" "${contents}") +endfunction() + +function(qt_deploy_runtime_dependencies) + + if(NOT __QT_DEPLOY_TOOL) + message(FATAL_ERROR "No Qt deploy tool available for this target platform") + endif() + + set(no_value_options + MACOS_BUNDLE + GENERATE_QT_CONF + VERBOSE + NO_OVERWRITE + NO_APP_STORE_COMPLIANCE # TODO: Might want a better name + ) + set(single_value_options + EXECUTABLE + BIN_DIR + LIB_DIR + PLUGINS_DIR + QML_DIR + ) + set(multi_value_options + # These ADDITIONAL_... options are based on what file(GET_RUNTIME_DEPENDENCIES) + # supports. We differentiate between the types of binaries so that we keep + # open the possibility of switching to a purely CMake implementation of + # the deploy tool based on file(GET_RUNTIME_DEPENDENCIES) instead of the + # individual platform-specific tools (macdeployqt, windeployqt, etc.). + ADDITIONAL_EXECUTABLES + ADDITIONAL_LIBRARIES + ADDITIONAL_MODULES + ) + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unparsed arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + if(NOT arg_EXECUTABLE) + message(FATAL_ERROR "EXECUTABLE must be specified") + endif() + + # None of these are used if the executable is a macOS app bundle + if(NOT arg_BIN_DIR) + set(arg_BIN_DIR "${QT_DEPLOY_BIN_DIR}") + endif() + if(NOT arg_LIB_DIR) + set(arg_LIB_DIR "${QT_DEPLOY_LIB_DIR}") + endif() + if(NOT arg_QML_DIR) + set(arg_QML_DIR "${QT_DEPLOY_QML_DIR}") + endif() + if(NOT arg_PLUGINS_DIR) + set(arg_PLUGINS_DIR "${QT_DEPLOY_PLUGINS_DIR}") + endif() + + # macdeployqt always writes out a qt.conf file. It will complain if one + # already exists, so leave it to create it for us if we will be running it. + if(MACOS_BUNDLE AND __QT_DEPLOY_SYSTEM_NAME STREQUAL Darwin) + # We might get EXECUTABLE pointing to either the actual binary under the + # Contents/MacOS directory, or it might be pointing to the top of the + # app bundle (i.e. the .app directory). We want the latter to + # pass to macdeployqt. + if(arg_EXECUTABLE MATCHES "^((.*/)?(.*).app)/Contents/MacOS/(.*)$") + set(arg_EXECUTABLE "${CMAKE_MATCH_1}") + endif() + elseif(arg_GENERATE_QT_CONF) + get_filename_component(exe_dir "${arg_EXECUTABLE}" DIRECTORY) + if(exe_dir STREQUAL "") + set(exe_dir ".") + set(prefix ".") + else() + string(REPLACE "/" ";" path "${exe_dir}") + list(LENGTH path path_count) + string(REPEAT "../" ${path_count} rel_path) + string(REGEX REPLACE "/+$" "" prefix "${rel_path}") + endif() + qt_deploy_qt_conf("${QT_DEPLOY_PREFIX}/${exe_dir}/qt.conf" + PREFIX "${prefix}" + BIN_DIR "${arg_BIN_DIR}" + LIB_DIR "${arg_LIB_DIR}" + PLUGINS_DIR "${arg_PLUGINS_DIR}" + QML_DIR "${arg_QML_DIR}" + ) + endif() + + set(extra_binaries_option "") + set(tool_options "") + + if(arg_VERBOSE OR __QT_DEPLOY_VERBOSE) + # macdeployqt supports 0-3: 0=no output, 1=error/warn (default), 2=normal, 3=debug + # windeployqt supports 0-2: 0=error/warn (default), 1=verbose, 2=full_verbose + if(__QT_DEPLOY_SYSTEM_NAME STREQUAL Windows) + list(APPEND tool_options --verbose 2) + elseif(__QT_DEPLOY_SYSTEM_NAME STREQUAL Darwin) + list(APPEND tool_options -verbose=3) + endif() + endif() + + if(__QT_DEPLOY_SYSTEM_NAME STREQUAL Windows) + list(APPEND tool_options + --dir . + --libdir "${arg_BIN_DIR}" # NOTE: Deliberately not arg_LIB_DIR + --plugindir "${arg_PLUGINS_DIR}" + ) + if(NOT arg_NO_OVERWRITE) + list(APPEND tool_options --force) + endif() + elseif(__QT_DEPLOY_SYSTEM_NAME STREQUAL Darwin) + set(extra_binaries_option "-executable=") + if(NOT arg_NO_APP_STORE_COMPLIANCE) + list(APPEND tool_options -appstore-compliant) + endif() + if(NOT arg_NO_OVERWRITE) + list(APPEND tool_options -always-overwrite) + endif() + endif() + + # This is an internal variable. It is normally unset and is only intended + # for debugging purposes. It may be removed at any time without warning. + list(APPEND tool_options ${__qt_deploy_tool_extra_options}) + + # 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} + ${arg_ADDITIONAL_MODULES} + ) + foreach(extra_binary IN LISTS additional_binaries) + list(APPEND tool_options "${extra_binaries_option}${extra_binary}") + endforeach() + + message(STATUS "Running Qt deploy tool for ${arg_EXECUTABLE}") + execute_process( + COMMAND_ECHO STDOUT + COMMAND "${__QT_DEPLOY_TOOL}" "${arg_EXECUTABLE}" ${tool_options} + WORKING_DIRECTORY "${QT_DEPLOY_PREFIX}" + RESULT_VARIABLE result + ) + if(result) + message(FATAL_ERROR "Executing ${__QT_DEPLOY_TOOL} failed: ${result}") + endif() + +endfunction() + +function(_qt_internal_show_skip_runtime_deploy_message qt_build_type_string) + message(STATUS + "Skipping runtime deployment steps. " + "Support for installing runtime dependencies is not implemented for " + "this target platform (${__QT_DEPLOY_SYSTEM_NAME}, ${qt_build_type_string})." + ) +endfunction() diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index a82cf30b36..eafdd02695 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -723,6 +723,8 @@ function(_qt_internal_finalize_executable target) endif() if(IOS) _qt_internal_finalize_ios_app("${target}") + elseif(APPLE) + _qt_internal_finalize_macos_app("${target}") endif() # For finalizer mode of plugin importing to work safely, we need to know the list of Qt @@ -943,6 +945,22 @@ function(_qt_internal_finalize_ios_app target) _qt_internal_set_placeholder_apple_bundle_version("${target}") endfunction() +function(_qt_internal_finalize_macos_app target) + get_target_property(is_bundle ${target} MACOSX_BUNDLE) + if(NOT is_bundle) + return() + endif() + + # Make sure the install rpath has at least the minimum needed if the app + # has any non-static frameworks. We can't rigorously know if the app will + # have any, even with a static Qt, so always add this. If there are no + # frameworks, it won't do any harm. + get_property(install_rpath TARGET ${target} PROPERTY INSTALL_RPATH) + list(APPEND install_rpath "@executable_path/../Frameworks") + list(REMOVE_DUPLICATES install_rpath) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "${install_rpath}") +endfunction() + if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_add_executable) qt6_add_executable(${ARGV}) @@ -2412,3 +2430,300 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) qt6_disable_unicode_defines(${ARGV}) endfunction() endif() + +function(_qt_internal_get_deploy_impl_dir var) + set(${var} "${CMAKE_BINARY_DIR}/.qt" PARENT_SCOPE) +endfunction() + +function(_qt_internal_add_deploy_support deploy_support_file) + get_filename_component(deploy_support_file "${deploy_support_file}" REALPATH) + + set(target ${QT_CMAKE_EXPORT_NAMESPACE}::Core) + get_target_property(aliased_target ${target} ALIASED_TARGET) + if(aliased_target) + set(target ${aliased_target}) + endif() + + get_property(scripts TARGET ${target} PROPERTY _qt_deploy_support_files) + if(NOT "${deploy_support_file}" IN_LIST scripts) + set_property(TARGET ${target} APPEND PROPERTY + _qt_deploy_support_files "${deploy_support_file}" + ) + endif() +endfunction() + +# Sets up the commands for use at install/deploy time +function(_qt_internal_setup_deploy_support) + get_property(cmake_role GLOBAL PROPERTY CMAKE_ROLE) + if(NOT cmake_role STREQUAL "PROJECT") + return() + endif() + + # Always set QT_DEPLOY_SUPPORT in the caller's scope, even if we've generated + # the deploy support file in a previous call. The project may be calling + # find_package() from sibling directories with separate variable scopes. + _qt_internal_get_deploy_impl_dir(deploy_impl_dir) + + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + set(QT_DEPLOY_SUPPORT "${deploy_impl_dir}/QtDeploySupport-$.cmake") + else() + set(QT_DEPLOY_SUPPORT "${deploy_impl_dir}/QtDeploySupport.cmake") + endif() + set(QT_DEPLOY_SUPPORT "${QT_DEPLOY_SUPPORT}" PARENT_SCOPE) + + get_property(have_generated_file GLOBAL PROPERTY _qt_have_generated_deploy_support) + if(have_generated_file) + return() + endif() + set_property(GLOBAL PROPERTY _qt_have_generated_deploy_support TRUE) + + include(GNUInstallDirs) + set(target ${QT_CMAKE_EXPORT_NAMESPACE}::Core) + get_target_property(aliased_target ${target} ALIASED_TARGET) + if(aliased_target) + set(target ${aliased_target}) + endif() + + # Make sure to look under the Qt bin dir with find_program, rather than randomly picking up + # a deployqt tool in the system. + # QT6_INSTALL_PREFIX is not set during Qt build, so add the hints conditionally. + set(find_program_hints) + if(QT6_INSTALL_PREFIX) + set(find_program_hints HINTS ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}) + endif() + + # In the generator expression logic below, we need safe_target_file because + # CMake evaluates expressions in both the TRUE and FALSE branches of $. + # We still need a target to give to $ when we have no deploy + # tool, so we cannot use something like $ directly. + if(APPLE AND NOT IOS) + find_program(MACDEPLOYQT_EXECUTABLE macdeployqt + ${find_program_hints}) + set(fallback "$<$:${MACDEPLOYQT_EXECUTABLE}>") + set(target_if_exists "$") + set(have_deploy_tool "$") + set(safe_target_file + "$>") + set(__QT_DEPLOY_TOOL "$") + elseif(WIN32) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt + ${find_program_hints}) + set(fallback "$<$:${WINDEPLOYQT_EXECUTABLE}>") + set(target_if_exists "$") + set(have_deploy_tool "$") + set(safe_target_file + "$>") + set(__QT_DEPLOY_TOOL "$") + else() + # Android is handled as a build target, not via this install-based approach. + # Therefore, we don't consider androiddeployqt here. + set(__QT_DEPLOY_TOOL "") + endif() + + _qt_internal_add_deploy_support("${CMAKE_CURRENT_LIST_DIR}/Qt6CoreDeploySupport.cmake") + + file(GENERATE OUTPUT "${QT_DEPLOY_SUPPORT}" CONTENT +"cmake_minimum_required(VERSION 3.16...3.21) + +# These are part of the public API. Projects should use them to provide a +# consistent set of prefix-relative destinations. +if(NOT QT_DEPLOY_BIN_DIR) + set(QT_DEPLOY_BIN_DIR \"${CMAKE_INSTALL_BINDIR}\") +endif() +if(NOT QT_DEPLOY_LIB_DIR) + set(QT_DEPLOY_LIB_DIR \"${CMAKE_INSTALL_LIBDIR}\") +endif() +if(NOT QT_DEPLOY_PLUGINS_DIR) + set(QT_DEPLOY_PLUGINS_DIR \"plugins\") +endif() +if(NOT QT_DEPLOY_QML_DIR) + set(QT_DEPLOY_QML_DIR \"qml\") +endif() +if(NOT QT_DEPLOY_PREFIX) + set(QT_DEPLOY_PREFIX \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\") +endif() +if(QT_DEPLOY_PREFIX STREQUAL \"\") + set(QT_DEPLOY_PREFIX .) +endif() + +# These are internal implementation details. They may be removed at any time. +set(__QT_DEPLOY_SYSTEM_NAME \"${CMAKE_SYSTEM_NAME}\") +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_DEPLOY_GENERATOR_IS_MULTI_CONFIG \"${is_multi_config}\") +set(__QT_DEPLOY_ACTIVE_CONFIG \"$\") + +# Define the CMake commands to be made available during deployment. +set(__qt_deploy_support_files + \"$,\" + \">\" +) +foreach(__qt_deploy_support_file IN LISTS __qt_deploy_support_files) + include(\"\${__qt_deploy_support_file}\") +endforeach() + +unset(__qt_deploy_support_file) +unset(__qt_deploy_support_files) +") +endfunction() + +# Note this needs to be a macro because it sets variables intended for the +# calling scope. +macro(qt6_standard_project_setup) + # A parent project might want to prevent child projects pulled in with + # add_subdirectory() from changing the parent's preferred arrangement. + # They can set this variable to true to effectively disable this function. + if(NOT QT_NO_STANDARD_PROJECT_SETUP) + + # All changes below this point should not result in a change to an + # existing value, except for CMAKE_INSTALL_RPATH which may append new + # values (but no duplicates). + + # Use standard install locations, provided by GNUInstallDirs. All + # platforms should have this included so that we know the + # CMAKE_INSTALL_xxxDIR variables will be set. + include(GNUInstallDirs) + if(WIN32) + # Windows has no RPATH support, so we need all non-plugin DLLs in + # the same directory as application executables if we want to be + # able to run them without having to augment the PATH environment + # variable. Don't discard an existing value in case the project has + # already set this to somewhere else. Our setting is somewhat + # opinionated, so make it easy for projects to choose something else. + if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + elseif(NOT APPLE) + # Apart from Windows and Apple, most other platforms support RPATH + # and $ORIGIN. Make executables and non-static libraries use an + # install RPATH that allows them to find library dependencies if the + # project installs things to the directories defined by the + # CMAKE_INSTALL_xxxDIR variables (which is what CMake's defaults + # are based on). + file(RELATIVE_PATH __qt_relDir + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR} + ) + list(APPEND CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${__qt_relDir}) + list(REMOVE_DUPLICATES CMAKE_INSTALL_RPATH) + unset(__qt_reldir) + endif() + + # Turn these on by default, unless they are already set. Projects can + # always turn off any they really don't want after we return. + foreach(auto_set IN ITEMS MOC UIC RCC) + if(NOT DEFINED CMAKE_AUTO${auto_set}) + set(CMAKE_AUTO${auto_set} TRUE) + endif() + endforeach() + endif() +endmacro() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + macro(qt_standard_project_setup) + qt6_standard_project_setup(${ARGV}) + endmacro() +endif() + +function(qt6_generate_deploy_app_script) + # We use a TARGET keyword option instead of taking the target as the first + # positional argument. This is to keep open the possibility of deploying + # an app for which we don't have a target (e.g. an application from a + # third party project that the caller may want to include in their own + # package). We would add an EXECUTABLE keyword for that, which would be + # mutually exclusive with the TARGET keyword. + set(no_value_options + NO_UNSUPPORTED_PLATFORM_ERROR + ) + set(single_value_options + TARGET + FILENAME_VARIABLE + ) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 0 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 arg_TARGET) + message(FATAL_ERROR "TARGET must be specified") + endif() + if(NOT arg_FILENAME_VARIABLE) + message(FATAL_ERROR "FILENAME_VARIABLE must be specified") + 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 + # create deployment scripts for different scenarios). + string(MAKE_C_IDENTIFIER "${arg_TARGET}" target_id) + string(SHA1 args_hash "${ARGV}") + string(SUBSTRING "${args_hash}" 0 10 short_hash) + _qt_internal_get_deploy_impl_dir(deploy_impl_dir) + 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 "-$") + endif() + set(${arg_FILENAME_VARIABLE} "${file_name}" PARENT_SCOPE) + + if(QT6_IS_SHARED_LIBS_BUILD) + set(qt_build_type_string "shared Qt libs") + else() + set(qt_build_type_string "static Qt libs") + endif() + + if(APPLE AND NOT IOS AND QT6_IS_SHARED_LIBS_BUILD) + # TODO: Handle non-bundle applications if possible. + get_target_property(is_bundle ${arg_TARGET} MACOSX_BUNDLE) + if(NOT is_bundle) + message(FATAL_ERROR + "Executable targets have to be app bundles to use this command " + "on Apple platforms." + ) + endif() + file(GENERATE OUTPUT "${file_name}" CONTENT " +include(${QT_DEPLOY_SUPPORT}) +qt_deploy_runtime_dependencies( + EXECUTABLE $.app + MACOS_BUNDLE +) +") + + elseif(WIN32 AND QT6_IS_SHARED_LIBS_BUILD) + file(GENERATE OUTPUT "${file_name}" CONTENT " +include(${QT_DEPLOY_SUPPORT}) +qt_deploy_runtime_dependencies( + EXECUTABLE ${CMAKE_INSTALL_BINDIR}/$ + 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. + # We also don't do it if targeting Linux, but we could provide an option to do + # so if we had a deploy tool or purely CMake-based deploy implementation. + # Error out by default unless the project opted out of the error. + # This provides us a migration path in the future without breaking compatibility promises. + message(FATAL_ERROR + "Support for installing runtime dependencies is not implemented for " + "this target platform (${CMAKE_SYSTEM_NAME}, ${qt_build_type_string})." + ) + else() + file(GENERATE OUTPUT "${file_name}" CONTENT " +include(${QT_DEPLOY_SUPPORT}) +_qt_internal_show_skip_runtime_deploy_message(\"${qt_build_type_string}\") +") + endif() + +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + macro(qt_generate_deploy_app_script) + qt6_generate_deploy_app_script(${ARGV}) + endmacro() +endif()