aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/Qt6QmlDeploySupport.cmake
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/Qt6QmlDeploySupport.cmake')
-rw-r--r--src/qml/Qt6QmlDeploySupport.cmake331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/qml/Qt6QmlDeploySupport.cmake b/src/qml/Qt6QmlDeploySupport.cmake
new file mode 100644
index 0000000000..a230e71409
--- /dev/null
+++ b/src/qml/Qt6QmlDeploySupport.cmake
@@ -0,0 +1,331 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# 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. It would not normally be
+# included directly, it should be pulled in automatically by the deploy
+# support set up by qtbase.
+
+cmake_minimum_required(VERSION 3.16...3.21)
+
+function(qt6_deploy_qml_imports)
+ set(no_value_options
+ NO_QT_IMPORTS
+ )
+ set(single_value_options
+ TARGET
+ PLUGINS_DIR # Internal option, only used for macOS app bundle targets
+ QML_DIR
+ PLUGINS_FOUND # Name of an output 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 "Unparsed arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
+ if(NOT arg_TARGET)
+ message(FATAL_ERROR "TARGET must be specified")
+ 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()
+
+ # The target's finalizer should have written out this file
+ string(MAKE_C_IDENTIFIER "${arg_TARGET}" target_id)
+ set(filename "${__QT_DEPLOY_IMPL_DIR}/deploy_qml_imports/${target_id}")
+ if(__QT_DEPLOY_GENERATOR_IS_MULTI_CONFIG)
+ string(APPEND filename "-${__QT_DEPLOY_ACTIVE_CONFIG}")
+ endif()
+ string(APPEND filename ".cmake")
+ if(NOT EXISTS "${filename}")
+ message(FATAL_ERROR
+ "No QML imports information recorded for target ${arg_TARGET}. "
+ "The target must be an executable and qt_add_qml_module() must "
+ "have been called with it. If using a CMake version lower than 3.19, ensure "
+ "that the executable is manually finalized with qt_finalize_target(). "
+ "Missing file:\n ${filename}"
+ )
+ endif()
+ include(${filename})
+
+endfunction()
+
+if(NOT __QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_deploy_qml_imports)
+ if(__QT_DEFAULT_MAJOR_VERSION EQUAL 6)
+ qt6_deploy_qml_imports(${ARGV})
+
+ cmake_parse_arguments(PARSE_ARGV 0 arg "" "PLUGINS_FOUND" "")
+ if(arg_PLUGINS_FOUND)
+ set(${arg_PLUGINS_FOUND} ${${arg_PLUGINS_FOUND}} PARENT_SCOPE)
+ endif()
+ else()
+ message(FATAL_ERROR "qt_deploy_qml_imports() is only available in Qt 6.")
+ endif()
+ endfunction()
+endif()
+
+function(_qt_internal_deploy_qml_imports_for_target)
+ set(no_value_options
+ BUNDLE
+ NO_QT_IMPORTS
+ )
+ set(single_value_options
+ IMPORTS_FILE
+ PLUGINS_FOUND
+ QML_DIR
+ PLUGINS_DIR
+ )
+ 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()
+ foreach(opt IN LISTS single_value_options)
+ if(NOT arg_${opt})
+ message(FATAL_ERROR "Required argument not provided: ${opt}")
+ endif()
+ endforeach()
+
+ include("${arg_IMPORTS_FILE}")
+
+ macro(_qt_internal_parse_qml_imports_entry prefix index)
+ cmake_parse_arguments("${prefix}"
+ ""
+ "CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;LINKTARGET"
+ ""
+ ${qml_import_scanner_import_${index}}
+ )
+ endmacro()
+
+ get_filename_component(install_prefix_abs "${QT_DEPLOY_PREFIX}" ABSOLUTE)
+ set(plugins_found "")
+
+ if(__QT_DEPLOY_POST_BUILD)
+ message(STATUS "Running macOS bundle QML support POST_BUILD routine.")
+ endif()
+
+ # Parse the generated cmake file. It is possible for the scanner to find no
+ # usage of QML, in which case the import count is 0.
+ if(qml_import_scanner_imports_count GREATER 0)
+ set(processed_names "")
+ math(EXPR last_index "${qml_import_scanner_imports_count} - 1")
+ foreach(index RANGE 0 ${last_index})
+ _qt_internal_parse_qml_imports_entry(entry ${index})
+
+ if("${entry_NAME}" STREQUAL "")
+ message(WARNING "No NAME at scan index ${index}: ${imports_file}")
+ continue()
+ endif()
+
+ # A plugin might have multiple entries (e.g. for different versions).
+ # Only copy it once.
+ if("${entry_NAME}" IN_LIST processed_names)
+ continue()
+ endif()
+ # Even if we skip this one, we don't need to process it again
+ list(APPEND processed_names ${entry_NAME})
+
+ if("${entry_PATH}" STREQUAL "" OR
+ "${entry_PLUGIN}" STREQUAL "" OR
+ "${entry_RELATIVEPATH}" STREQUAL "")
+ # These might be a valid QML module, but not have a plugin library.
+ # We only care about modules that have a plugin we need to copy.
+ continue()
+ endif()
+
+ if(arg_NO_QT_IMPORTS AND
+ "${entry_LINKTARGET}" MATCHES "${__QT_CMAKE_EXPORT_NAMESPACE}::")
+ continue()
+ endif()
+
+ # For installation, we want the qmldir file and its plugin. If the
+ # CMake project generating the plugin sets version details on its
+ # CMake target, we might have symlinks and version numbers in the
+ # file names, so account for those. There should never be plugin
+ # libraries for more than one QML module in the directory, so we
+ # shouldn't need to worry about matching plugins we don't want.
+ set(relative_qmldir "${arg_QML_DIR}/${entry_RELATIVEPATH}")
+ if("${CMAKE_INSTALL_PREFIX}" STREQUAL "")
+ set(install_qmldir "./${relative_qmldir}")
+ else()
+ set(install_qmldir "${CMAKE_INSTALL_PREFIX}/${relative_qmldir}")
+ endif()
+ set(dest_qmldir "${QT_DEPLOY_PREFIX}/${relative_qmldir}")
+ if(arg_BUNDLE)
+ if("${CMAKE_INSTALL_PREFIX}" STREQUAL "")
+ set(install_plugin "./${arg_PLUGINS_DIR}")
+ else()
+ set(install_plugin "${CMAKE_INSTALL_PREFIX}/${arg_PLUGINS_DIR}")
+ endif()
+ set(dest_plugin "${QT_DEPLOY_PREFIX}/${arg_PLUGINS_DIR}")
+ else()
+ set(install_plugin "${install_qmldir}")
+ set(dest_plugin "${dest_qmldir}")
+ endif()
+
+ file(INSTALL "${entry_PATH}/qmldir" DESTINATION "${install_qmldir}")
+
+ if(__QT_DEPLOY_POST_BUILD)
+ # We are being invoked as a post-build step. The plugin might
+ # not exist yet, so we can't even glob for it, let alone copy
+ # it. We know what its name should be though, so we can create
+ # a symlink to where it will eventually be, which will be enough
+ # to allow it to run from the build tree. It won't matter if
+ # the plugin gets updated later in the build, the symlink will
+ # still be pointing at the right location.
+ # In theory, this could be possible for any platform that
+ # supports symlinks (which all do in some form now, even
+ # Windows if the right permissions are set), but we only really
+ # expect to need this for macOS app bundles.
+ if(DEFINED __QT_DEPLOY_TARGET_${entry_LINKTARGET}_FILE)
+ set(source_file "${__QT_DEPLOY_TARGET_${entry_LINKTARGET}_FILE}")
+ get_filename_component(source_file_name "${source_file}" NAME)
+ set(final_destination "${dest_qmldir}/${source_file_name}")
+ else()
+ # TODO: This is inconsistent with what we do further down below for the
+ # installation case. There we file(GLOB) any files we find, whereas here we
+ # build the path manually, because the file might not exist yet.
+ # Ideally both cases should use neither file(GLOB) nor manual path building,
+ # and instead rely on available target information or qmldir information.
+ # Currently that is not possible because we don't have all targets exposed
+ # via the __QT_DEPLOY_TARGET_{target} mechanism, only those that are built as
+ # part of the current project, and the qmldir -> qmlimportscanner does print
+ # the full file path, because there is one qmldir, but possibly 2+ plugins
+ # (debug and release).
+ set(plugin_suffix "")
+ if(__QT_DEPLOY_ACTIVE_CONFIG STREQUAL "Debug")
+ string(APPEND plugin_suffix "${__QT_DEPLOY_QT_DEBUG_POSTFIX}")
+ endif()
+
+ set(source_file "${entry_PATH}/lib${entry_PLUGIN}${plugin_suffix}.dylib")
+ set(final_destination "${dest_qmldir}/lib${entry_PLUGIN}${plugin_suffix}.dylib")
+ endif()
+
+ message(STATUS "Symlinking: ${final_destination}")
+ file(CREATE_LINK
+ "${source_file}"
+ "${final_destination}"
+ SYMBOLIC
+ )
+
+ # We don't add this plugin to plugins_found because we don't
+ # actually make a copy of the plugin. We don't want the caller
+ # thinking they should further process what would still be the
+ # original plugin in the build tree.
+ continue()
+ endif()
+
+ # Deploy the plugin.
+ if(DEFINED __QT_DEPLOY_TARGET_${entry_LINKTARGET}_FILE)
+ set(files "${__QT_DEPLOY_TARGET_${entry_LINKTARGET}_FILE}")
+ else()
+ # We don't know the exact target file name. Fall back to file name heuristics.
+
+ # Construct a regular expression that matches the plugin's file name.
+ set(plugin_regex "^(.*/)?(lib)?${entry_PLUGIN}")
+ if(__QT_DEPLOY_QT_IS_MULTI_CONFIG_BUILD_WITH_DEBUG)
+ # If our application is a release build, do not match any debug suffix.
+ # If our application is a debug build, match exactly a debug suffix.
+ if(__QT_DEPLOY_ACTIVE_CONFIG STREQUAL "Debug")
+ string(APPEND plugin_regex "${__QT_DEPLOY_QT_DEBUG_POSTFIX}")
+ endif()
+ else()
+ # The Qt installation does only contain one build of the plugin. We match any
+ # possible debug suffix, or none.
+ string(APPEND plugin_regex ".*")
+ endif()
+ string(APPEND plugin_regex "\\.(so|dylib|dll)(\\.[0-9]+)*$")
+
+ file(GLOB files LIST_DIRECTORIES false "${entry_PATH}/*${entry_PLUGIN}*")
+ list(FILTER files INCLUDE REGEX "${plugin_regex}")
+ endif()
+ file(INSTALL ${files} DESTINATION "${install_plugin}" USE_SOURCE_PERMISSIONS)
+
+ get_filename_component(dest_plugin_abs "${dest_plugin}" ABSOLUTE)
+ if(__QT_DEPLOY_TOOL STREQUAL "GRD")
+ # Use the full plugin path for deployment. This is necessary for file(GRD) to
+ # resolve the dependencies of the plugins.
+ list(APPEND plugins_found ${files})
+ else()
+ # Use relative paths for the plugins. If we used full paths here, macdeployqt would
+ # modify the RPATHS of plugins in the Qt installation.
+ file(RELATIVE_PATH rel_path "${install_prefix_abs}" "${dest_plugin_abs}")
+ foreach(file IN LISTS files)
+ get_filename_component(filename "${file}" NAME)
+ list(APPEND plugins_found "${rel_path}/${filename}")
+ endforeach()
+ endif()
+
+ # Install runtime dependencies on Windows.
+ if(__QT_DEPLOY_SYSTEM_NAME STREQUAL "Windows")
+ foreach(file IN LISTS __QT_DEPLOY_TARGET_${entry_LINKTARGET}_RUNTIME_DLLS)
+ if(__QT_DEPLOY_VERBOSE)
+ message(STATUS "runtime dependency for QML plugin '${entry_PLUGIN}':")
+ endif()
+ file(INSTALL ${file} DESTINATION "${QT_DEPLOY_PREFIX}/${QT_DEPLOY_BIN_DIR}")
+ endforeach()
+ endif()
+
+ if(__QT_DEPLOY_TOOL STREQUAL "GRD" AND __QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH)
+ # The RPATHs of the installed plugins do not match Qt's original lib directory.
+ # We must set the RPATH to point to QT_DEPLOY_LIBDIR.
+ _qt_internal_get_rpath_origin(rpath_origin)
+ foreach(file_path IN LISTS files)
+ get_filename_component(file_name ${file_path} NAME)
+ file(RELATIVE_PATH rel_lib_dir "${dest_plugin}"
+ "${QT_DEPLOY_PREFIX}/${QT_DEPLOY_LIB_DIR}"
+ )
+ _qt_internal_set_rpath(
+ FILE "${dest_plugin}/${file_name}"
+ NEW_RPATH "${rpath_origin}/${rel_lib_dir}"
+ )
+ endforeach()
+ endif()
+
+ if(arg_BUNDLE)
+ # Actual plugin binaries will be in PlugIns, but qmldir files
+ # expect them to be in the same directory as themselves
+ # (i.e. under Resources/qml/...). Add a symlink at the place
+ # the qmldir expects the binary to be. This arrangement keeps
+ # binaries under PlugIns and non-binaries under Resources,
+ # which is required for code signing to work properly.
+ get_filename_component(dest_qmldir_abs "${dest_qmldir}" ABSOLUTE)
+ file(RELATIVE_PATH rel_path "${dest_qmldir_abs}" "${dest_plugin_abs}")
+ foreach(plugin_file IN LISTS files)
+ get_filename_component(filename "${plugin_file}" NAME)
+
+ set(final_destination "${dest_qmldir}/${filename}")
+ message(STATUS "Symlinking: ${final_destination}")
+ file(CREATE_LINK "${rel_path}/${filename}" "${final_destination}" SYMBOLIC)
+ endforeach()
+ endif()
+ endforeach()
+ endif()
+
+ set(${arg_PLUGINS_FOUND} ${plugins_found} PARENT_SCOPE)
+
+endfunction()
+
+function(_qt_internal_show_skip_qml_runtime_deploy_message)
+ # Don't show the message in static Qt builds, it can be misleading, because we still
+ # run qmlimportscanner / link the static qml plguins into the binary despite not having
+ # a qml deployment step.
+ if(__QT_DEPLOY_IS_SHARED_LIBS_BUILD)
+ message(STATUS "Skipping QML module deployment steps.")
+ endif()
+endfunction()