aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/Qt6QmlMacros.cmake
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/Qt6QmlMacros.cmake')
-rw-r--r--src/qml/Qt6QmlMacros.cmake4807
1 files changed, 3907 insertions, 900 deletions
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake
index 2a22831794..a64e2eea6b 100644
--- a/src/qml/Qt6QmlMacros.cmake
+++ b/src/qml/Qt6QmlMacros.cmake
@@ -1,693 +1,2912 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Q6QmlMacros
#
-set(__qt_qml_macros_module_base_dir "${CMAKE_CURRENT_LIST_DIR}")
+set(__qt_qml_macros_module_base_dir "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
+
+# Install support uses the CMAKE_INSTALL_xxxDIR variables. Include this here
+# so that it is more likely to get pulled in earlier at a higher level, and also
+# to avoid re-including it many times later
+include(GNUInstallDirs)
+_qt_internal_add_deploy_support("${CMAKE_CURRENT_LIST_DIR}/Qt6QmlDeploySupport.cmake")
-#
-# Create a Qml Module. Arguments:
-#
-# URI: Declares the module identifier of the module. The module identifier is
-# the (dotted URI notation) identifier for the module, which must match the
-# module's install path. (REQUIRED)
-#
-# VERSION: The module's version. (REQUIRED)
-#
-# TARGET_PATH: Overwrite the generated target path. By default the target path
-# is generated from the URI by replacing the '.' with a '/'. However, under
-# certain circumstance this may not be enough. Use this argument to provide
-# a replacement. (OPTIONAL)
-#
-# RESOURCE_PREFIX: Resource Prefix to be used when generating a static library.
-# When building a static library, the qmldir file is embedded into the library
-# using rcc. It is is also used by the Qt Quick Compiler to embed compiled
-# Qml files into a shared or static library. If none is supplied we will
-# generate the following prefix: /org.qt-project/imports/${target_path}.
-# (OPTIONAL)
-#
-# OUTPUT_DIRECTORY: If the module is not to be build under
-# ${CMAKE_CURRENT_BINARY_DIR}. This ensures the qmldir file is copied to the
-# right location. (OPTIONAL)
-#
-# INSTALL_LOCATION: Intended installation directory for this module. If no
-# value is supplied, the default installation path will be ${INSTALL_QMLDIR}.
-# (OPTIONAL).
-#
-# DO_NOT_INSTALL_METADATA: When present, will not install the supporting files.
-#
-# SOURCES: List of C++ sources. (OPTIONAL)
-#
-# DEPENDENCIES: List of QML Module dependencies and their versions. The module
-# and its version must be separated via a slash(/). E.g. QtQuick/2.0
-#
-# PAST_MAJOR_VERSIONS: List of past major versions this QML module was available
-# in. Ensures that the module can be imported when using these major versions.
-#
-# QML_FILES: List of Qml files. See qt6_target_qml_files for more information
-# on how to specify additional properties on qml files. (OPTIONAL)
-#
-# CLASSNAME: Provides the class name of the C++ plugin used by the module. This
-# information is required for all the QML modules that depend on a C++ plugin
-# for additional functionality. Qt Quick applications built with static
-# linking cannot resolve the module imports without this information.
-# (REQUIRED for static QML modules backed by C++ sources aka non-pure QML modules)
-#
-# DESIGNER_SUPPORTED: Specify this argument if the plugin is supported by Qt
-# Quick Designer. By default, the plugin will not be supported. (OPTIONAL)
-#
-# TYPEINFO: Path to a file which declares a type description file for the module
-# that can be read by QML tools such as Qt Creator to access information about
-# the types defined by the module's plugins. (OPTIONAL)
-#
-# IMPORTS: List of other Qml Modules that this module imports. A version can be
-# specified by appending it after a slash(/), e.g QtQuick/2.0. The minor
-# version may be omitted, e.g. QtQuick/2. Alternatively "auto" may be given
-# as version to forward the version the current module is being imported with,
-# e.g. QtQuick/auto. (OPTIONAL)
-#
-# OPTIONAL_IMPORTS: List of other Qml Modules that this module may import at
-# run-time. Those are not automatically imported by the QML engine when
-# importing the current module, but rather serve as hints to tools like
-# qmllint. Versions can be specified in the same as for IMPORT. (OPTIONAL)
-#
-# RESOURCE_EXPORT: In static builds, when Qml files are processed via the Qt
-# Quick Compiler generate a separate static library that will be linked in
-# as an Interface. Supply an output variable to perform any custom actions
-# on these extra generated targets.
-#
-# SKIP_TYPE_REGISTRATION: When present will cause the generated qmldir file
-# to not list any qml types. These are expected to be registered by the
-# c++ plugin code instead.
-#
-# PLUGIN_OPTIONAL: The plugin is marked as optional in the qmldir file. If the
-# type registration functions are already available by other means, typically
-# by linking a library proxied by the plugin, it won't be loaded.
-#
-# PURE_MODULE: The plugin does not take any C++ source files. A dummy class plugin cpp file will
-# be generated to ensure the module is found by the Qml engine.
-#
-# IMPORT_PATH: State that QML modules this one depends on may be found in the given import paths.
-#
-# This function is currently in Technical Preview.
-# It's signature and behavior might change.
function(qt6_add_qml_module target)
- set(args_optional
- GENERATE_QMLTYPES
- INSTALL_QMLTYPES
+ set(args_option
+ STATIC
+ SHARED
DESIGNER_SUPPORTED
- DO_NOT_INSTALL_METADATA
- SKIP_TYPE_REGISTRATION
- PLUGIN_OPTIONAL
- PURE_MODULE
+ FOLLOW_FOREIGN_VERSIONING
+ AUTO_RESOURCE_PREFIX
+ NO_PLUGIN
+ NO_PLUGIN_OPTIONAL
+ NO_CREATE_PLUGIN_TARGET
+ NO_GENERATE_PLUGIN_SOURCE
+ NO_GENERATE_QMLTYPES
+ NO_GENERATE_QMLDIR
+ NO_LINT
+ NO_CACHEGEN
+ NO_RESOURCE_TARGET_PATH
+ NO_IMPORT_SCAN
+ ENABLE_TYPE_COMPILER
+
+ # Used to mark modules as having static side effects (i.e. if they install an image provider)
+ __QT_INTERNAL_STATIC_MODULE
+ # Used to mark modules as being a system module that provides all builtins
+ __QT_INTERNAL_SYSTEM_MODULE
+ # Give the resource for the qmldir a unique name; TODO: Remove once we can
+ __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE
)
- if (QT_BUILDING_QT)
- list(APPEND args_optional DO_NOT_CREATE_TARGET)
- endif()
-
set(args_single
+ PLUGIN_TARGET
+ INSTALLED_PLUGIN_TARGET # Internal option only, it may be removed
+ OUTPUT_TARGETS
RESOURCE_PREFIX
URI
- TARGET_PATH
+ TARGET_PATH # Internal option only, it may be removed
VERSION
OUTPUT_DIRECTORY
- INSTALL_LOCATION
- CLASSNAME
+ CLASS_NAME
TYPEINFO
+ NAMESPACE
+ # TODO: We don't handle installation, warn if callers used these with the old
+ # API and eventually remove them once we have updated all other repos
RESOURCE_EXPORT
+ INSTALL_DIRECTORY
+ INSTALL_LOCATION
+ TYPE_COMPILER_NAMESPACE
+ QMLTC_EXPORT_DIRECTIVE
+ QMLTC_EXPORT_FILE_NAME
)
set(args_multi
- SOURCES
- QML_FILES
- IMPORTS
- IMPORT_PATH
- OPTIONAL_IMPORTS
- DEPENDENCIES
- PAST_MAJOR_VERSIONS
+ SOURCES
+ QML_FILES
+ RESOURCES
+ IMPORTS
+ IMPORT_PATH
+ OPTIONAL_IMPORTS
+ DEFAULT_IMPORTS
+ DEPENDENCIES
+ PAST_MAJOR_VERSIONS
)
- cmake_parse_arguments(arg
- "${args_optional}"
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ "${args_option}"
"${args_single}"
"${args_multi}"
- ${ARGN}
)
-
- if (NOT arg_URI)
- message(FATAL_ERROR "qt6_add_qml_module called without a module URI. Please specify one using the URI argument.")
+ if(arg_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "Unknown/unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
endif()
- if (NOT arg_VERSION)
- message(FATAL_ERROR "qt6_add_qml_module called without a module version. Please specify one using the VERSION argument.")
+ # Warn about options we no longer need/use (these were used by the internal
+ # targets and examples, but the logic has been shifted to
+ # qt_internal_add_qml_module() or left as a responsibility of the caller).
+ if(DEFINED arg_RESOURCE_EXPORT)
+ message(AUTHOR_WARNING
+ "RESOURCE_EXPORT will be ignored. This function does not handle "
+ "installation, which is what RESOURCE_EXPORT was previously used "
+ "for. Please update your project to install the target directly."
+ )
endif()
- if (NOT "${arg_VERSION}" MATCHES "[0-9]+\\.[0-9]+")
- message(FATAL_ERROR "qt6_add_qml_module called with an invalid version argument: '${arg_VERSION}'. Expected version style: VersionMajor.VersionMinor.")
+ if(DEFINED arg_INSTALL_DIRECTORY)
+ message(AUTHOR_WARNING
+ "INSTALL_DIRECTORY will be ignored. This function does not handle "
+ "installation, please update your project to install the target "
+ "directly."
+ )
endif()
- # If C++ sources were directly specified (not via qt_internal_add_qml_module), we assume the
- # user will provide a plugin.cpp file. Don't generate a dummy plugin.cpp file in this case.
- #
- # If no sources were specified or the plugin was marked as a pure QML module, generate a
- # dummy plugin.cpp file.
- if (arg_SOURCES OR NOT arg_PURE_MODULE)
- set(create_pure_qml_module_plugin FALSE)
- else()
- set(create_pure_qml_module_plugin TRUE)
+ if(DEFINED arg_INSTALL_LOCATION)
+ message(AUTHOR_WARNING
+ "INSTALL_LOCATION will be ignored. This function does not handle "
+ "installation, please update your project to install the target "
+ "directly."
+ )
endif()
- if (arg_DO_NOT_CREATE_TARGET AND NOT TARGET "${target}")
- message(FATAL_ERROR "qt6_add_qml_module called with DO_NOT_CREATE_TARGET, but the given target '${target}' is not a cmake target")
+ # Mandatory arguments
+ if (NOT arg_URI)
+ message(FATAL_ERROR
+ "Called without a module URI. Please specify one using the URI argument."
+ )
endif()
- if (arg_DO_NOT_CREATE_TARGET)
- get_target_property(target_type ${target} TYPE)
- if (target_type STREQUAL "STATIC_LIBRARY")
- set(is_static TRUE)
- elseif(target_type STREQUAL "MODULE_LIBRARY")
- set(is_static FALSE)
- else()
- message(FATAL_ERROR "qt6_add_qml_module called with DO_NOT_CREATE_TARGET, but target '${target}' is neither a static or a module library.")
- endif()
- else()
- # TODO: Creating a library here means we're missing creation of supporting .prl files,
- # as well as install(TARGET foo EXPORT bar) mapping, as opposed to when it's done
- # by qt_internal_add_plugin inside the qt_internal_add_qml_module call.
- if(NOT BUILD_SHARED_LIBS)
- add_library(${target} STATIC)
- set(is_static TRUE)
-
- # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables.
- _qt_internal_disable_static_default_plugins("${resource_target}")
- else()
- add_library(${target} MODULE)
- set(is_static FALSE)
- if(APPLE)
- # CMake defaults to using .so extensions for loadable modules, aka plugins,
- # but Qt plugins are actually suffixed with .dylib.
- set_property(TARGET "${target}" PROPERTY SUFFIX ".dylib")
- endif()
- if(WIN32)
- # CMake sets for Windows-GNU platforms the suffix "lib"
- set_property(TARGET "${target}" PROPERTY PREFIX "")
- endif()
- endif()
- _qt_internal_apply_strict_cpp("${target}")
+ if (NOT arg_VERSION)
+ set(arg_VERSION "254.254")
+ elseif ("${arg_VERSION}" MATCHES "^([0-9]+\\.[0-9]+)\\.[0-9]+$")
+ set(arg_VERSION "${CMAKE_MATCH_1}")
+ elseif (NOT "${arg_VERSION}" MATCHES "^[0-9]+\\.[0-9]+$")
+ message(FATAL_ERROR
+ "Called with an invalid version argument: '${arg_VERSION}'. "
+ "Expected version in the form: VersionMajor.VersionMinor."
+ )
endif()
+ # Other arguments and checking for invalid combinations
if (NOT arg_TARGET_PATH)
+ # NOTE: This will always be used for copying things to the build
+ # directory, but it will not be used for resource paths if
+ # NO_RESOURCE_TARGET_PATH was given.
string(REPLACE "." "/" arg_TARGET_PATH ${arg_URI})
endif()
- if(create_pure_qml_module_plugin)
- _qt_internal_create_dummy_qml_plugin("${target}" "${arg_URI}" arg_CLASSNAME)
- endif()
-
- if (ANDROID)
- # Adjust Qml plugin names on Android similar to qml_plugin.prf which calls
- # $$qt5LibraryTarget($$TARGET, "qml/$$TARGETPATH/").
- # Example plugin names:
- # qtdeclarative
- # TARGET_PATH: QtQml/Models
- # file name: libqml_QtQml_Models_modelsplugin_arm64-v8a.so
- # qtquickcontrols2
- # TARGET_PATH: QtQuick/Controls.2/Material
- # file name:
- # libqml_QtQuick_Controls.2_Material_qtquickcontrols2materialstyleplugin_arm64-v8a.so
- string(REPLACE "/" "_" android_plugin_name_infix_name "${arg_TARGET_PATH}")
-
- set(final_android_qml_plugin_name "qml_${android_plugin_name_infix_name}_${target}")
- set_target_properties(${target}
- PROPERTIES
- LIBRARY_OUTPUT_NAME "${final_android_qml_plugin_name}"
- )
+ if(arg_NO_PLUGIN AND DEFINED arg_PLUGIN_TARGET)
+ message(FATAL_ERROR
+ "NO_PLUGIN was specified, but PLUGIN_TARGET was also given. "
+ "At most one of these can be present."
+ )
endif()
- if (NOT arg_RESOURCE_PREFIX)
- set(arg_RESOURCE_PREFIX "/org.qt-project/imports")
- endif()
+ if (NOT arg_ENABLE_TYPE_COMPILER)
+ if (DEFINED arg_TYPE_COMPILER_NAMESPACE)
+ message(WARNING
+ "TYPE_COMPILER_NAMESPACE is set, but ENABLE_TYPE_COMPILER is not specified. "
+ "The TYPE_COMPILER_NAMESPACE value will be ignored."
+ )
+ endif()
- set(should_install "TRUE")
- if (NOT arg_INSTALL_LOCATION)
- message(AUTHOR_WARNING "No Qml install location provided for target ${target}."
- "Consider specifying the INSTALL_LOCATION option.")
- set(should_install "FALSE")
+ if (DEFINED arg_QMLTC_EXPORT_DIRECTIVE)
+ message(WARNING
+ "QMLTC_EXPORT_DIRECTIVE is set, but ENABLE_TYPE_COMPILER is not specified. "
+ "The QMLTC_EXPORT_DIRECTIVE value will be ignored."
+ )
+ endif()
+ if (DEFINED arg_QMLTC_EXPORT_FILE_NAME)
+ message(WARNING
+ "QMLTC_EXPORT_FILE_NAME is set, but ENABLE_TYPE_COMPILER is not specified. "
+ "The QMLTC_EXPORT_FILE_NAME will be ignored."
+ )
+ endif()
+ else()
+ if ((DEFINED arg_QMLTC_EXPORT_FILE_NAME) AND (NOT (DEFINED arg_QMLTC_EXPORT_DIRECTIVE)))
+ message(FATAL_ERROR
+ "Specifying a value for QMLTC_EXPORT_FILE_NAME also requires one for QMLTC_EXPORT_DIRECTIVE."
+ )
+ endif()
endif()
- if (DEFINED QT_WILL_INSTALL AND NOT QT_WILL_INSTALL
- AND NOT IS_ABSOLUTE "${arg_INSTALL_LOCATION}" AND QT_BUILD_DIR)
- set(arg_INSTALL_LOCATION "${QT_BUILD_DIR}/${arg_INSTALL_LOCATION}")
- endif()
+ set(is_executable FALSE)
+ if(TARGET ${target})
+ if(arg_STATIC OR arg_SHARED)
+ message(FATAL_ERROR
+ "Cannot use STATIC or SHARED keyword when passed an existing target (${target})"
+ )
+ endif()
- set_target_properties(${target}
- PROPERTIES
- QT_QML_MODULE_TARGET_PATH "${arg_TARGET_PATH}"
- QT_QML_MODULE_URI "${arg_URI}"
- QT_RESOURCE_PREFIX "${arg_RESOURCE_PREFIX}/${arg_TARGET_PATH}"
- QT_QML_MODULE_VERSION "${arg_VERSION}"
- QT_QML_MODULE_INSTALL_DIR "${arg_INSTALL_LOCATION}"
- QT_QML_MODULE_RESOURCE_EXPORT "${arg_RESOURCE_EXPORT}"
- )
+ # With CMake 3.17 and earlier, a source file's generated property isn't
+ # visible outside of the directory scope in which it is set. That can
+ # lead to build errors for things like type registration due to CMake
+ # thinking nothing will create a missing file on the first run. With
+ # CMake 3.18 or later, we can force that visibility. Policy CMP0118
+ # added in CMake 3.20 should have made this unnecessary, but we can't
+ # rely on that because the user project controls what it is set to at
+ # the point where it matters, which is the end of the target's
+ # directory scope (hence we can't even test for it here).
+ get_target_property(source_dir ${target} SOURCE_DIR)
+ if(NOT source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR AND
+ CMAKE_VERSION VERSION_LESS "3.18")
+ message(WARNING
+ "qt6_add_qml_module() is being called in a different "
+ "directory scope to the one in which the target \"${target}\" "
+ "was created. CMake 3.18 or later is required to generate a "
+ "project robustly for this scenario, but you are using "
+ "CMake ${CMAKE_VERSION}. Ideally, qt6_add_qml_module() should "
+ "only be called from the same scope as the one the target was "
+ "created in to avoid dependency and visibility problems."
+ )
+ endif()
- if (NOT DO_NOT_CREATE_TARGET)
- if (arg_OUTPUT_DIRECTORY)
- set_target_properties(${target}
- PROPERTIES
- LIBRARY_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
- ARCHIVE_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
+ get_target_property(backing_target_type ${target} TYPE)
+ get_target_property(is_android_executable "${target}" _qt_is_android_executable)
+ if (backing_target_type STREQUAL "EXECUTABLE" OR is_android_executable)
+ if(DEFINED arg_PLUGIN_TARGET)
+ message(FATAL_ERROR
+ "A QML module with an executable as its backing target "
+ "cannot have a plugin."
+ )
+ endif()
+ if(arg_NO_CREATE_PLUGIN_TARGET)
+ message(WARNING
+ "A QML module with an executable as its backing target "
+ "cannot have a plugin. The NO_CREATE_PLUGIN_TARGET option "
+ "has no effect and should be removed."
+ )
+ endif()
+ set(arg_NO_PLUGIN TRUE)
+ set(lib_type "")
+ set(is_executable TRUE)
+ elseif(arg_NO_RESOURCE_TARGET_PATH)
+ message(FATAL_ERROR
+ "NO_RESOURCE_TARGET_PATH cannot be used for a backing target "
+ "that is not an executable"
)
- elseif (should_install)
- install(TARGETS ${target}
- DESTINATION "${arg_INSTALL_LOCATION}"
+ elseif(backing_target_type STREQUAL "STATIC_LIBRARY")
+ set(lib_type STATIC)
+ elseif(backing_target_type MATCHES "(SHARED|MODULE)_LIBRARY")
+ set(lib_type SHARED)
+ else()
+ message(FATAL_ERROR "Unsupported backing target type: ${backing_target_type}")
+ endif()
+ else()
+ if(arg_STATIC AND arg_SHARED)
+ message(FATAL_ERROR
+ "Both STATIC and SHARED specified, at most one can be given"
+ )
+ endif()
+
+ if(arg_NO_RESOURCE_TARGET_PATH)
+ message(FATAL_ERROR
+ "NO_RESOURCE_TARGET_PATH can only be provided when an existing "
+ "executable target is passed in as the backing target"
)
endif()
- endif()
- if (arg_OUTPUT_DIRECTORY)
- set(target_output_dir ${arg_OUTPUT_DIRECTORY})
- else()
- if(is_static)
- get_target_property(target_output_dir ${target} ARCHIVE_OUTPUT_DIRECTORY)
+ # Explicit arguments take precedence, otherwise default to using the same
+ # staticality as what Qt was built with. This follows the already
+ # established default behavior for building ordinary Qt plugins.
+ # We don't allow the standard CMake BUILD_SHARED_LIBS variable to control
+ # the default because that can lead to different defaults depending on
+ # whether you build with a separate backing target or not.
+ if(arg_STATIC)
+ set(lib_type STATIC)
+ elseif(arg_SHARED)
+ set(lib_type SHARED)
+ elseif(QT6_IS_SHARED_LIBS_BUILD)
+ set(lib_type SHARED)
else()
- get_target_property(target_output_dir ${target} LIBRARY_OUTPUT_DIRECTORY)
+ set(lib_type STATIC)
endif()
endif()
- if (arg_SKIP_TYPE_REGISTRATION)
- set_target_properties(${target} PROPERTIES QT_QML_MODULE_SKIP_TYPE_REGISTRATION TRUE)
+ if(arg_NO_PLUGIN)
+ # Simplifies things a bit further below
+ set(arg_PLUGIN_TARGET "")
+ elseif(NOT DEFINED arg_PLUGIN_TARGET)
+ if(arg_NO_CREATE_PLUGIN_TARGET)
+ # We technically could allow this and rely on the project using the
+ # default plugin target name, but not doing so gives us the
+ # flexibility to potentially change that default later if needed.
+ message(FATAL_ERROR
+ "PLUGIN_TARGET must also be provided when NO_CREATE_PLUGIN_TARGET "
+ "is used. If you want to disable creating a plugin altogether, "
+ "use the NO_PLUGIN option instead."
+ )
+ endif()
+ set(arg_PLUGIN_TARGET ${target}plugin)
endif()
-
- if (arg_SOURCES)
- target_sources(${target} PRIVATE ${arg_SOURCES})
+ if(arg_NO_CREATE_PLUGIN_TARGET AND arg_PLUGIN_TARGET STREQUAL target AND NOT TARGET ${target})
+ message(FATAL_ERROR
+ "PLUGIN_TARGET is the same as the backing target, which is allowed, "
+ "but NO_CREATE_PLUGIN_TARGET was also given and the target does not "
+ "exist. Either ensure the target is already created or do not "
+ "specify NO_CREATE_PLUGIN_TARGET."
+ )
endif()
-
- if (arg_IMPORT_PATH)
- set_target_properties(${target} PROPERTIES QT_QML_IMPORT_PATH "${arg_IMPORT_PATH}")
+ if(NOT arg_INSTALLED_PLUGIN_TARGET)
+ set(arg_INSTALLED_PLUGIN_TARGET ${arg_PLUGIN_TARGET})
endif()
- # Insert the plugins URI into its meta data to enable usage
- # of static plugins in QtDeclarative (like in mkspecs/features/qml_plugin.prf).
- set_property(TARGET "${target}" APPEND PROPERTY AUTOMOC_MOC_OPTIONS "-Muri=${arg_URI}")
-
- # Tracker so we can generate unique resource names for multiple
- # target_qml_files() calls.
- set_target_properties(${target} PROPERTIES QT6_QML_MODULE_ADD_QML_FILES_COUNT 1)
-
- # Generate qmldir file
- set(qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/qmldir")
- set_target_properties(${target} PROPERTIES QT_QML_MODULE_QMLDIR_FILE ${qmldir_file})
- set(qmldir_file_contents "module ${arg_URI}\n")
+ set(no_gen_source)
+ if(arg_NO_GENERATE_PLUGIN_SOURCE)
+ set(no_gen_source NO_GENERATE_PLUGIN_SOURCE)
+ endif()
- if (arg_PLUGIN_OPTIONAL)
- string(APPEND qmldir_file_contents "optional plugin ${target}${QT_LIBINFIX}\n")
+ if(arg_OUTPUT_DIRECTORY)
+ get_filename_component(arg_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
+ ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}"
+ )
else()
- string(APPEND qmldir_file_contents "plugin ${target}${QT_LIBINFIX}\n")
+ if("${QT_QML_OUTPUT_DIRECTORY}" STREQUAL "")
+ set(arg_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+ # For libraries, we assume/require that the source directory
+ # structure is consistent with the target path. For executables,
+ # the source directory will usually not reflect the target path
+ # and the project will often expect to be able to use resource
+ # paths that don't include the target path (they need the
+ # NO_RESOURCE_TARGET_PATH option if they do that). Tooling always
+ # needs the target path in the file system though, so the output
+ # directory should always have it. Handle the special case for
+ # executables to ensure this is what we get.
+ if(is_executable)
+ string(APPEND arg_OUTPUT_DIRECTORY "/${arg_TARGET_PATH}")
+ endif()
+ else()
+ if(NOT IS_ABSOLUTE "${QT_QML_OUTPUT_DIRECTORY}")
+ message(FATAL_ERROR
+ "QT_QML_OUTPUT_DIRECTORY must be an absolute path, but given: "
+ "${QT_QML_OUTPUT_DIRECTORY}"
+ )
+ endif()
+ # This inherently does what we want for libraries and executables
+ set(arg_OUTPUT_DIRECTORY ${QT_QML_OUTPUT_DIRECTORY}/${arg_TARGET_PATH})
+ endif()
endif()
- if (arg_CLASSNAME)
- string(APPEND qmldir_file_contents "classname ${arg_CLASSNAME}\n")
+ # Sanity check that we are not trying to have two different QML modules use
+ # the same output directory.
+ get_property(dirs GLOBAL PROPERTY _qt_all_qml_output_dirs)
+ if(dirs)
+ list(FIND dirs "${arg_OUTPUT_DIRECTORY}" index)
+ if(NOT index EQUAL -1)
+ get_property(qml_targets GLOBAL PROPERTY _qt_all_qml_targets)
+ list(GET qml_targets ${index} other_target)
+ message(FATAL_ERROR
+ "Output directory for target \"${target}\" is already used by "
+ "another QML module (target \"${other_target}\"). "
+ "Output directory is:\n ${arg_OUTPUT_DIRECTORY}\n"
+ )
+ endif()
endif()
- if (arg_DESIGNER_SUPPORTED)
- string(APPEND qmldir_file_contents "designersupported\n")
+ set_property(GLOBAL APPEND PROPERTY _qt_all_qml_uris ${arg_URI})
+ set_property(GLOBAL APPEND PROPERTY _qt_all_qml_output_dirs ${arg_OUTPUT_DIRECTORY})
+ set_property(GLOBAL APPEND PROPERTY _qt_all_qml_targets ${target})
+
+ if(NOT arg_CLASS_NAME AND TARGET "${arg_PLUGIN_TARGET}")
+ get_target_property(class_name ${arg_PLUGIN_TARGET} QT_PLUGIN_CLASS_NAME)
+ if(class_name)
+ set(arg_CLASS_NAME)
+ endif()
endif()
- if (arg_TYPEINFO)
- string(APPEND qmldir_file_contents "typeinfo ${arg_TYPEINFO}\n")
+ if(NOT arg_CLASS_NAME)
+ _qt_internal_compute_qml_plugin_class_name_from_uri("${arg_URI}" arg_CLASS_NAME)
+ endif()
+
+ if(TARGET ${target})
+ if(arg_PLUGIN_TARGET STREQUAL target)
+ # Insert the plugin's URI into its meta data to enable usage
+ # of static plugins in QtDeclarative (like in mkspecs/features/qml_plugin.prf).
+ set_property(TARGET ${target} APPEND PROPERTY
+ AUTOMOC_MOC_OPTIONS "-Muri=${arg_URI}"
+ )
+ endif()
else()
- # This always need to be written out since at the moment we have cases
- # where qmltyperegistrar is not run with the plugin but on a module
- # e.g: src/qml generates the qmltypes for src/imports/qtqml.
- # When this has been fixed/standardized we should move this to
- # qt6_qml_type_registration() so that it is written out when the
- # plugins.qmltypes is actually generated.
- string(APPEND qmldir_file_contents "typeinfo plugins.qmltypes\n")
+ if(arg_PLUGIN_TARGET STREQUAL target)
+ set(conditional_args ${no_gen_source})
+ if(arg_NAMESPACE)
+ list(APPEND conditional_args NAMESPACE ${arg_NAMESPACE})
+ endif()
+ qt6_add_qml_plugin(${target}
+ ${lib_type}
+ OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
+ URI ${arg_URI}
+ CLASS_NAME ${arg_CLASS_NAME}
+ ${conditional_args}
+ )
+ else()
+ qt6_add_library(${target} ${lib_type})
+ endif()
endif()
- macro(_add_imports imports import_string)
- foreach(import IN LISTS ${imports})
+ if(NOT target STREQUAL Qml)
+ target_link_libraries(${target} PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Qml)
+ endif()
+
+ if(NOT arg_TYPEINFO AND NOT arg_NO_GENERATE_QMLTYPES)
+ set(arg_TYPEINFO ${target}.qmltypes)
+ endif()
+
+ foreach(import_set IN ITEMS IMPORTS OPTIONAL_IMPORTS DEFAULT_IMPORTS)
+ foreach(import IN LISTS arg_${import_set})
string(FIND ${import} "/" slash_position REVERSE)
if (slash_position EQUAL -1)
- string(APPEND qmldir_file_contents "${import_string} ${import}\n")
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_${import_set} "${import}"
+ )
else()
string(SUBSTRING ${import} 0 ${slash_position} import_module)
math(EXPR slash_position "${slash_position} + 1")
string(SUBSTRING ${import} ${slash_position} -1 import_version)
- if (import_version MATCHES "[0-9]+\\.[0-9]+" OR import_version MATCHES "[0-9]+")
- string(APPEND qmldir_file_contents "${import_string} ${import_module} ${import_version}\n")
- elseif (import_version MATCHES "auto")
- string(APPEND qmldir_file_contents "${import_string} ${import_module} auto\n")
+ if (import_version MATCHES "^([0-9]+(\\.[0-9]+)?|auto)$")
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_${import_set} "${import_module} ${import_version}"
+ )
else()
- message(FATAL_ERROR "Invalid module ${import_string} version number. Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'.")
+ message(FATAL_ERROR
+ "Invalid module ${import} version number. "
+ "Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'."
+ )
endif()
endif()
endforeach()
- endmacro()
-
- _add_imports(arg_IMPORTS "import")
- _add_imports(arg_OPTIONAL_IMPORTS "optional import")
+ endforeach()
foreach(dependency IN LISTS arg_DEPENDENCIES)
string(FIND ${dependency} "/" slash_position REVERSE)
if (slash_position EQUAL -1)
- string(APPEND qmldir_file_contents "depends ${dependency}\n")
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_DEPENDENCIES "${dependency}"
+ )
else()
- string(SUBSTRING ${dependency} 0 ${slash_position} dep_module)
+ string(SUBSTRING ${dependency} 0 ${slash_position} dep_module_uri)
math(EXPR slash_position "${slash_position} + 1")
string(SUBSTRING ${dependency} ${slash_position} -1 dep_version)
- if (dep_version MATCHES "[0-9]+\\.[0-9]+" OR dep_version MATCHES "[0-9]+")
- string(APPEND qmldir_file_contents "depends ${dep_module} ${dep_version}\n")
- elseif (dep_version MATCHES "auto")
- string(APPEND qmldir_file_contents "depends ${dep_module} auto\n")
+ if (dep_version MATCHES "^([0-9]+(\\.[0-9]+)?|auto)$")
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_DEPENDENCIES "${dep_module_uri} ${dep_version}"
+ )
else()
- message(FATAL_ERROR "Invalid module dependency version number. Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'.")
+ message(FATAL_ERROR
+ "Invalid module dependency version number. "
+ "Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'."
+ )
endif()
endif()
endforeach()
+ _qt_internal_collect_qml_module_dependencies(${target})
- _qt_internal_qmldir_defer_file(WRITE "${qmldir_file}" "${qmldir_file_contents}")
-
- # Process qml files
- if (arg_QML_FILES)
- qt6_target_qml_files(${target} FILES ${arg_QML_FILES})
- endif()
-
- # Embed qmldir in static builds
- if (is_static)
- # The qmldir resource name needs to match the one generated by qmake's qml_module.prf, to
- # ensure that all Q_INIT_RESOURCE(resource_name) calls in Qt code don't lead to undefined
- # symbol errors when linking an application project.
- # The Q_INIT_RESOURCE() calls are not strictly necessary anymore because the CMake Qt
- # build passes around the compiled resources as object files.
- # These object files have global initiliazers that don't get discared when linked into
- # an application (as opposed to when the resource libraries were embedded into the static
- # libraries when Qt was built with qmake).
- # The reason to match the naming is to ensure that applications link successfully regardless
- # if Qt was built with CMake or qmake, while the build system transition phase is still
- # happening.
- string(REPLACE "/" "_" qmldir_resource_name ${arg_TARGET_PATH})
- string(PREPEND qmldir_resource_name "qmake_")
-
- set_source_files_properties("${qmldir_file}"
- PROPERTIES QT_RESOURCE_ALIAS "qmldir"
+ if(arg_AUTO_RESOURCE_PREFIX)
+ if(arg_RESOURCE_PREFIX)
+ message(FATAL_ERROR
+ "Both RESOURCE_PREFIX and AUTO_RESOURCE_PREFIX are specified for ${target}. "
+ "You can only have one."
+ )
+ else()
+ set(arg_RESOURCE_PREFIX "/qt/qml")
+ message(DEPRECATION "AUTO_RESOURCE_PREFIX is deprecated. "
+ "Please use the qt_policy(SET) command to set the QTP0001 policy, "
+ "or use the qt_standard_project_setup() command to set your preferred "
+ "REQUIRES to get the preferred behavior. "
+ "Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details.")
+ endif()
+ elseif(arg_RESOURCE_PREFIX)
+ _qt_internal_canonicalize_resource_path("${arg_RESOURCE_PREFIX}" arg_RESOURCE_PREFIX)
+ elseif(arg_NO_RESOURCE_TARGET_PATH)
+ # Suppress the warning if NO_RESOURCE_TARGET_PATH is given.
+ # In that case, we assume the user knows what they want.
+ set(arg_RESOURCE_PREFIX "/")
+ else()
+ __qt_internal_setup_policy(QTP0001 "6.5.0"
+"':/qt/qml/' is the default resource prefix for QML modules. \
+Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details."
)
+ qt6_policy(GET QTP0001 use_auto_prefix_policy)
+ if ("${use_auto_prefix_policy}" STREQUAL "NEW")
+ set(arg_RESOURCE_PREFIX "/qt/qml")
+ else()
+ set(arg_RESOURCE_PREFIX "/")
+ endif()
+ endif()
+
+ if(arg_NO_RESOURCE_TARGET_PATH)
+ set(qt_qml_module_resource_prefix "${arg_RESOURCE_PREFIX}")
+ else()
+ if(arg_RESOURCE_PREFIX STREQUAL "/") # Checked so we prevent double-slash
+ set(qt_qml_module_resource_prefix "/${arg_TARGET_PATH}")
+ else()
+ set(qt_qml_module_resource_prefix "${arg_RESOURCE_PREFIX}/${arg_TARGET_PATH}")
+ endif()
+ endif()
+
+ set_target_properties(${target} PROPERTIES
+ QT_QML_MODULE_NO_LINT "${arg_NO_LINT}"
+ QT_QML_MODULE_NO_CACHEGEN "${arg_NO_CACHEGEN}"
+ QT_QML_MODULE_NO_GENERATE_QMLDIR "${arg_NO_GENERATE_QMLDIR}"
+ QT_QML_MODULE_NO_PLUGIN "${arg_NO_PLUGIN}"
+ QT_QML_MODULE_NO_PLUGIN_OPTIONAL "${arg_NO_PLUGIN_OPTIONAL}"
+ QT_QML_MODULE_NO_IMPORT_SCAN "${arg_NO_IMPORT_SCAN}"
+ _qt_qml_module_follow_foreign_versioning "${arg_FOLLOW_FOREIGN_VERSIONING}"
+ QT_QML_MODULE_URI "${arg_URI}"
+ QT_QML_MODULE_TARGET_PATH "${arg_TARGET_PATH}"
+ QT_QML_MODULE_VERSION "${arg_VERSION}"
+ QT_QML_MODULE_CLASS_NAME "${arg_CLASS_NAME}"
+
+ QT_QML_MODULE_PLUGIN_TARGET "${arg_PLUGIN_TARGET}"
+ QT_QML_MODULE_INSTALLED_PLUGIN_TARGET "${arg_INSTALLED_PLUGIN_TARGET}"
+
+ # Also Save the PLUGIN_TARGET values in a separate property to circumvent
+ # https://gitlab.kitware.com/cmake/cmake/-/issues/21484 when exporting the properties
+ _qt_qml_module_plugin_target "${arg_PLUGIN_TARGET}"
+ _qt_qml_module_installed_plugin_target "${arg_INSTALLED_PLUGIN_TARGET}"
+
+ QT_QML_MODULE_DESIGNER_SUPPORTED "${arg_DESIGNER_SUPPORTED}"
+ QT_QML_MODULE_IS_STATIC "${arg___QT_INTERNAL_STATIC_MODULE}"
+ QT_QML_MODULE_IS_SYSTEM "${arg___QT_INTERNAL_SYSTEM_MODULE}"
+ QT_QML_MODULE_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
+ QT_QML_MODULE_RESOURCE_PREFIX "${qt_qml_module_resource_prefix}"
+ QT_QML_MODULE_PAST_MAJOR_VERSIONS "${arg_PAST_MAJOR_VERSIONS}"
+
+ # TODO: Check how this is used by qt6_android_generate_deployment_settings()
+ QT_QML_IMPORT_PATH "${arg_IMPORT_PATH}"
+ )
- qt6_add_resources(${target} ${qmldir_resource_name}
- FILES "${qmldir_file}"
- OUTPUT_TARGETS resource_targets
+ if(arg_TYPEINFO)
+ set_target_properties(${target} PROPERTIES
+ QT_QML_MODULE_TYPEINFO "${arg_TYPEINFO}"
)
+ endif()
- if (resource_targets AND arg_RESOURCE_EXPORT)
- install(TARGETS ${resource_targets}
- EXPORT "${arg_RESOURCE_EXPORT}"
- DESTINATION "${arg_INSTALL_LOCATION}"
- )
+ # Executables don't have a plugin target, so no need to export the properties.
+ if(NOT backing_target_type STREQUAL "EXECUTABLE" AND NOT is_android_executable)
+ set_property(TARGET ${target} APPEND PROPERTY
+ EXPORT_PROPERTIES _qt_qml_module_plugin_target _qt_qml_module_installed_plugin_target
+ )
+ endif()
- # When building a static Qt, we need to record information about the compiled resource
- # object files to embed them into .prl files.
- if(COMMAND qt_internal_record_rcc_object_files)
- qt_internal_record_rcc_object_files(
- "${target}" "${resource_targets}" INSTALL_LOCATION "${arg_INSTALL_LOCATION}")
- endif()
+ if(NOT arg_NO_GENERATE_QMLTYPES)
+ set(type_registration_extra_args "")
+ if(arg_NAMESPACE)
+ list(APPEND type_registration_extra_args NAMESPACE ${arg_NAMESPACE})
endif()
+ set_target_properties(${target} PROPERTIES _qt_internal_has_qmltypes TRUE)
+ _qt_internal_qml_type_registration(${target} ${type_registration_extra_args})
else()
- # Copy QMLDIR file to build directory
- add_custom_command(TARGET ${target} POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy
- ${qmldir_file}
- ${target_output_dir}
+ set_target_properties(${target} PROPERTIES _qt_internal_has_qmltypes FALSE)
+ endif()
+
+ set(output_targets)
+
+ if(NOT arg_NO_GENERATE_QMLDIR)
+ _qt_internal_target_generate_qmldir(${target})
+ set_source_files_properties(${arg_OUTPUT_DIRECTORY}/qmldir
+ PROPERTIES GENERATED TRUE
)
- # Install QMLDIR file
- if (NOT DO_NOT_INSTALL_METADATA AND should_install)
- install(FILES ${qmldir_file}
- DESTINATION "${arg_INSTALL_LOCATION}"
- )
+ if(${arg___QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE})
+ # TODO: Make this the default and remove the option
+ string(REPLACE "/" "_" qmldir_resource_name "${target}_qmldir_${arg_TARGET_PATH}")
+ else()
+ # Embed qmldir in qrc. The following comments relate mostly to Qt5->6 transition.
+ # The requirement to keep the same resource name might no longer apply, but it doesn't
+ # currently appear to cause any hinderance to keep it.
+ # The qmldir resource name needs to match the one generated by qmake's qml_module.prf,
+ # to ensure that all Q_INIT_RESOURCE(resource_name) calls in Qt code don't lead to
+ # undefined symbol errors when linking an application project.
+ # The Q_INIT_RESOURCE() calls are not strictly necessary anymore because the CMake Qt
+ # build passes around the compiled resources as object files.
+ # These object files have global initiliazers that don't get discared when linked into
+ # an application (as opposed to when the resource libraries were embedded into the
+ # static libraries when Qt was built with qmake).
+ # The reason to match the naming is to ensure that applications link successfully
+ # regardless if Qt was built with CMake or qmake, while the build system transition
+ # phase is still happening.
+ string(REPLACE "/" "_" qmldir_resource_name "qmake_${arg_TARGET_PATH}")
endif()
- endif()
- # Install and Copy plugin.qmltypes if exists
- set(target_plugin_qmltypes "${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes")
+ # The qmldir file ALWAYS has to be under the target path, even in the
+ # resources. If it isn't, an explicit import can't find it. We need a
+ # second copy NOT under the target path if NO_RESOURCE_TARGET_PATH is
+ # given so that the implicit import will work.
+ set(prefixes "${qt_qml_module_resource_prefix}")
+ if(arg_NO_RESOURCE_TARGET_PATH)
+ # The above prefixes item won't include the target path, so add a
+ # second one that does.
+ if(qt_qml_module_resource_prefix STREQUAL "/")
+ list(APPEND prefixes "/${arg_TARGET_PATH}")
+ else()
+ list(APPEND prefixes "${qt_qml_module_resource_prefix}/${arg_TARGET_PATH}")
+ endif()
+ endif()
+ set_source_files_properties(${arg_OUTPUT_DIRECTORY}/qmldir
+ PROPERTIES QT_RESOURCE_ALIAS "qmldir"
+ )
- # For an in-source build, ensure that the file is not the one that was generated by
- # qt6_qml_type_registration.
- get_target_property(target_binary_dir ${target} BINARY_DIR)
- set(generated_marker_file "${target_binary_dir}/.generated/plugins.qmltypes")
+ foreach(prefix IN LISTS prefixes)
+ set(resource_targets)
+ qt6_add_resources(${target} ${qmldir_resource_name}
+ FILES ${arg_OUTPUT_DIRECTORY}/qmldir
+ PREFIX "${prefix}"
+ OUTPUT_TARGETS resource_targets
+ )
- if (EXISTS "${target_plugin_qmltypes}" AND NOT EXISTS "${generated_marker_file}")
- set_target_properties(${target}
- PROPERTIES QT_QML_MODULE_PLUGIN_TYPES_FILE "${target_plugin_qmltypes}"
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_qmldir_resource_name "${qmldir_resource_name}")
+ set_property(TARGET ${target} APPEND PROPERTY
+ _qt_qml_module_sanitized_resource_names "${sanitized_qmldir_resource_name}")
+
+ list(APPEND output_targets ${resource_targets})
+ # If we are adding the same file twice, we need a different resource
+ # name for the second one. It has the same QT_RESOURCE_ALIAS but a
+ # different prefix, so we can't put it in the same resource.
+ string(APPEND qmldir_resource_name "_copy")
+ endforeach()
+ endif()
+
+ if(NOT arg_NO_PLUGIN AND NOT arg_NO_CREATE_PLUGIN_TARGET)
+ # This also handles the case where ${arg_PLUGIN_TARGET} already exists,
+ # including where it is the same as ${target}. If ${arg_PLUGIN_TARGET}
+ # already exists, it will update the necessary things that are specific
+ # to qml plugins.
+ if(TARGET ${arg_PLUGIN_TARGET})
+ set(plugin_args "")
+ else()
+ set(plugin_args ${lib_type})
+ endif()
+ list(APPEND plugin_args ${no_gen_source})
+ if(arg_NAMESPACE)
+ list(APPEND plugin_args NAMESPACE ${arg_NAMESPACE})
+ endif()
+ qt6_add_qml_plugin(${arg_PLUGIN_TARGET}
+ ${plugin_args}
+ OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
+ BACKING_TARGET ${target}
+ CLASS_NAME ${arg_CLASS_NAME}
)
+ endif()
- _qt_internal_qmldir_defer_file(APPEND "${qmldir_file}" "typeinfo plugins.qmltypes\n")
+ if(TARGET "${arg_PLUGIN_TARGET}" AND NOT arg_PLUGIN_TARGET STREQUAL target)
+ target_link_libraries(${arg_PLUGIN_TARGET} PRIVATE ${target})
+ endif()
- if (NOT arg_DO_NOT_INSTALL_METADATA AND should_install)
- install(FILES "${target_plugin_qmltypes}"
- DESTINATION "${arg_INSTALL_LOCATION}"
- )
+ target_sources(${target} PRIVATE ${arg_SOURCES})
+
+ # QML tooling might need to map build dir paths to source dir paths. Create
+ # a mapping file before qt6_target_qml_sources() to be able to use it
+ _qt_internal_qml_map_build_files(${target} ${qt_qml_module_resource_prefix} dir_map_qrc)
+ # use different property from _qt_generated_qrc_files since this qrc is
+ # special (and is not a real resource file)
+ set_property(TARGET ${target} APPEND PROPERTY _qt_qml_meta_qrc_files "${dir_map_qrc}")
+
+ set(cache_target)
+ qt6_target_qml_sources(${target}
+ __QT_INTERNAL_FORCE_DEFER_QMLDIR
+ QML_FILES ${arg_QML_FILES}
+ RESOURCES ${arg_RESOURCES}
+ OUTPUT_TARGETS cache_target
+ PREFIX "${qt_qml_module_resource_prefix}"
+ )
+ list(APPEND output_targets ${cache_target})
+
+ # Build an init object library for static plugins and propagate it along with the plugin
+ # target.
+ # TODO: Figure out if we can move this code block into qt_add_qml_plugin. Need to consider
+ # various corner cases.
+ # QTBUG-96937
+ if(TARGET "${arg_PLUGIN_TARGET}")
+ get_target_property(plugin_lib_type ${arg_PLUGIN_TARGET} TYPE)
+ if(plugin_lib_type STREQUAL "STATIC_LIBRARY")
+ __qt_internal_add_static_plugin_init_object_library(
+ "${arg_PLUGIN_TARGET}" plugin_init_target)
+ list(APPEND output_targets ${plugin_init_target})
+
+ __qt_internal_propagate_object_library("${arg_PLUGIN_TARGET}" "${plugin_init_target}")
endif()
-
- add_custom_command(TARGET ${target} POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy
- ${target_plugin_qmltypes}
- ${target_output_dir}
- )
endif()
- # Copy/Install type info file
- if (EXISTS ${arg_TYPEINFO})
- if (NOT arg_DO_NOT_INSTALL_METADATA AND should_install)
- install(FILES "${arg_TYPEINFO}"
- DESTINATION "${arg_INSTALL_LOCATION}"
+ if(NOT arg_NO_GENERATE_QMLDIR)
+ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0")
+ # Defer the write to allow more qml files to be added later by calls to
+ # qt6_target_qml_sources(). We wrap the deferred call with EVAL CODE
+ # so that ${target} is evaluated now rather than the end of the scope.
+ # We also delay target finalization until after our deferred write
+ # because the qmldir file must be written before any finalizer
+ # might call qt_import_qml_plugins().
+ cmake_language(EVAL CODE
+ "cmake_language(DEFER ID_VAR write_id CALL _qt_internal_write_deferred_qmldir_file ${target})"
)
+ _qt_internal_delay_finalization_until_after(${write_id})
+ else()
+ # Can't defer the write, have to do it now
+ _qt_internal_write_deferred_qmldir_file(${target})
endif()
+ endif()
- add_custom_command(TARGET ${target} POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy
- ${arg_TYPEINFO}
- ${target_output_dir}
+ if (arg_ENABLE_TYPE_COMPILER)
+ if (DEFINED arg_TYPE_COMPILER_NAMESPACE AND NOT $<STREQUAL:"${arg_TYPE_COMPILER_NAMESPACE}","">)
+ set(qmltc_namespace ${arg_TYPE_COMPILER_NAMESPACE})
+ else()
+ string(REPLACE "." "::" qmltc_namespace "${arg_URI}")
+ endif()
+ _qt_internal_target_enable_qmltc(${target}
+ QML_FILES ${arg_QML_FILES}
+ IMPORT_PATHS ${arg_IMPORT_PATH}
+ NAMESPACE ${qmltc_namespace}
+ EXPORT_MACRO_NAME ${arg_QMLTC_EXPORT_DIRECTIVE}
+ EXPORT_FILE_NAME ${arg_QMLTC_EXPORT_FILE_NAME}
+ MODULE ${arg_URI}
)
endif()
- if (arg_INSTALL_QMLTYPES)
- set_target_properties(${target} PROPERTIES QT_QML_MODULE_INSTALL_QMLTYPES TRUE)
- if (arg_INSTALL_LOCATION)
- get_target_property(qml_module_install_dir ${target} QT_QML_MODULE_INSTALL_DIR)
- if (NOT qml_module_install_dir)
- set_target_properties(${target}
- PROPERTIES QT_QML_MODULE_INSTALL_DIR "${arg_INSTALL_LOCATION}"
+ if(arg_OUTPUT_TARGETS)
+ set(${arg_OUTPUT_TARGETS} ${output_targets} PARENT_SCOPE)
+ endif()
+
+
+ set(ensure_set_properties
+ QT_QML_MODULE_PLUGIN_TYPES_FILE
+ QT_QML_MODULE_QML_FILES
+ QT_QML_MODULE_RESOURCES # Original files as provided by the project (absolute)
+ QT_QML_MODULE_RESOURCE_PATHS # By qmlcachegen (resource paths)
+ QT_QMLCACHEGEN_DIRECT_CALLS
+ QT_QMLCACHEGEN_EXECUTABLE
+ QT_QMLCACHEGEN_ARGUMENTS
+ )
+ foreach(prop IN LISTS ensure_set_properties)
+ get_target_property(val ${target} ${prop})
+ if("${val}" MATCHES "-NOTFOUND$")
+ set_target_properties(${target} PROPERTIES ${prop} "")
+ endif()
+ endforeach()
+
+ if(${QT_QML_GENERATE_QMLLS_INI})
+ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0")
+ # collect all build dirs obtained from all the qt_add_qml_module calls and
+ # write the .qmlls.ini file in a deferred call
+
+ if(NOT "${arg_OUTPUT_DIRECTORY}" STREQUAL "")
+ set(output_folder "${arg_OUTPUT_DIRECTORY}")
+ else()
+ set(output_folder "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+ string(REPLACE "." ";" uri_bits "${arg_URI}")
+ set(build_folder "${output_folder}")
+ foreach(bit IN LISTS uri_bits)
+ get_filename_component(build_folder "${build_folder}" DIRECTORY)
+ endforeach()
+ get_directory_property(_qmlls_ini_build_folders _qmlls_ini_build_folders)
+ list(APPEND _qmlls_ini_build_folders "${build_folder}")
+ set_directory_properties(PROPERTIES _qmlls_ini_build_folders "${_qmlls_ini_build_folders}")
+
+ # if no call with id 'qmlls_ini_generation_id' was deferred for this directory, do it now
+ cmake_language(DEFER GET_CALL qmlls_ini_generation_id call)
+ if("${call}" STREQUAL "")
+ cmake_language(EVAL CODE
+ "cmake_language(DEFER ID qmlls_ini_generation_id CALL _qt_internal_write_deferred_qmlls_ini_file)"
)
endif()
+ else()
+ get_property(__qt_internal_generate_qmlls_ini_warning GLOBAL PROPERTY __qt_internal_generate_qmlls_ini_warning)
+ if (NOT "${__qt_internal_generate_qmlls_ini_warning}")
+ message(WARNING "QT_QML_GENERATE_QMLLS_INI is not supported on CMake versions < 3.19, disabling...")
+ set_property(GLOBAL PROPERTY __qt_internal_generate_qmlls_ini_warning ON)
+ endif()
endif()
endif()
+endfunction()
- if (arg_PAST_MAJOR_VERSIONS)
- set_target_properties(${target} PROPERTIES QT_QML_PAST_MAJOR_VERSIONS "${arg_PAST_MAJOR_VERSIONS}")
- endif()
-
- # Generate meta types data
- if (arg_GENERATE_QMLTYPES)
- qt6_qml_type_registration(${target})
+function(_qt_internal_write_deferred_qmlls_ini_file)
+ set(qmlls_ini_file "${CMAKE_CURRENT_SOURCE_DIR}/.qmlls.ini")
+ get_directory_property(_qmlls_ini_build_folders _qmlls_ini_build_folders)
+ list(REMOVE_DUPLICATES _qmlls_ini_build_folders)
+ if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ # replace cmake list separator ';' with unix path separator ':'
+ string(REPLACE ";" ":" concatenated_build_dirs "${_qmlls_ini_build_folders}")
+ else()
+ # cmake list separator and windows path separator are both ';', so no replacement needed
+ set(concatenated_build_dirs "${_qmlls_ini_build_folders}")
endif()
+ set(file_content "[General]\nbuildDir=${concatenated_build_dirs}\nno-cmake-calls=false\n")
+ file(CONFIGURE OUTPUT "${qmlls_ini_file}" CONTENT "${file_content}")
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_add_qml_module)
qt6_add_qml_module(${ARGV})
+ cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "")
+ if(arg_OUTPUT_TARGETS)
+ set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE)
+ endif()
endfunction()
endif()
-# Creates a dummy Qml plugin class for pure Qml modules.
-# Needed for both shared and static Qt builds, so that the Qml engine knows to load the plugin.
-function(_qt_internal_create_dummy_qml_plugin target uri out_class_name)
- # Use the escaped URI name as the basis for the class name.
+# Make the prefix conform to the following:
+# - Starts with a "/"
+# - Does not end with a "/" unless the prefix is exactly "/"
+function(_qt_internal_canonicalize_resource_path path out_var)
+ if(NOT path)
+ set(path "/")
+ endif()
+ if(NOT path MATCHES "^/")
+ string(PREPEND path "/")
+ endif()
+ if(path MATCHES [[(.+)/$]])
+ set(path "${CMAKE_MATCH_1}")
+ endif()
+ set(${out_var} "${path}" PARENT_SCOPE)
+endfunction()
+
+function(_qt_internal_get_escaped_uri uri out_var)
string(REGEX REPLACE "[^A-Za-z0-9]" "_" escaped_uri "${uri}")
+ set(${out_var} "${escaped_uri}" PARENT_SCOPE)
+endfunction()
- set(qt_qml_plugin_class_name "${escaped_uri}Plugin")
- set(generated_cpp_file_name_base "Qt6_PureQmlModule_${target}_${qt_qml_plugin_class_name}")
- set(qt_qml_plugin_moc_include_name "${generated_cpp_file_name_base}.moc")
+function(_qt_internal_compute_qml_plugin_class_name_from_uri uri out_var)
+ _qt_internal_get_escaped_uri("${uri}" escaped_uri)
+ set(${out_var} "${escaped_uri}Plugin" PARENT_SCOPE)
+endfunction()
+
+macro(_qt_internal_genex_getproperty var target property)
+ set(${var} "$<TARGET_PROPERTY:${target},${property}>")
+ set(have_${var} "$<BOOL:${${var}}>")
+endmacro()
+
+macro(_qt_internal_genex_getjoinedproperty var target property item_prefix glue)
+ _qt_internal_genex_getproperty(${var} ${target} ${property})
+ set(${var} "$<${have_${var}}:${item_prefix}$<JOIN:${${var}},${glue}${item_prefix}>>")
+endmacro()
- set(register_types_function_name "qml_register_types_${escaped_uri}")
- set(qt_qml_plugin_intro "extern void ${register_types_function_name}();")
- if(QT_BUILDING_QT)
- string(APPEND qt_qml_plugin_intro "\n\nQT_BEGIN_NAMESPACE")
- set(qt_qml_plugin_outro "QT_END_NAMESPACE")
+# Creates a genex that will call a c++ macro on each of the list values.
+# Handles empty lists.
+macro(_qt_internal_genex_get_list_joined_with_macro var input_list macro_name with_ending_semicolon)
+ set(${var} "${input_list}")
+ set(have_${var} "$<BOOL:${${var}}>")
+
+ set(_macro_begin "${macro_name}(")
+ set(_macro_end ")")
+ if(with_ending_semicolon)
+ string(APPEND _macro_end ";")
endif()
+ set(_macro_glue "${_macro_end}\n${_macro_begin}")
+
+ string(JOIN "" ${var}
+ "${_macro_begin}"
+ "$<JOIN:${${var}},${_macro_glue}>"
+ "${_macro_end}")
+
+ set(${var} "$<${have_${var}}:${${var}}>")
+
+ unset(_macro_begin)
+ unset(_macro_end)
+ unset(_macro_glue)
+endmacro()
+
+# Reads a target property that contains a list of values and creates a genex that will
+# call a c++ macro on each of the values.
+# Handles empty properties.
+macro(_qt_internal_genex_get_property_joined_with_macro var target property macro_name
+ with_ending_semicolon)
+ _qt_internal_genex_getproperty(${var} ${target} ${property})
+ _qt_internal_genex_get_list_joined_with_macro("${var}" "${${var}}" "${macro_name}"
+ "${with_ending_semicolon}")
+endmacro()
+
+macro(_qt_internal_genex_getoption var target property)
+ set(${var} "$<BOOL:$<TARGET_PROPERTY:${target},${property}>>")
+endmacro()
+
+# Gets the Qt import paths, prepends -I, appends the values to the given import_paths_var output
+# variable.
+function(_qt_internal_extend_qml_import_paths import_paths_var)
+ set(local_var ${${import_paths_var}})
+
+ _qt_internal_get_main_qt_qml_import_paths(qt_import_paths)
+
+ # Append the paths instead of prepending them, to ensure Qt import paths are searched after
+ # any target or build dir specific import paths.
+ foreach(import_path IN LISTS qt_import_paths)
+ list(APPEND local_var -I "${import_path}")
+ endforeach()
- set(qt_qml_plugin_constructor_content
- "volatile auto registration = &${register_types_function_name};
- Q_UNUSED(registration);
-")
+ set(${import_paths_var} ${local_var} PARENT_SCOPE)
+endfunction()
- set(template_path "${__qt_qml_macros_module_base_dir}/Qt6QmlPluginTemplate.cpp.in")
- set(generated_cpp_file_name "${generated_cpp_file_name_base}.cpp")
- set(generated_cpp_file_path "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name}")
+function(_qt_internal_assign_to_qmllint_targets_folder target)
+ get_property(folder_name GLOBAL PROPERTY QT_QMLLINTER_TARGETS_FOLDER)
+ if("${folder_name}" STREQUAL "")
+ get_property(__qt_qt_targets_folder GLOBAL PROPERTY QT_TARGETS_FOLDER)
+ set(folder_name "${__qt_qt_targets_folder}/QmlLinter")
+ set_property(GLOBAL PROPERTY QT_QMLLINTER_TARGETS_FOLDER ${folder_name})
+ endif()
+ set_property(TARGET ${target} PROPERTY FOLDER "${folder_name}")
+endfunction()
- configure_file("${template_path}" "${generated_cpp_file_path}" @ONLY)
+function(_qt_internal_target_enable_qmllint target)
+ set(lint_target ${target}_qmllint)
+ set(lint_target_json ${target}_qmllint_json)
+ set(lint_target_module ${target}_qmllint_module)
+ if(TARGET ${lint_target} OR TARGET ${target}_qmllint_json OR TARGET ${target}_qmllint_module)
+ return()
+ endif()
- target_sources("${target}" PRIVATE "${generated_cpp_file_path}")
- target_link_libraries("${target}" PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Qml)
+ _qt_internal_genex_getproperty(qmllint_files ${target} QT_QML_LINT_FILES)
+ _qt_internal_genex_getjoinedproperty(import_args ${target}
+ QT_QML_IMPORT_PATH "-I$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ _qt_internal_genex_getjoinedproperty(qrc_args ${target}
+ _qt_generated_qrc_files "--resource$<SEMICOLON>" "$<SEMICOLON>"
+ )
- set(${out_class_name} "${qt_qml_plugin_class_name}" PARENT_SCOPE)
+ # Facilitate self-import so it can find the qmldir file. We also try to walk
+ # back up the directory structure to find a base path under which this QML
+ # module is located. Such a base path is likely to be used for other QML
+ # modules that we might need to find, so add it to the import path if we
+ # find a compatible directory structure. It doesn't make sense to do this
+ # for an executable though, since it can never be found as a QML module for
+ # a different QML module/target.
+ get_target_property(target_type ${target} TYPE)
+ get_target_property(is_android_executable ${target} _qt_is_android_executable)
+ if(target_type STREQUAL "EXECUTABLE" OR is_android_executable)
+ # The executable's own QML module's qmldir file will usually be under a
+ # subdirectory (matching the module's target path) below the target's
+ # build directory.
+ list(APPEND import_args -I "$<TARGET_PROPERTY:${target},BINARY_DIR>")
+ elseif(target_type MATCHES "LIBRARY")
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ get_target_property(target_path ${target} QT_QML_MODULE_TARGET_PATH)
+ if(output_dir MATCHES "${target_path}$")
+ string(REGEX REPLACE "(.*)/${target_path}" "\\1" base_dir "${output_dir}")
+ list(APPEND import_args -I "${base_dir}")
+ else()
+ message(WARNING
+ "The ${target} target is a QML module with target path ${target_path}. "
+ "It uses an OUTPUT_DIRECTORY of ${output_dir}, which should end in the "
+ "same target path, but doesn't. Tooling such as qmllint may not work "
+ "correctly."
+ )
+ endif()
+ endif()
- # Enable AUTOMOC explicitly, because the generated cpp file expects to include its moc-ed
- # output file.
- set_property(TARGET "${target}" PROPERTY AUTOMOC ON)
+ if(NOT "${QT_QML_OUTPUT_DIRECTORY}" STREQUAL "")
+ list(APPEND import_args -I "${QT_QML_OUTPUT_DIRECTORY}")
+ endif()
+
+ _qt_internal_extend_qml_import_paths(import_args)
+
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+
+ set(qmllint_args
+ --bare
+ ${import_args}
+ ${qrc_args}
+ ${qmllint_files}
+ )
+
+ get_target_property(target_binary_dir ${target} BINARY_DIR)
+ set(qmllint_dir ${target_binary_dir}/.rcc/qmllint)
+ set(qmllint_rsp_path ${qmllint_dir}/${target}.rsp)
+
+ file(GENERATE
+ OUTPUT "${qmllint_rsp_path}"
+ CONTENT "$<JOIN:${qmllint_args},\n>\n"
+ )
+
+ set(cmd
+ ${tool_wrapper}
+ $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint>
+ @${qmllint_rsp_path}
+ )
+
+ set(cmd_dummy ${CMAKE_COMMAND} -E echo "Nothing to do for target ${lint_target}.")
+
+ # We need this target to depend on all qml type registrations. This is the
+ # only way we can be sure that all *.qmltypes files for any QML modules we
+ # depend on will have been generated.
+ add_custom_target(${lint_target}
+ COMMAND "$<IF:${have_qmllint_files},${cmd},${cmd_dummy}>"
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${QT_CMAKE_EXPORT_NAMESPACE}::qmllint
+ ${qmllint_files}
+ ${qmllint_rsp_path}
+ $<TARGET_NAME_IF_EXISTS:all_qmltyperegistrations>
+ WORKING_DIRECTORY "$<TARGET_PROPERTY:${target},SOURCE_DIR>"
+ )
+ _qt_internal_assign_to_qmllint_targets_folder(${lint_target})
+
+ list(APPEND qmllint_args "--json" "${CMAKE_BINARY_DIR}/${lint_target}.json")
+
+ set(qmllint_rsp_path ${qmllint_dir}/${target}_json.rsp)
+
+ file(GENERATE
+ OUTPUT "${qmllint_rsp_path}"
+ CONTENT "$<JOIN:${qmllint_args},\n>\n"
+ )
+
+ set(cmd
+ ${tool_wrapper}
+ $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint>
+ @${qmllint_rsp_path}
+ )
+
+ add_custom_target(${lint_target_json}
+ COMMAND "$<${have_qmllint_files}:${cmd}>"
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${QT_CMAKE_EXPORT_NAMESPACE}::qmllint
+ ${qmllint_files}
+ ${qmllint_rsp_path}
+ $<TARGET_NAME_IF_EXISTS:all_qmltyperegistrations>
+ WORKING_DIRECTORY "$<TARGET_PROPERTY:${target},SOURCE_DIR>"
+ )
+ _qt_internal_assign_to_qmllint_targets_folder(${lint_target_json})
+
+ set_target_properties(${lint_target_json} PROPERTIES EXCLUDE_FROM_ALL TRUE)
+
+ get_target_property(module_uri ${target} QT_QML_MODULE_URI)
+
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+
+ set(qmllint_args
+ ${import_args}
+ ${qrc_args}
+ --module
+ ${module_uri}
+ )
+
+ set(qmllint_rsp_path ${qmllint_dir}/${target}_module.rsp)
+
+ file(GENERATE
+ OUTPUT "${qmllint_rsp_path}"
+ CONTENT "$<JOIN:${qmllint_args},\n>\n"
+ )
+
+ set(cmd
+ ${tool_wrapper}
+ $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint>
+ @${qmllint_rsp_path}
+ )
+
+ add_custom_target(${lint_target_module}
+ COMMAND ${cmd}
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${QT_CMAKE_EXPORT_NAMESPACE}::qmllint
+ ${qmllint_files}
+ ${qmllint_rsp_path}
+ $<TARGET_NAME_IF_EXISTS:all_qmltyperegistrations>
+ WORKING_DIRECTORY "$<TARGET_PROPERTY:${target},SOURCE_DIR>"
+ )
+ _qt_internal_assign_to_qmllint_targets_folder(${lint_target_module})
+
+ # Make the global linting target depend on the one we add here.
+ # Note that the caller is free to change the value of QT_QMLLINT_ALL_TARGET
+ # for different QML modules if they wish, which means they can implement
+ # their own grouping of the ${target}_qmllint targets.
+ _qt_internal_add_all_qmllint_target(QT_QMLLINT_ALL_TARGET
+ all_qmllint ${lint_target})
+ _qt_internal_add_all_qmllint_target(QT_QMLLINT_JSON_ALL_TARGET
+ all_qmllint_json ${lint_target_json})
+ _qt_internal_add_all_qmllint_target(QT_QMLLINT_MODULE_ALL_TARGET
+ all_qmllint_module ${lint_target_module})
endfunction()
+# This is a modified version of __qt_propagate_generated_resource from qtbase.
#
-# Add Qml files (.qml,.js,.mjs) to a Qml module. This will also append the
-# qml files to the qmldir file of the module. Two source file properties can
-# be used to control the generated qmldir entry.
-#
-# QT_QML_SOURCE_VERSION: Version(s) for this qml file. If not present the module
-# version will be used.
-# QT_QML_SOURCE_TYPENAME: Override the file's type name. If not present the
-# type name will be deduced using the file's basename.
-# QT_QML_SINGLETON_TYPE: Set to true if this qml file contains a singleton
-# type.
-# QT_QML_INTERNAL_TYPE: When set to true, the type specified by
-# QT_QML_SOURCE_TYPENAME will not be available to users of this module.
-# QT_QML_SKIP_QMLDIR_ENTRY: When set to true, no qmldir entry will be created for
-# the source file. Useful if a file needs to be installed (like a private JS
-# file) but does not expose a public type.
-#
-# e.g.:
-# set_source_files_properties(my_qml_file.qml
-# PROPERTIES
-# QT_QML_SOURCE_VERSION "2.0;6.0"
-# QT_QML_SOURCE_TYPENAME MyQmlFile
-#
-# qt6_target_qml_files(my_qml_module
-# FILES
-# my_qml_file.qml
-# )
-#
-# Will produce the following entry in the qmldir file
-#
-# MyQmlFile 2.0 my_qml_file.qml
+# It uses a common __qt_internal_propagate_object_library function to link and propagate the object
+# library to the end-point executable.
#
-#
-# This function is currently in Technical Preview.
-# It's signature and behavior might change.
-function(qt6_target_qml_files target)
+# The reason for propagating the qmlcache target as a 'fake resource' from the build system
+# perspective is to ensure proper handling of the object files in generated qmake .prl files.
+function(_qt_internal_propagate_qmlcache_object_lib
+ target
+ generated_source_code
+ link_condition
+ output_generated_target)
+ set(resource_target "${target}_qmlcache")
+ qt6_add_library("${resource_target}" OBJECT "${generated_source_code}")
+
+ # Needed to trigger the handling of the object library for .prl generation.
+ set_property(TARGET ${resource_target} APPEND PROPERTY _qt_resource_name ${resource_target})
+
+ # Export info that this is a qmlcache target, in case if we ever need to detect such targets,
+ # similar how we need it for plugin initializers.
+ set_property(TARGET ${resource_target} PROPERTY _is_qt_qmlcache_target TRUE)
+ set_property(TARGET ${resource_target} APPEND PROPERTY
+ EXPORT_PROPERTIES _is_qt_qmlcache_target
+ )
- cmake_parse_arguments(arg "" "" "FILES" ${ARGN})
- get_target_property(resource_count ${target} QT6_QML_MODULE_ADD_QML_FILES_COUNT)
- get_target_property(qmldir_file ${target} QT_QML_MODULE_QMLDIR_FILE)
- if (NOT qmldir_file)
- message(FATAL_ERROR "qt6_target_qml_file: ${target} is not a Qml module")
+ # Save the path to the generated source file, relative to the the current build dir.
+ # The path will be used in static library prl file generation to ensure qmake links
+ # against the installed resource object files.
+ # Example saved path:
+ # .rcc/qrc_qprintdialog.cpp
+ file(RELATIVE_PATH generated_cpp_file_relative_path
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ "${generated_source_code}")
+ set_property(TARGET ${resource_target} APPEND PROPERTY
+ _qt_resource_generated_cpp_relative_path "${generated_cpp_file_relative_path}")
+
+ # Qml specific additions.
+ target_link_libraries(${resource_target} PRIVATE
+ ${QT_CMAKE_EXPORT_NAMESPACE}::QmlPrivate
+ ${QT_CMAKE_EXPORT_NAMESPACE}::Core
+ )
+
+ __qt_internal_propagate_object_library(${target} ${resource_target}
+ EXTRA_CONDITIONS "${link_condition}"
+ )
+
+ set(${output_generated_target} "${resource_target}" PARENT_SCOPE)
+endfunction()
+
+# Create an 'all_qmllint' target. The target's name can be user-controlled by ${target_var} with the
+# default name ${default_target_name}. The parameter ${lint_target} holds the name of the single
+# foo_qmllint target that should be triggered by the all_qmllint target.
+function(_qt_internal_add_all_qmllint_target target_var default_target_name lint_target)
+ set(target_name "${${target_var}}")
+ if("${target_name}" STREQUAL "")
+ set(target_name ${default_target_name})
endif()
+ _qt_internal_add_phony_target(${target_name}
+ WARNING_VARIABLE QT_NO_QMLLINT_CREATION_WARNING
+ TARGET_CREATED_HOOK _qt_internal_assign_to_qmllint_targets_folder
+ )
+ _qt_internal_add_phony_target_dependencies(${target_name} ${lint_target})
+endfunction()
- if (NOT arg_FILES)
- return()
+# Hack for the Visual Studio generator. Create the all_qmllint target named ${target} and work
+# around the lack of a working add_dependencies by calling 'cmake --build' for every dependency.
+function(_qt_internal_add_all_qmllint_target_deferred target)
+ get_property(target_dependencies GLOBAL PROPERTY _qt_target_${target}_dependencies)
+ set(target_commands "")
+ foreach(dependency IN LISTS target_dependencies)
+ list(APPEND target_commands
+ COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" -t ${dependency}
+ )
+ endforeach()
+ add_custom_target(${target} ${target_commands})
+ _qt_internal_assign_to_qmllint_targets_folder(${target})
+endfunction()
+
+function(_qt_internal_target_enable_qmlcachegen target qmlcachegen)
+ set_target_properties(${target} PROPERTIES _qt_cachegen_set_up TRUE)
+
+ get_target_property(target_binary_dir ${target} BINARY_DIR)
+ set(qmlcache_dir ${target_binary_dir}/.rcc/qmlcache)
+ set(qmlcache_resource_name qmlcache_${target})
+
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_qmlcache_resource_name "${qmlcache_resource_name}")
+ set_target_properties(${target} PROPERTIES _qt_cachegen_sanitized_resource_name
+ "${sanitized_qmlcache_resource_name}")
+
+ # INTEGRITY_SYMBOL_UNIQUENESS
+ # The cache loader file name has to be unique, because the Integrity compiler uses the file name
+ # for the generation of the translation unit static constructor symbol name.
+ # e.g. __sti___19_qmlcache_loader_cpp_11acedbd
+ # For some reason the symbol is created with global visibility.
+ #
+ # When an application links against the Basic and Fusion static qml plugins, the linker
+ # fails with duplicate symbol errors because both of those plugins will contain the same symbol.
+ #
+ # With gcc on regular Linux, the symbol names are also the same, but it's not a problem because
+ # they have local (hidden) visbility.
+ #
+ # Make the file name unique by prepending the target name.
+ set(qmlcache_loader_cpp ${qmlcache_dir}/${target}_qmlcache_loader.cpp)
+
+ set(qmlcache_loader_list ${qmlcache_dir}/${target}_qml_loader_file_list.rsp)
+ set(qmlcache_resource_paths "$<TARGET_PROPERTY:${target},QT_QML_MODULE_RESOURCE_PATHS>")
+
+ _qt_internal_genex_getjoinedproperty(qrc_resource_args ${target}
+ _qt_generated_qrc_files "--resource$<SEMICOLON>" "$<SEMICOLON>"
+ )
+
+ if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
+ set(qmlcachegen "$<COMMAND_CONFIG:${qmlcachegen}>")
endif()
- math(EXPR new_count "${resource_count} + 1")
- set_target_properties(${target} PROPERTIES QT6_QML_MODULE_ADD_QML_FILES_COUNT ${new_count})
- qt6_add_resources(${target} "${target}_qml_files_${new_count}"
- FILES ${arg_FILES}
- OUTPUT_TARGETS resource_targets
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+ set(cmd
+ ${tool_wrapper}
+ ${qmlcachegen}
+ --resource-name "${qmlcache_resource_name}"
+ -o "${qmlcache_loader_cpp}"
+ "@${qmlcache_loader_list}"
)
- get_target_property(skip_type_registration ${target} QT_QML_MODULE_SKIP_TYPE_REGISTRATION)
- get_target_property(target_resource_export ${target} QT_QML_MODULE_RESOURCE_EXPORT)
- get_target_property(qml_module_install_dir ${target} QT_QML_MODULE_INSTALL_DIR)
- if(NOT qml_module_install_dir)
- message(AUTHOR_WARNING
- "No QT_QML_MODULE_INSTALL_DIR property value provided for the '${target}' target. "
- "Installation of qml_files will most likely be broken.")
+ file(GENERATE
+ OUTPUT ${qmlcache_loader_list}
+ CONTENT "$<JOIN:${qrc_resource_args},\n>\n$<JOIN:${qmlcache_resource_paths},\n>\n"
+ )
+
+ add_custom_command(
+ OUTPUT ${qmlcache_loader_cpp}
+ COMMAND "${cmd}"
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${qmlcachegen}
+ ${qmlcache_loader_list}
+ $<TARGET_PROPERTY:${target},_qt_generated_qrc_files>
+ VERBATIM
+ )
+
+ # The current scope sees the file as generated automatically, but the
+ # target scope may not if it is different. Force it where we can.
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set_source_files_properties(
+ ${qmlcache_loader_cpp}
+ TARGET_DIRECTORY ${target}
+ PROPERTIES GENERATED TRUE
+ SKIP_AUTOGEN TRUE
+ )
+ endif()
+ get_target_property(target_source_dir ${target} SOURCE_DIR)
+ if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ add_custom_target(${target}_qmlcachegen DEPENDS ${qmlcache_loader_cpp})
+ add_dependencies(${target} ${target}_qmlcachegen)
+ endif()
+
+ # TODO: Probably need to reject ${target} being an object library as unsupported
+ target_sources(${target} PRIVATE "${qmlcache_loader_cpp}")
+ target_link_libraries(${target} PRIVATE
+ ${QT_CMAKE_EXPORT_NAMESPACE}::QmlPrivate
+ ${QT_CMAKE_EXPORT_NAMESPACE}::Core
+ )
+endfunction()
+
+# We cannot defer writing out the qmldir file to generation time because the
+# qmlimportscanner runs at configure time as part of target finalizers.
+# Therefore, the best we can do is defer writing the qmldir file if we are
+# using a recent enough CMake version, otherwise we write it out progressively
+# on each call that adds qml sources. The immediate progressive writes will
+# trigger some unnecessary rebuilds after reconfiguring due to the qmldir
+# file's timestamp being updated even though its contents might not change,
+# but that's the cost of not having deferred write capability.
+function(_qt_internal_target_generate_qmldir target)
+
+ macro(_qt_internal_qmldir_item prefix property)
+ get_target_property(_value ${target} ${property})
+ if(_value)
+ string(APPEND content "${prefix} ${_value}\n")
+ endif()
+ endmacro()
+
+ macro(_qt_internal_qmldir_item_list prefix property)
+ get_target_property(_values ${target} ${property})
+ if(_values)
+ foreach(_value IN LISTS _values)
+ string(APPEND content "${prefix} ${_value}\n")
+ endforeach()
+ endif()
+ endmacro()
+
+ get_target_property(uri ${target} QT_QML_MODULE_URI)
+ if(NOT uri)
+ message(FATAL_ERROR "Target ${target} has no URI set, cannot create qmldir")
endif()
+ set(content "module ${uri}\n")
+
+ _qt_internal_qmldir_item(linktarget QT_QML_MODULE_INSTALLED_PLUGIN_TARGET)
+
+ get_target_property(plugin_target ${target} QT_QML_MODULE_PLUGIN_TARGET)
+ if(plugin_target)
+ get_target_property(no_plugin_optional ${target} QT_QML_MODULE_NO_PLUGIN_OPTIONAL)
+ if(NOT no_plugin_optional MATCHES "NOTFOUND" AND NOT no_plugin_optional)
+ string(APPEND content "optional ")
+ endif()
- if (resource_targets)
- install(TARGETS ${resource_targets}
- EXPORT "${target_resource_export}"
- DESTINATION "${qml_module_install_dir}"
+ get_target_property(target_path ${target} QT_QML_MODULE_TARGET_PATH)
+ _qt_internal_get_qml_plugin_output_name(plugin_output_name ${plugin_target}
+ TARGET_PATH "${target_path}"
+ URI "${uri}"
)
+ string(APPEND content "plugin ${plugin_output_name}\n")
+
+ _qt_internal_qmldir_item(classname QT_QML_MODULE_CLASS_NAME)
+ endif()
+
+ get_target_property(designer_supported ${target} QT_QML_MODULE_DESIGNER_SUPPORTED)
+ if(designer_supported)
+ string(APPEND content "designersupported\n")
+ endif()
+
+ get_target_property(static_module ${target} QT_QML_MODULE_IS_STATIC)
+ if (static_module)
+ string(APPEND content "static\n")
+ endif()
+
+ get_target_property(system_module ${target} QT_QML_MODULE_IS_SYSTEM)
+ if (system_module)
+ string(APPEND content "system\n")
+ endif()
+
+ _qt_internal_qmldir_item(typeinfo QT_QML_MODULE_TYPEINFO)
+
+ _qt_internal_qmldir_item_list(import QT_QML_MODULE_IMPORTS)
+ _qt_internal_qmldir_item_list("optional import" QT_QML_MODULE_OPTIONAL_IMPORTS)
+ _qt_internal_qmldir_item_list("default import" QT_QML_MODULE_DEFAULT_IMPORTS)
+
+ _qt_internal_qmldir_item_list(depends QT_QML_MODULE_DEPENDENCIES)
+
+ get_target_property(prefix ${target} QT_QML_MODULE_RESOURCE_PREFIX)
+ if(prefix)
+ # Ensure we use a path that ends with a "/", but handle the special case
+ # of "/" without anything after it
+ if(NOT prefix STREQUAL "/" AND NOT prefix MATCHES "/$")
+ string(APPEND prefix "/")
+ endif()
+ string(APPEND content "prefer :${prefix}\n")
+ endif()
+
+ # TODO: What about multi-config generators? Would we need per-config qmldir
+ # files (because we will have per-config plugin targets)?
+
+ # Record the contents but defer the actual write. We will write the file
+ # later, either at the end of qt6_add_qml_module() or the end of the
+ # directory scope (depending on the CMake version being used).
+ set_property(TARGET ${target} PROPERTY _qt_internal_qmldir_content "${content}")
+
+ # NOTE: qt6_target_qml_sources() may append further content later.
+endfunction()
+
+function(_qt_internal_write_deferred_qmldir_file target)
+ get_target_property(__qt_qmldir_content ${target} _qt_internal_qmldir_content)
+ get_target_property(out_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ set(qmldir_file "${out_dir}/qmldir")
+ configure_file(${__qt_qml_macros_module_base_dir}/Qt6qmldirTemplate.cmake.in ${qmldir_file} @ONLY)
+endfunction()
+
+# With a macOS framework Qt build, moc needs to be passed -F<qt-framework-path>
+# arguments to resolve framework style includes like #include <QtCore/qobject.h>
+# Extract the location of the Qt frameworks by querying the imported location of
+# the target (where target is a Qt library). Do not care about non-Qt targets.
+function(_qt_internal_qml_get_qt_framework_path target out_var)
+ set(value "")
+ # NOTE: only exercise IMPORTED_LOCATION of various flavors. this seems to be
+ # good enough in other places (e.g. when locating qmlimportscanner)
+ get_target_property(target_path ${target} IMPORTED_LOCATION)
+ if(NOT target_path)
+ set(configs "RELWITHDEBINFO;RELEASE;MINSIZEREL;DEBUG")
+ foreach(config ${configs})
+ get_target_property(target_path ${target} IMPORTED_LOCATION_${config})
+ # NOTE: to be fair, any location is good enough. the macro
+ # definitions we need must not vary between configurations
+ if(target_path)
+ break()
+ endif()
+ endforeach()
+ endif()
+ string(REGEX REPLACE "(.*)/Qt[^/]+\\.framework.*" "\\1" target_fw_path "${target_path}")
+ if(target_fw_path)
+ set(value "${target_fw_path}")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+function(_qt_internal_qml_get_qt_framework_path_moc_option target out_var)
+ _qt_internal_qml_get_qt_framework_path(${target} target_fw_path)
+ if(target_fw_path)
+ set(${out_var} "-F${target_fw_path}" PARENT_SCOPE)
+ else()
+ set(${out_var} "" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# creates a QRC mapping between QML files in build directory and QML files in
+# source directory
+function(_qt_internal_qml_map_build_files target qml_module_prefix qrc_file_out_var)
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ if(NOT output_dir)
+ # TODO: we might want to support non-qml modules here (think QML
+ # language server) but it is unclear whether the below code would
+ # succeed, so just abort here until we have a proper test case
+ message(WARNING "Target ${target} is not a QML module. Cannot detect its output directory")
+ return()
+ endif()
+
+ set(qrcContents "")
+ string(APPEND qrcContents " <file alias=\"${qml_module_prefix}\">${output_dir}</file>\n")
- # When building a static Qt, we need to record information about the compiled resource
- # object files to embed them into .prl files.
- if(COMMAND qt_internal_record_rcc_object_files)
- qt_internal_record_rcc_object_files(
- "${target}" "${resource_targets}" INSTALL_LOCATION "${qml_module_install_dir}")
+ # dump the contents into the .qrc file
+ set(template_file "${__qt_qml_macros_module_base_dir}/Qt6QmlModuleDirMappingTemplate.qrc.in")
+ set(generated_qrc_file "${output_dir}/${target}_qml_module_dir_map.qrc")
+ set(qt_qml_module_dir_mapping_contents "${qrcContents}")
+ configure_file(${template_file} ${generated_qrc_file})
+
+ set(${qrc_file_out_var} ${generated_qrc_file} PARENT_SCOPE)
+endfunction()
+
+# Creates a qrc file that maps QML file path to generated C++ header file name
+function(_qt_internal_qml_add_qmltc_file_mapping_resource qrc_file target qml_files)
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ set(qrcContents "")
+
+ # go over all QML_FILES, mapping QML files to generated C++ sources
+ foreach(qml_file IN LISTS qml_files)
+ get_filename_component(file_absolute ${qml_file} ABSOLUTE)
+ get_filename_component(file_basename ${file_absolute} NAME_WLE) # extension is always .qml
+ string(REGEX REPLACE "[$#?]+" "_" compiled_file ${file_basename})
+
+ # NB: use <lowercase(file_name)>.<extension> pattern. if
+ # lowercase(file_name) is already taken (e.g. project has main.qml and
+ # main.h/main.cpp), the compilation might fail. in this case, expect
+ # user to specify QT_QMLTC_FILE_BASENAME
+ string(TOLOWER ${compiled_file} file_name)
+
+ get_source_file_property(specified_file_name ${qml_file} QT_QMLTC_FILE_BASENAME)
+ if (specified_file_name)
+ get_filename_component(file_name ${specified_file_name} NAME_WLE)
endif()
+
+ # <file_name>.h is enough as corresponding .cpp is not interesting (and
+ # we can get it easily by replacing the extension)
+ string(APPEND qrcContents " <file alias=\"${file_name}.h\">${file_absolute}</file>\n")
+ endforeach()
+
+ # dump the contents into the .qrc file
+ set(template_file "${__qt_qml_macros_module_base_dir}/Qt6QmltcFileMappingTemplate.qrc.in")
+ set(generated_qrc_file "${output_dir}/.qmltc/${target}/${target}_qmltc_file_map.qrc")
+ set(qt_qml_qmltc_file_mapping_contents "${qrcContents}")
+ configure_file(${template_file} ${generated_qrc_file})
+
+ set(${qrc_file} ${generated_qrc_file} PARENT_SCOPE)
+endfunction()
+
+# Compile Qml files (.qml) to C++ source files with QML type compiler (qmltc).
+function(_qt_internal_target_enable_qmltc target)
+ set(args_option "")
+ set(args_single NAMESPACE EXPORT_MACRO_NAME EXPORT_FILE_NAME MODULE)
+ set(args_multi QML_FILES IMPORT_PATHS)
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ "${args_option}" "${args_single}" "${args_multi}"
+ )
+ if(arg_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "Unknown/unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
+ if (NOT arg_QML_FILES)
+ message(FATAL_ERROR "FILES option not given or contains empty list for target ${target}")
endif()
- qt6_target_enable_qmllint(${target})
+ if(NOT TARGET "${target}")
+ message(FATAL_ERROR "\"${target}\" is not a known target")
+ endif()
- set(file_contents "")
- foreach(qml_file IN LISTS arg_FILES)
- get_filename_component(qml_file_dir "${qml_file}" DIRECTORY)
- if (NOT "${qml_file_dir}" STREQUAL "")
- set(qml_file_dir "/${qml_file_dir}")
+ get_target_property(target_source_dir ${target} SOURCE_DIR)
+ get_target_property(target_binary_dir ${target} BINARY_DIR)
+
+ set(generated_sources_other_scope)
+
+ set(compiled_files) # compiled files list to be used to generate MOC C++
+ set(qmltc_executable "$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmltc>")
+ if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
+ set(qmltc_executable "$<COMMAND_CONFIG:${qmltc_executable}>")
+ endif()
+
+ set(common_args "")
+ if(arg_NAMESPACE)
+ list(APPEND common_args --namespace "${arg_NAMESPACE}")
+ endif()
+ if(arg_MODULE)
+ list(APPEND common_args --module "${arg_MODULE}")
+ endif()
+ if(arg_EXPORT_MACRO_NAME)
+ list(APPEND common_args --export "${arg_EXPORT_MACRO_NAME}")
+ if(arg_EXPORT_FILE_NAME)
+ list(APPEND common_args --exportInclude "${arg_EXPORT_FILE_NAME}")
endif()
- if (qml_module_install_dir)
- if (NOT QT_WILL_INSTALL)
- file(COPY "${qml_file}" DESTINATION "${qml_module_install_dir}${qml_file_dir}")
- else()
- install(FILES "${qml_file}" DESTINATION "${qml_module_install_dir}${qml_file_dir}")
+ endif()
+
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ set(qmldir_file ${output_dir}/qmldir)
+ # TODO: we still need to specify the qmldir here for _explicit_ imports of
+ # own module. in theory this could be pushed to the user side
+ list(APPEND common_args "-i" ${qmldir_file})
+
+ foreach(import_path IN LISTS arg_IMPORT_PATHS)
+ list(APPEND common_args -I "${import_path}")
+ endforeach()
+
+ _qt_internal_extend_qml_import_paths(common_args)
+
+ # we explicitly depend on qmldir (due to `-i ${qmldir_file}`) but also
+ # implicitly on the generated qmltypes file, which is a part of qmldir
+ set(qml_module_files)
+ list(APPEND qml_module_files ${qmldir_file})
+ get_target_property(qmltypes_file ${target} QT_QML_MODULE_TYPEINFO)
+ if(qmltypes_file)
+ list(APPEND qml_module_files ${output_dir}/${qmltypes_file})
+ endif()
+
+ get_target_property(potential_qml_modules ${target} LINK_LIBRARIES)
+ foreach(lib ${potential_qml_modules})
+ if(NOT TARGET ${lib})
+ continue()
+ endif()
+
+ # if we have a versionless Qt lib, find the public one with a version
+ if(lib MATCHES "^Qt::(.*)")
+ set(lib "${CMAKE_MATCH_1}")
+ if(lib MATCHES "^(.*)Private") # remove "Private"
+ set(lib "${CMAKE_MATCH_1}")
+ endif()
+ set(lib ${QT_CMAKE_EXPORT_NAMESPACE}::${lib})
+ if(NOT TARGET ${lib})
+ continue()
endif()
endif()
- if (skip_type_registration AND qml_file MATCHES "\\.qml$")
+ # when we have a suitable lib, ignore INTERFACE_LIBRARY and IMPORTED
+ get_target_property(lib_type ${lib} TYPE)
+ get_target_property(lib_is_imported ${lib} IMPORTED)
+ if(lib_type STREQUAL "INTERFACE_LIBRARY" OR lib_is_imported)
+ continue()
+ endif()
+
+ # get any QT_QML_MODULE_ property, this way we can tell whether we deal
+ # with QML module target or not. use output dir as it's used later
+ get_target_property(external_output_dir ${lib} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ if(NOT external_output_dir) # not a QML module, so not interesting
continue()
endif()
- get_source_file_property(qml_file_skip_qmldir ${qml_file} QT_QML_SKIP_QMLDIR_ENTRY)
- if (qml_file_skip_qmldir)
+ get_target_property(external_qmltypes_file ${lib} QT_QML_MODULE_TYPEINFO)
+ if(external_qmltypes)
+ # add linked module's qmltypes file to a list of target
+ # dependencies. unlike qmllint or other tooling, qmltc only cares
+ # about explicitly linked libraries. things like plugins are not
+ # supported by design and would result in C++ compilation errors
+ list(APPEND qml_module_files ${external_output_dir}/${external_qmltypes_file})
+ endif()
+ endforeach()
+
+ # ignore non-QML and to-be-skipped QML files
+ set(filtered_qml_files "")
+ foreach(qml_file_src IN LISTS arg_QML_FILES)
+ if(NOT qml_file_src MATCHES "\\.(qml)$")
continue()
endif()
+ get_source_file_property(skip_qmltc ${qml_file_src} QT_QML_SKIP_TYPE_COMPILER)
+ if(skip_qmltc)
+ continue()
+ endif()
+ list(APPEND filtered_qml_files ${qml_file_src})
+ endforeach()
+
+ # qmltc needs qrc files to supply to the QQmlJSResourceFileMapper
+ _qt_internal_genex_getjoinedproperty(qrc_args ${target}
+ _qt_generated_qrc_files "--resource$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ # qmltc also needs meta data qrc files when importing types from own module
+ _qt_internal_genex_getjoinedproperty(metadata_qrc_args ${target}
+ _qt_qml_meta_qrc_files "--meta-resource$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ list(APPEND qrc_args ${metadata_qrc_args})
+
+ # NB: pass qml files variable as string to preserve its list nature
+ # (otherwise we lose all but first element of the list inside a function)
+ _qt_internal_qml_add_qmltc_file_mapping_resource(
+ qmltc_file_map_qrc ${target} "${filtered_qml_files}")
+ # append --resource <qrc> to qmltc's command line
+ list(APPEND qrc_args --resource "${qmltc_file_map_qrc}")
+
+ list(APPEND common_args ${qrc_args})
+
+ foreach(qml_file_src IN LISTS filtered_qml_files)
+ get_filename_component(file_absolute ${qml_file_src} ABSOLUTE)
+
+ get_filename_component(file_basename ${file_absolute} NAME_WLE) # extension is always .qml
+ string(REGEX REPLACE "[$#?]+" "_" compiled_file ${file_basename})
+ string(TOLOWER ${compiled_file} file_name)
+
+ # NB: use <lowercase(file_name)>.<extension> pattern. if
+ # lowercase(file_name) is already taken (e.g. project has main.qml and
+ # main.h/main.cpp), the compilation might fail. in this case, expect
+ # user to specify QT_QMLTC_FILE_BASENAME
+ get_source_file_property(specified_file_name ${qml_file_src} QT_QMLTC_FILE_BASENAME)
+ if (specified_file_name)
+ get_filename_component(file_name ${specified_file_name} NAME_WLE)
+ endif()
+
+ # Note: add '${target}' to path to avoid potential conflicts where 2+
+ # distinct targets use the same ${target_binary_dir}/.qmltc/ output dir
+ set(compiled_header "${target_binary_dir}/.qmltc/${target}/${file_name}.h")
+ set(compiled_cpp "${target_binary_dir}/.qmltc/${target}/${file_name}.cpp")
+ get_filename_component(out_dir ${compiled_header} DIRECTORY)
+
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+ add_custom_command(
+ OUTPUT ${compiled_header} ${compiled_cpp}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${out_dir}
+ COMMAND
+ ${tool_wrapper}
+ ${qmltc_executable}
+ --bare
+ --header "${compiled_header}"
+ --impl "${compiled_cpp}"
+ ${common_args}
+ ${file_absolute}
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${qmltc_executable}
+ "${file_absolute}"
+ ${qml_module_files}
+ $<TARGET_PROPERTY:${target},_qt_generated_qrc_files>
+ $<TARGET_PROPERTY:${target},_qt_qml_meta_qrc_files>
+ ${qmltc_file_map_qrc}
+ COMMENT "Compiling ${qml_file_src} with qmltc"
+ VERBATIM
+ )
+
+ set_source_files_properties(${compiled_header} ${compiled_cpp}
+ PROPERTIES SKIP_AUTOGEN ON
+ SKIP_UNITY_BUILD_INCLUSION ON)
+ target_sources(${target} PRIVATE ${compiled_header} ${compiled_cpp})
+ target_include_directories(${target} PUBLIC ${out_dir})
+ # The current scope automatically sees the file as generated, but the
+ # target scope may not if it is different. Force it where we can.
+ # We will also have to add the generated file to a target in this
+ # scope at the end to ensure correct dependencies.
+ if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ list(APPEND generated_sources_other_scope ${compiled_header} ${compiled_cpp})
+ endif()
+ endif()
+
+ list(APPEND compiled_files ${compiled_header})
+ endforeach()
+
+ set(extra_moc_options "")
+ if(APPLE AND QT_FEATURE_framework)
+ # this is a special case, where we need -F options passed to manual moc.
+ # since we're in qmltc code, we only ever need to check QtCore and QtQml
+ # for framework path
+ list(APPEND link_libs ${QT_CMAKE_EXPORT_NAMESPACE}::Core ${QT_CMAKE_EXPORT_NAMESPACE}::Qml)
+ foreach(lib ${link_libs})
+ _qt_internal_qml_get_qt_framework_path_moc_option(${lib} moc_option)
+ if(moc_option)
+ list(APPEND extra_moc_options ${moc_option})
+ endif()
+ endforeach()
+ endif()
+
+ # run MOC manually for the generated files
+ qt6_wrap_cpp(compiled_moc_files ${compiled_files} TARGET ${target} OPTIONS ${extra_moc_options})
+ set_source_files_properties(${compiled_moc_files} PROPERTIES SKIP_AUTOGEN ON
+ SKIP_UNITY_BUILD_INCLUSION ON)
+ target_sources(${target} PRIVATE ${compiled_moc_files})
+ if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set_source_files_properties(${generated_sources_other_scope} ${compiled_moc_files}
+ TARGET_DIRECTORY ${target}
+ PROPERTIES
+ SKIP_AUTOGEN TRUE
+ GENERATED TRUE
+ )
+ endif()
+
+ if(NOT TARGET ${target}_tooling)
+ message(FATAL_ERROR
+ "${target}_tooling is not found, although it should be in this function.")
+ endif()
+ # adding sources to ${target}_tooling would ensure that these sources
+ # become a dependency of ${target} in this weird case that we have.
+ # add_dependencies() for ${target} and ${target}_tooling must have been
+ # added as part of qt_add_qml_module() command run.
+ target_sources(${target}_tooling PRIVATE
+ ${generated_sources_other_scope} ${compiled_moc_files}
+ )
+ endif()
+
+endfunction()
+
+function(qt6_target_compile_qml_to_cpp target)
+ message(WARNING
+ "qt6_target_compile_qml_to_cpp() can no longer be used standalone and is planned to be "
+ "removed in a future Qt release. To enable qmltc compilation, use:\n"
+ "qt6_add_qml_module(${target} ... ENABLE_TYPE_COMPILER)"
+ )
+endfunction()
+
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_target_compile_qml_to_cpp)
+ qt6_target_compile_qml_to_cpp(${ARGV})
+ endfunction()
+endif()
+
+# Get extern declaration for types registration function.
+function(_qt_internal_qml_get_types_extern_declaration register_types_function_name out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_list_joined_with_macro(
+ content
+ "${register_types_function_name}"
+ "QT_DECLARE_EXTERN_SYMBOL_VOID"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference the types registration function in the plugin constructor.
+# Ensures the symbol is not discarded when linking.
+function(_qt_internal_qml_get_types_keep_reference register_types_function_name out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_list_joined_with_macro(
+ content
+ "${register_types_function_name}"
+ "QT_KEEP_SYMBOL"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get extern declaration for cachegen resource.
+function(_qt_internal_qml_get_cachegen_extern_resource_declaration target out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_cachegen_sanitized_resource_name"
+ "QT_DECLARE_EXTERN_RESOURCE"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference the cachegen resource in the plugin constructor.
+# Ensures the resource is not discarded when linking.
+function(_qt_internal_qml_get_cachegen_resource_keep_reference target out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_cachegen_sanitized_resource_name"
+ "QT_KEEP_RESOURCE"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get extern declaration for a regular resource.
+function(_qt_internal_qml_get_resource_extern_declarations target out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_qml_module_sanitized_resource_names"
+ "QT_DECLARE_EXTERN_RESOURCE"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get code block that should reference regular resources in the plugin constructor.
+# Ensures the resources are not discarded when linking.
+function(_qt_internal_qml_get_resource_keep_references target out_var)
+ set(with_ending_semicolon FALSE)
+ _qt_internal_genex_get_property_joined_with_macro(
+ content
+ "${target}"
+ "_qt_qml_module_sanitized_resource_names"
+ "QT_KEEP_RESOURCE"
+ ${with_ending_semicolon}
+ )
+
+ string(PREPEND content "\n")
+ set(${out_var} "${content}" PARENT_SCOPE)
+endfunction()
+
+# Get 3 different code blocks that should be inserted into various locations of the generated
+# qml plugin cpp file. The code blocks ensure that if a target links to a plugin backed by a static
+# library and initializes the plugin using Q_IMPORT_PLUGIN, none of the resources or type
+# registration functions in the backing library are discarded by the linker.
+function(_qt_internal_qml_get_symbols_to_keep
+ target
+ backing_target_type
+ register_types_function_name
+ out_var_intro
+ out_var_intro_namespaced
+ out_var_constructor)
+ set(intro_content "")
+ set(intro_namespaced_content "")
+
+ # External symbol declarations.
+ _qt_internal_qml_get_types_extern_declaration("${register_types_function_name}" output)
+ string(APPEND intro_namespaced_content "${output}")
+
+ if(backing_target_type STREQUAL "STATIC_LIBRARY")
+ _qt_internal_qml_get_cachegen_extern_resource_declaration(${target} output)
+ string(APPEND intro_content "${output}")
+
+ _qt_internal_qml_get_resource_extern_declarations(${target} output)
+ string(APPEND intro_content "${output}")
+ endif()
+
+
+ # Reference the external symbols in the plugin constructor to prevent the linker from
+ # discarding the symbols.
+ # The types symbol is an exported symbol, so we always reference it.
+ set(constructor_content "")
+
+ _qt_internal_qml_get_types_keep_reference("${register_types_function_name}" output)
+ string(APPEND constructor_content "${output}")
+
+ # Only reference the resources if the backing library is static.
+ # If the backing library is shared, the symbols will not be found at link time because they
+ # are not exported symbols.
+ if(backing_target_type STREQUAL "STATIC_LIBRARY")
+ _qt_internal_qml_get_cachegen_resource_keep_reference(${target} output)
+ string(APPEND constructor_content "${output}")
+
+ _qt_internal_qml_get_resource_keep_references(${target} output)
+ string(APPEND constructor_content "${output}")
+ endif()
+
+ set(${out_var_intro} "${intro_content}" PARENT_SCOPE)
+ set(${out_var_intro_namespaced} "${intro_namespaced_content}" PARENT_SCOPE)
+ set(${out_var_constructor} "${constructor_content}" PARENT_SCOPE)
+endfunction()
+
+function(_qt_internal_set_qml_target_multi_config_output_directory target output_directory)
+ # In multi-config builds we need to make sure that at least one configuration has the dynamic
+ # plugin that is located next to qmldir file, otherwise QML engine won't be able to load the
+ # plugin.
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ # We don't care about static plugins here since, they are linked at build time and
+ # their location doesn't affect the runtime.
+ get_target_property(target_type ${target} TYPE)
+ if(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY")
+ if(NOT "${output_directory}")
+ set(output_directory "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+
+ list(GET CMAKE_CONFIGURATION_TYPES 0 default_config)
+ string(JOIN "" output_directory_with_default_config
+ "$<IF:$<CONFIG:${default_config}>,"
+ "${output_directory},"
+ "${output_directory}/$<CONFIG>"
+ ">"
+ )
+ set_target_properties(${target} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${output_directory_with_default_config}"
+ LIBRARY_OUTPUT_DIRECTORY "${output_directory_with_default_config}"
+ )
+ endif()
+ endif()
+endfunction()
+
+function(qt6_add_qml_plugin target)
+ set(args_option
+ STATIC
+ SHARED
+ NO_GENERATE_PLUGIN_SOURCE
+ )
+
+ set(args_single
+ OUTPUT_DIRECTORY
+ URI
+ BACKING_TARGET
+ CLASS_NAME
+ NAMESPACE
+ # The following is only needed on Android, and even then, only if the
+ # default conversion from the URI is not applicable. It is an internal
+ # option, it may be removed.
+ TARGET_PATH
+ )
+
+ set(args_multi "")
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ "${args_option}"
+ "${args_single}"
+ "${args_multi}"
+ )
+
+ if(arg_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
+ if(NOT arg_URI)
+ if(NOT arg_BACKING_TARGET)
+ message(FATAL_ERROR "No URI or BACKING_TARGET provided")
+ endif()
+ if(NOT TARGET ${arg_BACKING_TARGET})
+ if(arg_BACKING_TARGET STREQUAL target)
+ message(FATAL_ERROR
+ "Plugin ${target} is its own backing target, URI must be provided"
+ )
+ else()
+ message(FATAL_ERROR
+ "No URI provided and unable to obtain it from the BACKING_TARGET "
+ "(${arg_BACKING_TARGET}) because no such target exists"
+ )
+ endif()
+ endif()
+ get_target_property(arg_URI ${arg_BACKING_TARGET} QT_QML_MODULE_URI)
+ if(NOT arg_URI)
+ message(FATAL_ERROR
+ "No URI provided and the BACKING_TARGET (${arg_BACKING_TARGET}) "
+ "does not have one set either"
+ )
+ endif()
+ endif()
+
+ # TODO: Probably should remove TARGET_PATH as a supported keyword now
+ if(NOT arg_TARGET_PATH AND TARGET "${arg_BACKING_TARGET}")
+ get_target_property(arg_TARGET_PATH ${arg_BACKING_TARGET} QT_QML_MODULE_TARGET_PATH)
+ endif()
+ if(NOT arg_TARGET_PATH)
+ string(REPLACE "." "/" arg_TARGET_PATH "${arg_URI}")
+ endif()
+
+ _qt_internal_get_escaped_uri("${arg_URI}" escaped_uri)
+
+ if(NOT arg_CLASS_NAME)
+ if(NOT "${arg_BACKING_TARGET}" STREQUAL "")
+ get_target_property(arg_CLASS_NAME ${arg_BACKING_TARGET} QT_QML_MODULE_CLASS_NAME)
+ endif()
+ if(NOT arg_CLASS_NAME)
+ _qt_internal_compute_qml_plugin_class_name_from_uri("${arg_URI}" arg_CLASS_NAME)
+ endif()
+ endif()
+
+ if(arg_BACKING_TARGET AND TARGET "${arg_BACKING_TARGET}")
+ get_target_property(backing_type ${arg_BACKING_TARGET} TYPE)
+ endif()
+
+ if(TARGET ${target})
+ # Plugin target already exists. Perform a few sanity checks, but we
+ # otherwise trust that the target is appropriate for use as a plugin.
+ get_target_property(target_type ${target} TYPE)
+ if(target_type STREQUAL "EXECUTABLE")
+ message(FATAL_ERROR "Plugins cannot be executables (target: ${target})")
+ endif()
+ foreach(arg IN ITEMS STATIC SHARED)
+ if(arg_${arg})
+ message(FATAL_ERROR
+ "Cannot specify ${arg} keyword, target ${target} already exists"
+ )
+ endif()
+ endforeach()
+
+ get_target_property(existing_class_name ${target} QT_PLUGIN_CLASS_NAME)
+ if(existing_class_name)
+ if(NOT existing_class_name STREQUAL arg_CLASS_NAME)
+ message(FATAL_ERROR
+ "An existing plugin target was given, but it has a different class name "
+ "(${existing_class_name}) to that being used here (${arg_CLASS_NAME})"
+ )
+ endif()
+ elseif(arg_CLASS_NAME)
+ set_property(TARGET ${target} PROPERTY QT_PLUGIN_CLASS_NAME "${arg_CLASS_NAME}")
+ else()
+ message(FATAL_ERROR
+ "An existing '${target}' plugin target was given, but it has no class name set "
+ "and no new class name was provided."
+ )
+ endif()
+ else()
+ if(arg_STATIC AND arg_SHARED)
+ message(FATAL_ERROR
+ "Cannot specify both STATIC and SHARED for target ${target}"
+ )
+ endif()
+ set(lib_type "")
+ if(arg_STATIC)
+ set(lib_type STATIC)
+ elseif(arg_SHARED)
+ set(lib_type SHARED)
+ endif()
+
+ if(TARGET "${arg_BACKING_TARGET}")
+ # Ensure that the plugin type we create will be compatible with the
+ # type of backing target we were given
+ if(backing_type STREQUAL "STATIC_LIBRARY")
+ if(lib_type STREQUAL "")
+ set(lib_type STATIC)
+ elseif(lib_type STREQUAL "SHARED")
+ message(FATAL_ERROR
+ "Mixing a static backing library with a non-static plugin "
+ "is not supported"
+ )
+ endif()
+ elseif(backing_type STREQUAL "SHARED_LIBRARY")
+ if(lib_type STREQUAL "")
+ set(lib_type SHARED)
+ elseif(lib_type STREQUAL "STATIC")
+ message(FATAL_ERROR
+ "Mixing a non-static backing library with a static plugin "
+ "is not supported"
+ )
+ endif()
+ elseif(backing_type STREQUAL "EXECUTABLE")
+ message(FATAL_ERROR
+ "A separate plugin should not be needed when the backing target "
+ "is an executable. Pre-create the plugin target before calling "
+ "this command if you really must have a separate plugin."
+ )
+ else()
+ # Object libraries, utility/custom targets
+ message(FATAL_ERROR "Unsupported backing target type: ${backing_type}")
+ endif()
+ endif()
+
+ qt6_add_plugin(${target} ${lib_type}
+ PLUGIN_TYPE qml_plugin
+ CLASS_NAME ${arg_CLASS_NAME}
+ )
+ endif()
+
+ get_target_property(install_rpath ${target} INSTALL_RPATH)
+ # Ignore any CMAKE_INSTALL_RPATH and set a better default RPATH on platforms
+ # that support it, if allowed. Projects will often set CMAKE_INSTALL_RPATH
+ # for executables or backing libraries, but forget about plugins. Because
+ # the path for QML plugins depends on their URI, it is unlikely that
+ # CMAKE_INSTALL_RPATH would ever be intended for use with QML plugins.
+ #
+ # Avoid setting INSTALL_RPATH if it was set before. This is mostly
+ # applicable for the Qml plugins built in Qt tree, that got INSTALL_RPATH
+ # from the qt_internal_add_plugin function, but also can be the case for the
+ # user Qml plugins created manually.
+ if(NOT WIN32 AND NOT QT_NO_QML_PLUGIN_RPATH AND NOT install_rpath)
+ # Construct a relative path from a default install location (assumed to
+ # be qml/target-path) to ${CMAKE_INSTALL_LIBDIR}. This would be
+ # applicable for Apple too (although unusual) if this is a bare install
+ # (i.e. not part of an app bundle).
+ string(REPLACE "/" ";" path "qml/${arg_TARGET_PATH}")
+ list(LENGTH path path_count)
+ string(REPEAT "../" ${path_count} rel_path)
+ string(APPEND rel_path "${CMAKE_INSTALL_LIBDIR}")
+ if(APPLE)
+ set(install_rpath
+ # If embedded in an app bundle, search in a bundle-local path
+ # first. This path should always be the same for every app
+ # bundle because plugin binaries should live in the PlugIns
+ # directory, not a subdirectory of it or anywhere else.
+ # Similarly, frameworks and bare shared libraries should always
+ # be in the bundle's Frameworks directory.
+ "@loader_path/../Frameworks"
+
+ # This will be needed if the plugin is not installed as part of
+ # an app bundle, such as when used by a command-line tool.
+ "@loader_path/${rel_path}"
+ )
+ else()
+ set(install_rpath "$ORIGIN/${rel_path}")
+ endif()
+ set_target_properties(${target} PROPERTIES INSTALL_RPATH "${install_rpath}")
+ endif()
+
+ get_target_property(moc_opts ${target} AUTOMOC_MOC_OPTIONS)
+ set(already_set FALSE)
+ if(moc_opts)
+ foreach(opt IN LISTS moc_opts)
+ if("${opt}" MATCHES "^-Muri=")
+ set(already_set TRUE)
+ break()
+ endif()
+ endforeach()
+ endif()
+ if(NOT already_set)
+ # Insert the plugin's URI into its meta data to enable usage
+ # of static plugins in QtDeclarative (like in mkspecs/features/qml_plugin.prf).
+ set_property(TARGET ${target} APPEND PROPERTY
+ AUTOMOC_MOC_OPTIONS "-Muri=${arg_URI}"
+ )
+ endif()
+
+ # Work around QTBUG-115152.
+ # Assign / duplicate the backing library's metatypes file to the qml plugin.
+ # This ensures that the metatypes are passed as foreign types to qmltyperegistrar when
+ # a consumer links against the plugin, but not the backing library.
+ # This is needed because the plugin links PRIVATEly to the backing library and thus the
+ # backing library's INTERFACE_SOURCES don't end up in the final consumer's SOURCES.
+ # Arguably doing this is cleaner than changing the linkage to PUBLIC, because that will
+ # propagate not only the INTERFACE_SOURCES, but also other link dependencies, which might
+ # be unwanted.
+ # In general this is a workaround due to CMake's limitations around support for propagating
+ # custom properties across targets to a final consumer target.
+ # https://gitlab.kitware.com/cmake/cmake/-/issues/20416
+ if(arg_BACKING_TARGET
+ AND TARGET "${arg_BACKING_TARGET}" AND NOT arg_BACKING_TARGET STREQUAL target)
+ get_target_property(plugin_meta_types_file "${target}" INTERFACE_QT_META_TYPES_BUILD_FILE)
+ get_target_property(
+ backing_meta_types_file "${arg_BACKING_TARGET}" INTERFACE_QT_META_TYPES_BUILD_FILE)
+ get_target_property(
+ backing_meta_types_file_name
+ "${arg_BACKING_TARGET}" INTERFACE_QT_META_TYPES_FILE_NAME)
+ get_target_property(backing_has_qmltypes "${arg_BACKING_TARGET}" _qt_internal_has_qmltypes)
+ if(backing_has_qmltypes AND NOT plugin_meta_types_file)
+ _qt_internal_assign_build_metatypes_files_and_properties(
+ "${target}"
+ METATYPES_FILE_NAME "${backing_meta_types_file_name}"
+ METATYPES_FILE_PATH "${backing_meta_types_file}"
+ )
+ endif()
+ endif()
+
+ if(ANDROID)
+ _qt_internal_get_qml_plugin_output_name(plugin_output_name ${target}
+ BACKING_TARGET "${arg_BACKING_TARGET}"
+ TARGET_PATH "${arg_TARGET_PATH}"
+ URI "${arg_URI}"
+ )
+ set_target_properties(${target}
+ PROPERTIES
+ LIBRARY_OUTPUT_NAME "${plugin_output_name}"
+ )
+ set_property(TARGET "${target}"
+ PROPERTY _qt_android_apply_arch_suffix_called_from_qt_impl TRUE)
+ qt6_android_apply_arch_suffix(${target})
+ endif()
+
+ if(NOT arg_OUTPUT_DIRECTORY AND arg_BACKING_TARGET AND TARGET ${arg_BACKING_TARGET})
+ get_target_property(arg_OUTPUT_DIRECTORY ${arg_BACKING_TARGET} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ endif()
+ if(arg_OUTPUT_DIRECTORY)
+ # Plugin target must be in the output directory. The backing target,
+ # if it is different to the plugin target, can be anywhere.
+ set_target_properties(${target} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
+ LIBRARY_OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
+ ARCHIVE_OUTPUT_DIRECTORY ${arg_OUTPUT_DIRECTORY}
+ )
+ endif()
+
+ _qt_internal_set_qml_target_multi_config_output_directory(${target} "${arg_OUTPUT_DIRECTORY}")
+
+ if(NOT arg_NO_GENERATE_PLUGIN_SOURCE)
+ set(generated_cpp_file_name_base "${target}_${arg_CLASS_NAME}")
+ set(register_types_function_name "qml_register_types_${escaped_uri}")
+
+ # These are all substituted in the template file used further below
+ set(qt_qml_plugin_class_name "${arg_CLASS_NAME}")
+ set(qt_qml_plugin_moc_include_name "${generated_cpp_file_name_base}.moc")
+ set(qt_qml_plugin_intro "")
+ set(qt_qml_plugin_outro "")
+ set(qt_qml_plugin_constructor_content "")
+
+ # Get the target that contains the resource names.
+ set(target_with_prop "${target}")
+ if(NOT arg_BACKING_TARGET STREQUAL "" AND TARGET "${arg_BACKING_TARGET}")
+ set(target_with_prop "${arg_BACKING_TARGET}")
+ endif()
+
+ _qt_internal_qml_get_symbols_to_keep(
+ "${target_with_prop}"
+ "${backing_type}"
+ "${register_types_function_name}"
+ extra_intro_content
+ extra_intro_namespaced_content
+ extra_costructor_content
+ )
+
+ string(APPEND qt_qml_plugin_intro "${extra_intro_content}\n\n")
+
+ if (arg_NAMESPACE)
+ string(APPEND qt_qml_plugin_intro "namespace ${arg_NAMESPACE} {\n")
+ string(APPEND qt_qml_plugin_outro "} // namespace ${arg_NAMESPACE}")
+ endif()
+
+ string(APPEND qt_qml_plugin_intro "${extra_intro_namespaced_content}")
+
+ string(APPEND qt_qml_plugin_constructor_content "${extra_costructor_content}")
+
+ # Configure file from the template.
+ set(generated_cpp_file_in
+ "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name_base}_in.cpp"
+ )
+ configure_file(
+ "${__qt_qml_macros_module_base_dir}/Qt6QmlPluginTemplate.cpp.in"
+ "${generated_cpp_file_in}"
+ @ONLY
+ )
+
+ get_target_property(template_generated ${target} _qt_qml_plugin_template_generated)
+
+ if(NOT template_generated)
+ # Generate the file to allow adding generator expressions.
+ set(generated_cpp_file
+ "${CMAKE_CURRENT_BINARY_DIR}/${generated_cpp_file_name_base}.cpp"
+ )
+ file(GENERATE OUTPUT "${generated_cpp_file}"
+ INPUT "${generated_cpp_file_in}"
+ )
+
+ # We can't rely on policy CMP0118 since user project controls it
+ set(scope_args)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set(scope_args TARGET_DIRECTORY ${target})
+ endif()
+ set_source_files_properties("${generated_cpp_file}" ${scope_args}
+ PROPERTIES GENERATED TRUE
+ )
+
+ # Because the add_qml_plugin function might be called multiple times on the same target,
+ # Only generate and add the cpp file once.
+ set_target_properties(${target} PROPERTIES _qt_qml_plugin_template_generated TRUE)
+ target_sources(${target} PRIVATE "${generated_cpp_file}")
+ endif()
+
+ # The generated cpp file expects to include its moc-ed output file.
+ set_target_properties(${target} PROPERTIES AUTOMOC TRUE)
+ endif()
+
+ target_link_libraries(${target} PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Qml)
+
+ # Link plugin against its backing lib if it has one.
+ if(NOT arg_BACKING_TARGET STREQUAL "" AND NOT arg_BACKING_TARGET STREQUAL target)
+ target_link_libraries(${target} PRIVATE ${arg_BACKING_TARGET})
+ endif()
+
+ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0")
+ # Defer the collection of plugin dependencies until after any extra target_link_libraries
+ # calls that a user project might do.
+ # We wrap the deferred call with EVAL CODE
+ # so that ${target} is evaluated now rather than the end of the scope.
+ cmake_language(EVAL CODE
+ "cmake_language(DEFER CALL _qt_internal_add_static_qml_plugin_dependencies \"${target}\" \"${arg_BACKING_TARGET}\")"
+ )
+ else()
+ # Can't defer, have to do it now.
+ _qt_internal_add_static_qml_plugin_dependencies("${target}" "${arg_BACKING_TARGET}")
+ endif()
+endfunction()
+
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_add_qml_plugin)
+ qt6_add_qml_plugin(${ARGV})
+ endfunction()
+endif()
+
+function(qt6_target_qml_sources target)
+
+ get_target_property(uri ${target} QT_QML_MODULE_URI)
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ if(NOT uri OR NOT output_dir)
+ message(FATAL_ERROR "Target ${target} is not a QML module")
+ endif()
+
+ set(args_option
+ NO_LINT
+ NO_CACHEGEN
+ NO_QMLDIR_TYPES
+ __QT_INTERNAL_FORCE_DEFER_QMLDIR # Used only by qt6_add_qml_module()
+ )
+
+ set(args_single
+ PREFIX
+ OUTPUT_TARGETS
+ )
+
+ set(args_multi
+ QML_FILES
+ RESOURCES
+ )
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ "${args_option}" "${args_single}" "${args_multi}"
+ )
+ if(arg_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "Unknown/unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
+ get_target_property(no_lint ${target} QT_QML_MODULE_NO_LINT)
+ if(NOT arg_QML_FILES AND NOT arg_RESOURCES)
+ if(NOT arg_NO_LINT AND NOT no_lint)
+ _qt_internal_target_enable_qmllint(${target})
+ endif()
+
+ if(arg_OUTPUT_TARGETS)
+ set(${arg_OUTPUT_TARGETS} "" PARENT_SCOPE)
+ endif()
+ return()
+ endif()
+
+ if(NOT arg___QT_INTERNAL_FORCE_DEFER_QMLDIR AND ${CMAKE_VERSION} VERSION_LESS "3.19.0")
+ message(FATAL_ERROR
+ "You are using CMake ${CMAKE_VERSION}, but CMake 3.19 or later "
+ "is required to add qml files with this function. Either pass "
+ "the qml files to qt6_add_qml_module() instead or update to "
+ "CMake 3.19 or later."
+ )
+ endif()
+
+ get_target_property(no_cachegen ${target} QT_QML_MODULE_NO_CACHEGEN)
+ get_target_property(no_qmldir ${target} QT_QML_MODULE_NO_GENERATE_QMLDIR)
+ get_target_property(resource_prefix ${target} QT_QML_MODULE_RESOURCE_PREFIX)
+ get_target_property(qml_module_version ${target} QT_QML_MODULE_VERSION)
+ get_target_property(past_major_versions ${target} QT_QML_MODULE_PAST_MAJOR_VERSIONS)
+
+ if(NOT output_dir)
+ # Probably not a qml module. We still want to support tooling for this
+ # scenario, it's just that we will be relying solely on the implicit
+ # imports to find things.
+ set(output_dir ${CMAKE_CURRENT_BINARY_DIR})
+ set(no_qmldir TRUE)
+ endif()
+
+ if(NOT arg_PREFIX)
+ if(resource_prefix)
+ set(arg_PREFIX ${resource_prefix})
+ else()
+ message(FATAL_ERROR
+ "PREFIX option not given and target ${target} does not have a "
+ "QT_QML_MODULE_RESOURCE_PREFIX property set."
+ )
+ endif()
+ endif()
+ _qt_internal_canonicalize_resource_path("${arg_PREFIX}" arg_PREFIX)
+ if(arg_PREFIX STREQUAL resource_prefix)
+ set(prefix_override "")
+ else()
+ set(prefix_override "${arg_PREFIX}")
+ endif()
+ if(NOT arg_PREFIX STREQUAL "/")
+ string(APPEND arg_PREFIX "/")
+ endif()
+
+ if (qml_module_version MATCHES "^([0-9]+)\\.")
+ set(qml_module_files_versions "${CMAKE_MATCH_1}.0")
+ else()
+ message(FATAL_ERROR
+ "No major version found in '${qml_module_version}'."
+ )
+ endif()
+ if (past_major_versions OR past_major_versions STREQUAL "0")
+ foreach (past_major_version ${past_major_versions})
+ list(APPEND qml_module_files_versions "${past_major_version}.0")
+ endforeach()
+ endif()
+
+ # Linting and cachegen can still occur for a target that isn't a qml module,
+ # but for such targets, there is no qmldir file to update.
+ if(arg_NO_LINT)
+ set(no_lint TRUE)
+ endif()
+ if(arg_NO_CACHEGEN)
+ set(no_cachegen TRUE)
+ endif()
+ if(no_qmldir MATCHES "NOTFOUND" OR arg_NO_QMLDIR_TYPES)
+ set(no_qmldir TRUE)
+ endif()
- # TODO: rename to QT_QML_SOURCE_VERSIONS
- get_source_file_property(qml_file_versions ${qml_file} QT_QML_SOURCE_VERSION)
- get_source_file_property(qml_file_typename ${qml_file} QT_QML_SOURCE_TYPENAME)
- get_source_file_property(qml_file_singleton ${qml_file} QT_QML_SINGLETON_TYPE)
- get_source_file_property(qml_file_internal ${qml_file} QT_QML_INTERNAL_TYPE)
- get_target_property(qml_module_version ${target} QT_QML_MODULE_VERSION)
+ if(NOT no_cachegen AND arg_QML_FILES)
- if (NOT qml_file_versions)
- set(qml_file_versions ${qml_module_version})
+ # Even if we don't generate a qmldir file, it still should be here, manually written.
+ # We can pass it unconditionally. If it's not there, qmlcachegen or qmlsc might warn,
+ # but that's not fatal.
+ set(qmldir_file ${output_dir}/qmldir)
+
+ _qt_internal_genex_getproperty(qmltypes_file ${target} QT_QML_MODULE_PLUGIN_TYPES_FILE)
+ _qt_internal_genex_getproperty(qmlcachegen ${target} QT_QMLCACHEGEN_EXECUTABLE)
+ _qt_internal_genex_getproperty(direct_calls ${target} QT_QMLCACHEGEN_DIRECT_CALLS)
+ _qt_internal_genex_getjoinedproperty(arguments ${target}
+ QT_QMLCACHEGEN_ARGUMENTS "$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ _qt_internal_genex_getjoinedproperty(import_paths ${target}
+ QT_QML_IMPORT_PATH "-I$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ _qt_internal_genex_getjoinedproperty(qrc_resource_args ${target}
+ _qt_generated_qrc_files "--resource$<SEMICOLON>" "$<SEMICOLON>"
+ )
+ get_target_property(target_type ${target} TYPE)
+ get_target_property(is_android_executable ${target} _qt_is_android_executable)
+ if(target_type STREQUAL "EXECUTABLE" OR is_android_executable)
+ # The application binary directory is part of the default import path.
+ list(APPEND import_paths -I "$<TARGET_PROPERTY:${target},BINARY_DIR>")
+ else()
+ string(REPLACE "." "/" uri_path "${uri}")
+ string(FIND "${output_dir}" "${uri_path}" position REVERSE)
+ string(LENGTH "${output_dir}" output_dir_length)
+ string(LENGTH "${uri_path}" uri_path_length)
+ math(EXPR import_path_length "${output_dir_length} - ${uri_path_length}")
+ if("${position}" EQUAL "${import_path_length}")
+ string(SUBSTRING "${output_dir}" "0" "${import_path_length}" import_path)
+ list(APPEND import_paths -I "${import_path}")
+ endif()
endif()
+ _qt_internal_extend_qml_import_paths(import_paths)
+ set(cachegen_args
+ ${import_paths}
+ -i "${qmldir_file}"
+ "$<${have_direct_calls}:--direct-calls>"
+ "$<${have_arguments}:${arguments}>"
+ ${qrc_resource_args}
+ )
- if (NOT qml_file_typename)
- get_filename_component(qml_file_typename ${qml_file} NAME_WLE)
+ # For direct evaluation in if() below
+ get_target_property(cachegen_prop ${target} QT_QMLCACHEGEN_EXECUTABLE)
+ if(cachegen_prop)
+ if(cachegen_prop STREQUAL "qmlcachegen" OR cachegen_prop STREQUAL "qmlsc")
+ # If it's qmlcachegen or qmlsc, don't go looking for other programs of that name
+ set(qmlcachegen "$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::${cachegen_prop}>")
+ else()
+ find_program(${target}_QMLCACHEGEN ${cachegen_prop})
+ if(${target}_QMLCACHEGEN)
+ set(qmlcachegen "${${target}_QMLCACHEGEN}")
+ else()
+ message(FATAL_ERROR "Invalid qmlcachegen binary ${cachegen_prop} for ${target}")
+ endif()
+ endif()
+ else()
+ set(have_qmlsc "$<TARGET_EXISTS:${QT_CMAKE_EXPORT_NAMESPACE}::qmlsc>")
+ set(cachegen_name "$<IF:${have_qmlsc},qmlsc,qmlcachegen>")
+ set(qmlcachegen "$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::${cachegen_name}>")
endif()
+ endif()
+
+ set(non_qml_cpp_files "")
+ set(non_qml_files "")
+ set(output_targets "")
+ set(copied_files "")
+
+ # We want to set source file properties in the target's own scope if we can.
+ # That's the canonical place the properties will be read from.
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)
+ set(scope_option TARGET_DIRECTORY ${target})
+ else()
+ set(scope_option "")
+ endif()
+
+ foreach(file_set IN ITEMS QML_FILES RESOURCES)
+ foreach(file_src IN LISTS arg_${file_set})
+ get_filename_component(file_absolute ${file_src} ABSOLUTE)
+
+ # Store the original files so the project can query them later.
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_${file_set} ${file_absolute}
+ )
+ if(prefix_override)
+ set_source_files_properties(${file_absolute} ${scope_option}
+ PROPERTIES
+ QT_QML_MODULE_PREFIX_OVERRIDE "${prefix_override}"
+ )
+ endif()
- foreach(qml_file_version IN LISTS qml_file_versions)
- if (qml_file_singleton)
- string(APPEND file_contents "singleton ")
+ # We need to copy the file to the build directory now so that when
+ # qmlimportscanner is run in qt6_import_qml_plugins() as part of
+ # target finalizers, the files will be there. We need to do this
+ # in a way that CMake doesn't create a dependency on the source or it
+ # will re-run CMake every time the file is modified. We also don't
+ # want to update the file's timestamp if its contents won't change.
+ # We still enforce the dependency on the source file by adding a
+ # build-time rule. This avoids having to re-run CMake just to re-copy
+ # the file.
+ __qt_get_relative_resource_path_for_file(file_resource_path ${file_src})
+ set(file_out ${output_dir}/${file_resource_path})
+
+ # Don't generate or copy the file in an in-source build if the source
+ # and destination paths are the same, it will cause a ninja dependency
+ # cycle at build time.
+ if(NOT file_out STREQUAL file_absolute)
+ get_filename_component(file_out_dir ${file_out} DIRECTORY)
+ file(MAKE_DIRECTORY ${file_out_dir})
+
+ if(EXISTS "${file_absolute}")
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.21")
+ # Significantly increases copying speed according to profiling, presumably
+ # because we bypass process creation.
+ file(COPY_FILE "${file_absolute}" "${file_out}" ONLY_IF_DIFFERENT)
+ else()
+ execute_process(COMMAND
+ ${CMAKE_COMMAND} -E copy_if_different ${file_absolute} ${file_out}
+ )
+ endif()
+ endif()
+
+ add_custom_command(OUTPUT ${file_out}
+ COMMAND ${CMAKE_COMMAND} -E copy ${file_absolute} ${file_out}
+ DEPENDS ${file_absolute}
+ WORKING_DIRECTORY $<TARGET_PROPERTY:${target},SOURCE_DIR>
+ VERBATIM
+ COMMENT "Copying ${file_src} to ${file_out}"
+ )
+ list(APPEND copied_files ${file_out})
endif()
- string(APPEND file_contents "${qml_file_typename} ${qml_file_version} ${qml_file}\n")
endforeach()
+ endforeach()
- if (qml_file_internal)
- string(APPEND file_contents "internal ${qml_file_typename} ${qml_file}\n")
+ set(generated_sources_other_scope)
+ set(extra_qmldirs)
+ foreach(qml_file_src IN LISTS arg_QML_FILES)
+ # This is to facilitate updating code that used the earlier tech preview
+ # API function qt6_target_qml_files()
+ if(NOT qml_file_src MATCHES "\\.(js|mjs|qml)$")
+ if(qml_file_src MATCHES "\\.(cpp|cxx|cc|c|c\\+\\+|h|hh|hxx|hpp|h\\+\\+)")
+ list(APPEND non_qml_cpp_files "${qml_file_src}")
+ else()
+ list(APPEND non_qml_files "${qml_file_src}")
+ endif()
+ continue()
+ endif()
+
+ # Mark QML files as source files, so that they do not appear in <Other Locations> in Creator
+ # or other IDEs
+ set_source_files_properties(${qml_file_src} HEADER_FILE_ONLY ON)
+ target_sources(${target} PRIVATE ${qml_file_src})
+
+ get_filename_component(file_absolute ${qml_file_src} ABSOLUTE)
+ __qt_get_relative_resource_path_for_file(file_resource_path ${qml_file_src})
+
+ get_filename_component(qml_file_resource_dir ${file_resource_path} DIRECTORY)
+ if(qml_file_resource_dir)
+ list(APPEND extra_qmldirs "${output_dir}/${qml_file_resource_dir}/qmldir")
+ endif()
+
+ # For the tooling steps below, run the tools on the copied qml file in
+ # the build directory, not the source directory. This is required
+ # because the tools may need to reference imported modules from
+ # subdirectories, which would require those subdirectories to have
+ # their generated qmldir files present. They also need to use the right
+ # resource paths and the source locations might be structured quite
+ # differently.
+
+ # Add file to those processed by qmllint
+ get_source_file_property(skip_qmllint ${qml_file_src} QT_QML_SKIP_QMLLINT)
+ if(NOT no_lint AND NOT skip_qmllint)
+ # The set of qml files to run qmllint on may be a subset of the
+ # full set of files, so record these in a separate property.
+ _qt_internal_target_enable_qmllint(${target})
+ set_property(TARGET ${target} APPEND PROPERTY QT_QML_LINT_FILES ${file_absolute})
endif()
+ # Add qml file's type to qmldir
+ get_source_file_property(skip_qmldir ${qml_file_src} QT_QML_SKIP_QMLDIR_ENTRY)
+ if(NOT no_qmldir AND NOT skip_qmldir)
+ get_source_file_property(qml_file_typename ${qml_file_src} QT_QML_SOURCE_TYPENAME)
+ if (NOT qml_file_typename)
+ get_filename_component(qml_file_ext ${qml_file_src} EXT)
+ get_filename_component(qml_file_typename ${qml_file_src} NAME_WE)
+ endif()
+
+ # Do not add qmldir entries for lowercase names. Those are not components.
+ if (qml_file_typename AND qml_file_typename MATCHES "^[A-Z]")
+ if (qml_file_ext AND NOT qml_file_ext STREQUAL ".qml" AND NOT qml_file_ext STREQUAL ".ui.qml"
+ AND NOT qml_file_ext STREQUAL ".js" AND NOT qml_file_ext STREQUAL ".mjs")
+ message(AUTHOR_WARNING
+ "${qml_file_src} has a file extension different from .qml, .ui.qml, .js, "
+ "and .mjs. This leads to unexpected component names."
+ )
+ endif()
+
+ # We previously accepted the singular form of this property name
+ # during tech preview. Issue a warning for that, but still
+ # honor it. The plural form will override it if both are set.
+ get_property(have_singular_property SOURCE ${qml_file_src}
+ PROPERTY QT_QML_SOURCE_VERSION SET
+ )
+ if(have_singular_property)
+ message(AUTHOR_WARNING
+ "The QT_QML_SOURCE_VERSION source file property has been replaced "
+ "by QT_QML_SOURCE_VERSIONS (i.e. plural rather than singular). "
+ "The singular form will eventually be removed, please update "
+ "the project to use the plural form instead for the file at:\n"
+ " ${qml_file_src}"
+ )
+ endif()
+ get_source_file_property(qml_file_versions ${qml_file_src} QT_QML_SOURCE_VERSIONS)
+ if(NOT qml_file_versions AND have_singular_property)
+ get_source_file_property(qml_file_versions ${qml_file_src} QT_QML_SOURCE_VERSION)
+ endif()
+
+ get_source_file_property(qml_file_singleton ${qml_file_src} QT_QML_SINGLETON_TYPE)
+ get_source_file_property(qml_file_internal ${qml_file_src} QT_QML_INTERNAL_TYPE)
+
+ if (qml_file_singleton AND qml_file_internal)
+ message(FATAL_ERROR
+ "${qml_file_src} is marked as both internal and as a "
+ "singleton, but singletons cannot be internal!")
+ endif()
+
+ if (NOT qml_file_versions)
+ set(qml_file_versions ${qml_module_files_versions})
+ endif()
+
+ set(qmldir_file_contents "")
+ foreach(qml_file_version IN LISTS qml_file_versions)
+ if (qml_file_singleton)
+ string(APPEND qmldir_file_contents "singleton ")
+ elseif (qml_file_internal)
+ continue()
+ endif()
+ string(APPEND qmldir_file_contents "${qml_file_typename} ${qml_file_version} ${file_resource_path}\n")
+ endforeach()
+
+ if (qml_file_internal)
+ # TODO: Remove when all qmldir parsers can parse internal types with versions.
+ # Instead handle internal types like singletons above.
+ # See QTCREATORBUG-28755
+ string(APPEND qmldir_file_contents "internal ${qml_file_typename} ${file_resource_path}\n")
+ endif()
+
+ set_property(TARGET ${target} APPEND_STRING PROPERTY
+ _qt_internal_qmldir_content "${qmldir_file_contents}"
+ )
+ endif()
+ endif()
+
+ # Run cachegen on the qml file, or if disabled, store the raw qml file in the resources
+ get_source_file_property(skip_cachegen ${qml_file_src} QT_QML_SKIP_CACHEGEN)
+ if(NOT no_cachegen AND NOT skip_cachegen)
+ # We delay this to here to ensure that we only ever enable cachegen
+ # after we know there will be at least one file to compile.
+ get_target_property(is_cachegen_set_up ${target} _qt_cachegen_set_up)
+ if(NOT is_cachegen_set_up)
+ _qt_internal_target_enable_qmlcachegen(${target} ${qmlcachegen})
+ endif()
+
+ # We ensured earlier that arg_PREFIX always ends with "/"
+ file(TO_CMAKE_PATH "${arg_PREFIX}${file_resource_path}" file_resource_path)
+
+ set_property(TARGET ${target} APPEND PROPERTY
+ QT_QML_MODULE_RESOURCE_PATHS ${file_resource_path}
+ )
+
+ file(RELATIVE_PATH file_relative ${CMAKE_CURRENT_SOURCE_DIR} ${file_absolute})
+ string(REGEX REPLACE "\\.(js|mjs|qml)$" "_\\1" compiled_file ${file_relative})
+ string(REGEX REPLACE "[$#?]+" "_" compiled_file ${compiled_file})
+
+ # The file name needs to be unique to work around an Integrity compiler issue.
+ # Search for INTEGRITY_SYMBOL_UNIQUENESS in this file for details.
+ set(compiled_file
+ "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/${target}_${compiled_file}.cpp")
+ get_filename_component(out_dir ${compiled_file} DIRECTORY)
+
+ if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
+ set(qmlcachegen_cmd "$<COMMAND_CONFIG:${qmlcachegen}>")
+ else()
+ set(qmlcachegen_cmd "${qmlcachegen}")
+ endif()
+
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+ add_custom_command(
+ OUTPUT ${compiled_file}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${out_dir}
+ COMMAND
+ ${tool_wrapper}
+ ${qmlcachegen_cmd}
+ --bare
+ --resource-path "${file_resource_path}"
+ ${cachegen_args}
+ -o "${compiled_file}"
+ "${file_absolute}"
+ COMMAND_EXPAND_LISTS
+ DEPENDS
+ ${qmlcachegen_cmd}
+ "${file_absolute}"
+ $<TARGET_PROPERTY:${target},_qt_generated_qrc_files>
+ "$<$<BOOL:${qmltypes_file}>:${qmltypes_file}>"
+ "${qmldir_file}"
+ VERBATIM
+ )
+
+ target_sources(${target} PRIVATE ${compiled_file})
+ set_source_files_properties(${compiled_file} PROPERTIES
+ SKIP_AUTOGEN ON
+ )
+ # The current scope automatically sees the file as generated, but the
+ # target scope may not if it is different. Force it where we can.
+ # We will also have to add the generated file to a target in this
+ # scope at the end to ensure correct dependencies.
+ get_target_property(target_source_dir ${target} SOURCE_DIR)
+ if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ list(APPEND generated_sources_other_scope ${compiled_file})
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set_source_files_properties(
+ ${compiled_file}
+ TARGET_DIRECTORY ${target}
+ PROPERTIES
+ SKIP_AUTOGEN TRUE
+ GENERATED TRUE
+ )
+ endif()
+ endif()
+ endif()
endforeach()
- _qt_internal_qmldir_defer_file(APPEND "${qmldir_file}" "${file_contents}")
+
+ if(ANDROID)
+ _qt_internal_collect_qml_root_paths("${target}" ${arg_QML_FILES})
+ endif()
+
+ if(non_qml_files OR non_qml_cpp_files)
+ if(non_qml_cpp_files)
+ list(JOIN non_qml_cpp_files "\n " file_list)
+ set(wrong_sources "\nwith SOURCES:\n ${file_list}"
+ )
+ endif()
+ if(non_qml_files)
+ list(JOIN non_qml_files "\n " file_list)
+ set(wrong_resources "\nwith RESOURCES:\n ${file_list}")
+ endif()
+
+ message(WARNING "Only .qml, .js or .mjs files should be added with QML_FILES. "
+ "The following files should be added${wrong_sources}${wrong_resources}")
+ endif()
+
+ if(copied_files OR generated_sources_other_scope)
+ if(CMAKE_VERSION VERSION_LESS 3.19)
+ # Called from qt6_add_qml_module() and we know there can only be
+ # this one call. With those constraints, we can use a custom target
+ # to implement the necessary dependencies to get files copied to the
+ # build directory when their source files change.
+ add_custom_target(${target}_tooling ALL
+ DEPENDS
+ ${copied_files}
+ ${generated_sources_other_scope}
+ )
+ add_dependencies(${target} ${target}_tooling)
+ else()
+ # We could be called multiple times and a custom target can only
+ # have file-level dependencies added at the time the target is
+ # created. Use an interface library instead, since we can add
+ # private sources to those and have the library act as a build
+ # system target from CMake 3.19 onward, and we can add the sources
+ # progressively over multiple calls.
+ if(NOT TARGET ${target}_tooling)
+ add_library(${target}_tooling INTERFACE)
+ add_dependencies(${target} ${target}_tooling)
+ endif()
+ target_sources(${target}_tooling PRIVATE
+ ${copied_files}
+ ${generated_sources_other_scope}
+ )
+ endif()
+ _qt_internal_assign_to_internal_targets_folder(${target}_tooling)
+ endif()
+
+ # Batch all the non-compiled qml sources into a single resource for this
+ # call. Subsequent calls for the same target will be in their own separate
+ # resource file.
+ get_target_property(counter ${target} QT_QML_MODULE_RAW_QML_SETS)
+ if(NOT counter)
+ set(counter 0)
+ endif()
+ set(resource_name ${target}_raw_qml_${counter})
+ set(resource_targets)
+ qt6_add_resources(${target} ${resource_name}
+ PREFIX ${arg_PREFIX}
+ FILES ${arg_QML_FILES} ${arg_RESOURCES}
+ OUTPUT_TARGETS resource_targets
+ )
+ list(APPEND output_targets ${resource_targets})
+
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_resource_name "${resource_name}")
+ set_property(TARGET ${target}
+ APPEND PROPERTY _qt_qml_module_sanitized_resource_names "${sanitized_resource_name}")
+
+ if(extra_qmldirs)
+ list(REMOVE_DUPLICATES extra_qmldirs)
+ __qt_internal_setup_policy(QTP0004 "6.8.0"
+"You need qmldir files for each extra directory that contains .qml files for your module. \
+Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0004.html for policy details."
+ )
+ qt6_policy(GET QTP0004 generate_extra_qmldirs_policy)
+ if ("${generate_extra_qmldirs_policy}" STREQUAL "NEW")
+ foreach(extra_qmldir IN LISTS extra_qmldirs)
+ set(__qt_qmldir_content "prefer :${arg_PREFIX}")
+ configure_file(
+ ${__qt_qml_macros_module_base_dir}/Qt6qmldirTemplate.cmake.in
+ "${extra_qmldir}"
+ @ONLY
+ )
+
+ set_source_files_properties("${extra_qmldir}"
+ PROPERTIES GENERATED TRUE
+ )
+ endforeach()
+
+ set(extra_qmldirs_targets)
+ qt6_add_resources(${target} "${resource_name}_extra_qmldirs"
+ PREFIX ${arg_PREFIX}
+ FILES ${extra_qmldirs}
+ BASE ${output_dir}
+ OUTPUT_TARGETS extra_qmldirs_targets
+ )
+ list(APPEND output_targets ${extra_qmldirs_targets})
+
+ set_property(TARGET ${target} PROPERTY _qt_internal_extra_qmldirs ${extra_qmldirs})
+
+ # Save the resource name in a property so we can reference it later in a qml plugin
+ # constructor, to avoid discarding the resource if it's in a static library.
+ __qt_internal_sanitize_resource_name(
+ sanitized_extra_qmldirs_resource_name "${resource_name}_extra_qmldirs")
+ set_property(TARGET ${target}
+ APPEND PROPERTY _qt_qml_module_sanitized_resource_names
+ "${sanitized_extra_qmldirs_resource_name}")
+ endif()
+ endif()
+
+ math(EXPR counter "${counter} + 1")
+ set_target_properties(${target} PROPERTIES QT_QML_MODULE_RAW_QML_SETS ${counter})
+
+ if(arg_OUTPUT_TARGETS)
+ set(${arg_OUTPUT_TARGETS} ${output_targets} PARENT_SCOPE)
+ endif()
+
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
- function(qt_target_qml_files)
- qt6_target_qml_files(${ARGV})
+ function(qt_target_qml_sources)
+ qt6_target_qml_sources(${ARGV})
+ cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "")
+ if(arg_OUTPUT_TARGETS)
+ set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE)
+ endif()
endfunction()
endif()
-# QT_QMLTYPES_FILENAME: If the target has the target property QT_QMLTPYES_FILENAME set, it will be
-# used for the name of the generated file. Otherwise, the file will be named plugins.qmltypes if the
-# target is a plugin, or ${target}.qmltypes in all other cases
-# This function is currently in Technical Preview.
-# It's signature and behavior might change.
-# MANUAL_MOC_JSON_FILES specifies a list of json files, generated by manual moc call,
-# to extract metatypes.
-function(qt6_qml_type_registration target)
- cmake_parse_arguments(arg "" "" "MANUAL_MOC_JSON_FILES" ${ARGN})
+function(qt6_generate_foreign_qml_types source_target destination_qml_target)
+ qt6_extract_metatypes(${source_target})
+ get_target_property(target_metatypes_json_file ${source_target}
+ INTERFACE_QT_META_TYPES_BUILD_FILE)
+ if (NOT target_metatypes_json_file)
+ message(FATAL_ERROR "Need target metatypes.json file")
+ endif()
+
+ get_target_property(automoc_enabled ${destination_qml_target} AUTOMOC)
+ if(NOT automoc_enabled)
+ message(FATAL_ERROR "qt6_generate_foreign_qml_types requires AUTOMOC to be enabled "
+ "on target '${destination_qml_target}'.")
+ endif()
+
+ set(registration_files_base ${source_target}_${destination_qml_target})
+ set(additional_sources
+ "${CMAKE_CURRENT_BINARY_DIR}/${registration_files_base}.cpp"
+ "${CMAKE_CURRENT_BINARY_DIR}/${registration_files_base}.h"
+ )
+
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+ add_custom_command(
+ OUTPUT
+ ${additional_sources}
+ DEPENDS
+ ${source_target}
+ ${target_metatypes_json_file}
+ ${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar
+ COMMAND
+ ${tool_wrapper}
+ $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar>
+ "--extract"
+ -o ${registration_files_base}
+ ${target_metatypes_json_file}
+ COMMENT "Generate QML registration code for target ${source_target}"
+ VERBATIM
+ )
+
+ target_sources(${destination_qml_target} PRIVATE ${additional_sources})
+endfunction()
+
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ if(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
+ function(qt_generate_foreign_qml_types)
+ qt6_generate_foreign_qml_types(${ARGV})
+ endfunction()
+ else()
+ message(FATAL_ERROR "qt_generate_foreign_qml_types() is only available in Qt 6.")
+ endif()
+endif()
+
+# target: Expected to be the backing target for a qml module. Certain target
+# properties normally set by qt6_add_qml_module() will be retrieved from this
+# target. (REQUIRED)
+#
+# MANUAL_MOC_JSON_FILES: Specifies a list of json files, generated by a manual
+# moc call, to extract metatypes. (OPTIONAL)
+#
+# NAMESPACE: Specifies a namespace the type registration function shall be
+# generated into. (OPTIONAL)
+#
+# REGISTRATIONS_TARGET: Specifies a separate target the generated .cpp file
+# shall be added to. If not given, the main backing target is used. (OPTIONAL)
+#
+function(_qt_internal_qml_type_registration target)
+ set(args_option)
+ set(args_single NAMESPACE REGISTRATIONS_TARGET)
+ set(args_multi MANUAL_MOC_JSON_FILES)
+
+ get_target_property(skipped ${target} _qt_is_skipped_test)
+ if(skipped)
+ return()
+ endif()
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ "${args_option}" "${args_single}" "${args_multi}"
+ )
+ if(arg_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR "Unknown/unexpected arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
get_target_property(import_name ${target} QT_QML_MODULE_URI)
if (NOT import_name)
message(FATAL_ERROR "Target ${target} is not a QML module")
endif()
- get_target_property(qmltypes_output_name ${target} QT_QMLTYPES_FILENAME)
+ get_target_property(qmltypes_output_name ${target} QT_QML_MODULE_TYPEINFO)
if (NOT qmltypes_output_name)
get_target_property(compile_definitions_list ${target} COMPILE_DEFINITIONS)
list(FIND compile_definitions_list QT_PLUGIN is_a_plugin)
@@ -698,23 +2917,14 @@ function(qt6_qml_type_registration target)
endif()
endif()
- # Horrible hack workaround to not install metatypes.json files for examples/ qml plugins into
- # ${qt_prefix}/lib/meta_types.
- # Put them into QT_QML_MODULE_INSTALL_DIR/lib/meta_types instead.
- get_target_property(qml_install_dir ${target} QT_QML_MODULE_INSTALL_DIR)
set(meta_types_json_args "")
-
- if(QT_BUILDING_QT AND QT_WILL_INSTALL AND qml_install_dir AND
- qml_install_dir MATCHES "^${INSTALL_EXAMPLESDIR}")
- set(meta_types_json_args "INSTALL_DIR" "${qml_install_dir}/lib/metatypes")
- endif()
-
if(arg_MANUAL_MOC_JSON_FILES)
list(APPEND meta_types_json_args "MANUAL_MOC_JSON_FILES" ${arg_MANUAL_MOC_JSON_FILES})
endif()
qt6_extract_metatypes(${target} ${meta_types_json_args})
get_target_property(import_version ${target} QT_QML_MODULE_VERSION)
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
get_target_property(target_source_dir ${target} SOURCE_DIR)
get_target_property(target_binary_dir ${target} BINARY_DIR)
get_target_property(target_metatypes_file ${target} INTERFACE_QT_META_TYPES_BUILD_FILE)
@@ -722,21 +2932,16 @@ function(qt6_qml_type_registration target)
message(FATAL_ERROR "Target ${target} does not have a meta types file")
endif()
- # Extract major and minor version
- if (NOT import_version MATCHES "[0-9]+\\.[0-9]+")
- message(FATAL_ERROR "Invalid module version number. Expected VersionMajor.VersionMinor.")
- endif()
- #string(FIND "${import_version}" "." dot_location)
- #string(SUBSTRING ${import_version} 0 ${dot_location} major_version)
- #math(EXPR dot_location "${dot_location}+1")
- #string(SUBSTRING ${import_version} ${dot_location} -1 minor_version)
- string(REPLACE "." ";" import_version_split "${import_version}")
- list(LENGTH import_version_split import_version_split_length)
- if(import_version_split_length GREATER 0)
- list(GET import_version_split 0 major_version)
- endif()
- if(import_version_split_length GREATER 1)
- list(GET import_version_split 1 minor_version)
+ # Extract major and minor version (could also have patch part, but we don't
+ # need that here)
+ if (import_version MATCHES "^([0-9]+)\\.([0-9]+)")
+ set(major_version ${CMAKE_MATCH_1})
+ set(minor_version ${CMAKE_MATCH_2})
+ else()
+ message(FATAL_ERROR
+ "Invalid module version number '${import_version}'. "
+ "Expected VersionMajor.VersionMinor."
+ )
endif()
# check if plugins.qmltypes is already defined
@@ -746,12 +2951,19 @@ function(qt6_qml_type_registration target)
endif()
set(cmd_args)
- set(plugin_types_file "${target_binary_dir}/${qmltypes_output_name}")
- set(generated_marker_file "${target_binary_dir}/.generated/${qmltypes_output_name}")
+ set(plugin_types_file "${output_dir}/${qmltypes_output_name}")
+ set(generated_marker_file "${target_binary_dir}/.qt/qmltypes/${qmltypes_output_name}")
get_filename_component(generated_marker_dir "${generated_marker_file}" DIRECTORY)
set_target_properties(${target} PROPERTIES
QT_QML_MODULE_PLUGIN_TYPES_FILE ${plugin_types_file}
)
+
+ if (arg_NAMESPACE)
+ list(APPEND cmd_args
+ --namespace=${arg_NAMESPACE}
+ )
+ endif()
+
list(APPEND cmd_args
--generate-qmltypes=${plugin_types_file}
--import-name=${import_name}
@@ -759,8 +2971,19 @@ function(qt6_qml_type_registration target)
--minor-version=${minor_version}
)
+
+ # Add --follow-foreign-versioning if requested
+ get_target_property(follow_foreign_versioning ${target}
+ _qt_qml_module_follow_foreign_versioning)
+
+ if (follow_foreign_versioning)
+ list(APPEND cmd_args
+ --follow-foreign-versioning
+ )
+ endif()
+
# Add past minor versions
- get_target_property(past_major_versions ${target} QT_QML_PAST_MAJOR_VERSIONS)
+ get_target_property(past_major_versions ${target} QT_QML_MODULE_PAST_MAJOR_VERSIONS)
if (past_major_versions OR past_major_versions STREQUAL "0")
foreach (past_major_version ${past_major_versions})
@@ -770,21 +2993,12 @@ function(qt6_qml_type_registration target)
endforeach()
endif()
-
# Run a script to recursively evaluate all the metatypes.json files in order
# to collect all foreign types.
string(TOLOWER "${target}_qmltyperegistrations.cpp" type_registration_cpp_file_name)
set(foreign_types_file "${target_binary_dir}/qmltypes/${target}_foreign_types.txt")
set(type_registration_cpp_file "${target_binary_dir}/${type_registration_cpp_file_name}")
- set(dependency_file_cpp "${target_binary_dir}/qmltypes/${type_registration_cpp_file_name}.d")
- file(RELATIVE_PATH cpp_file_name "${${CMAKE_PROJECT_NAME}_BINARY_DIR}" "${type_registration_cpp_file}")
-
- set (use_dep_files FALSE)
- if (CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "Ninja Multi-Config")
- set(use_dep_files TRUE)
- endif()
-
# Enable evaluation of metatypes.json source interfaces
set_target_properties(${target} PROPERTIES QT_CONSUMES_METATYPES TRUE)
set(genex_list "$<REMOVE_DUPLICATES:$<FILTER:$<TARGET_PROPERTY:${target},SOURCES>,INCLUDE,metatypes.json$>>")
@@ -799,6 +3013,11 @@ function(qt6_qml_type_registration target)
if (TARGET ${target}Private)
list(APPEND cmd_args --private-includes)
+ else()
+ string(REGEX MATCH "Private$" privateSuffix ${target})
+ if (privateSuffix)
+ list(APPEND cmd_args --private-includes)
+ endif()
endif()
get_target_property(target_metatypes_json_file ${target} INTERFACE_QT_META_TYPES_BUILD_FILE)
@@ -806,20 +3025,7 @@ function(qt6_qml_type_registration target)
message(FATAL_ERROR "Need target metatypes.json file")
endif()
- set(registration_cpp_file_dep_args)
- if (use_dep_files)
- set(registration_cpp_file_dep_args DEPFILE ${dependency_file_cpp})
- file(GENERATE OUTPUT "${dependency_file_cpp}"
- CONTENT "${cpp_file_name}: $<IF:$<BOOL:${genex_list}>,\\\n$<JOIN:${genex_list}, \\\n>, \\\n>"
- )
- endif()
-
- set(extra_env_command)
- if (WIN32)
- # TODO: FIXME: The env path is wrong when not building Qt, but a standalone example.
- file(TO_NATIVE_PATH "${${PROJECT_NAME}_BINARY_DIR}/bin$<SEMICOLON>${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR}$<SEMICOLON>%PATH%" env_path_native)
- set(extra_env_command COMMAND set \"PATH=${env_path_native}\")
- endif()
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
add_custom_command(
OUTPUT
${type_registration_cpp_file}
@@ -829,8 +3035,8 @@ function(qt6_qml_type_registration target)
${target_metatypes_json_file}
${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar
"$<$<BOOL:${genex_list}>:${genex_list}>"
- ${extra_env_command}
COMMAND
+ ${tool_wrapper}
$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar>
${cmd_args}
-o ${type_registration_cpp_file}
@@ -839,11 +3045,58 @@ function(qt6_qml_type_registration target)
${CMAKE_COMMAND} -E make_directory "${generated_marker_dir}"
COMMAND
${CMAKE_COMMAND} -E touch "${generated_marker_file}"
- ${registration_cpp_file_dep_args}
COMMENT "Automatic QML type registration for target ${target}"
+ VERBATIM
)
- target_sources(${target} PRIVATE ${type_registration_cpp_file})
+ # The ${target}_qmllint targets need to depend on the generation of all
+ # *.qmltypes files in the build. We have no way of reliably working out
+ # which QML modules a given target depends on at configure time, so we
+ # have to be conservative and make ${target}_qmllint targets depend on all
+ # *.qmltypes files. We need to provide a target for those dependencies
+ # here. Note that we can't use ${target} itself for those dependencies
+ # because the user might want to run qmllint without having to build the
+ # QML module.
+ add_custom_target(${target}_qmltyperegistration
+ DEPENDS
+ ${type_registration_cpp_file}
+ ${plugin_types_file}
+ )
+ _qt_internal_assign_to_internal_targets_folder(${target}_qmltyperegistration)
+ if(NOT TARGET all_qmltyperegistrations)
+ add_custom_target(all_qmltyperegistrations)
+ _qt_internal_assign_to_internal_targets_folder(all_qmltyperegistrations)
+ endif()
+ add_dependencies(all_qmltyperegistrations ${target}_qmltyperegistration)
+
+ set(effective_target ${target})
+ if(arg_REGISTRATIONS_TARGET)
+ set(effective_target ${arg_REGISTRATIONS_TARGET})
+ endif()
+
+ # Both ${effective_target} (via target_sources) and ${target}_qmltyperegistration (via
+ # add_custom_target DEPENDS option) depend on ${type_registration_cpp_file}.
+ # The new Xcode build system requires a common target to drive the generation of files,
+ # otherwise project configuration fails.
+ # Make ${effective_target} the common target, by adding it as a dependency for
+ # ${target}_qmltyperegistration.
+ # The consequence is that the ${target}_qmllint target will now first build ${effective_target}
+ # when using the Xcode generator (mostly only relevant for projects using Qt for iOS).
+ # See QTBUG-95763.
+ if(CMAKE_GENERATOR STREQUAL "Xcode")
+ add_dependencies(${target}_qmltyperegistration ${effective_target})
+ endif()
+
+ target_sources(${effective_target} PRIVATE ${type_registration_cpp_file})
+
+ # FIXME: The generated .cpp file has usually lost the path information for
+ # the headers it #include's. Since these generated .cpp files are in
+ # the build directory away from those headers, the header search path
+ # has to be augmented to ensure they can be found. We don't know what
+ # paths are needed, but add the source directory to at least handle
+ # the common case of headers in the same directory as the target.
+ # See QTBUG-93443.
+ target_include_directories(${effective_target} PRIVATE ${target_source_dir})
# Circumvent "too many sections" error when doing a 32 bit debug build on Windows with
# MinGW.
@@ -857,441 +3110,1195 @@ function(qt6_qml_type_registration target)
SKIP_AUTOGEN ON
${additional_source_files_properties}
)
-
- # Usually for Qt Qml-like modules and qml plugins, the installation destination of the .qmltypes
- # file is somewhere under the ${qt_prefix}/qml (Qt qml import path).
- #
- # For user-written qml plugins, the file should be installed next to the
- # binary / library, and not the Qt qml import path.
- #
- # Unfortunately CMake doesn't provide a way to query where a binary will be installed, so the
- # only way to know where to install is to request the installation path via a property.
- #
- # Thus only install the qmltypes file if an explicit path via the QT_QML_MODULE_INSTALL_DIR
- # property has been provided. Otherwise if installation is requested, and no path is provided,
- # warn the user, and don't install the file.
- get_target_property(install_qmltypes ${target} QT_QML_MODULE_INSTALL_QMLTYPES)
- if (install_qmltypes)
- if(qml_install_dir)
- if(NOT DEFINED QT_WILL_INSTALL OR QT_WILL_INSTALL)
- install(FILES ${plugin_types_file} DESTINATION "${qml_install_dir}")
- else()
- # Need to make the path absolute during a Qt non-prefix build, otherwise files are
- # written to the source dir because the paths are relative to the source dir.
- if(NOT IS_ABSOLUTE "${qml_install_dir}")
- set(qml_install_dir "${CMAKE_INSTALL_PREFIX}/${qml_install_dir}")
- endif()
-
- add_custom_command(TARGET ${target} POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${plugin_types_file}"
- "${qml_install_dir}/${qmltypes_output_name}"
- COMMENT "Copying ${plugin_types_file} to ${qml_install_dir}"
- )
- endif()
- else()
- message(AUTHOR_WARNING
- "No QT_QML_MODULE_INSTALL_DIR property value provided for the '${target}' target. "
- "Please either provide a value, or don't set the "
- "QT_QML_MODULE_INSTALL_QMLTYPES property. "
- "Skipping installation of '${qmltypes_output_name}'.")
- endif()
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+ set_source_files_properties(
+ ${type_registration_cpp_file}
+ TARGET_DIRECTORY ${effective_target}
+ PROPERTIES
+ SKIP_AUTOGEN TRUE
+ GENERATED TRUE
+ ${additional_source_files_properties}
+ )
endif()
- target_include_directories(${target} PRIVATE
- $<TARGET_PROPERTY:Qt::QmlPrivate,INTERFACE_INCLUDE_DIRECTORIES>
+ target_include_directories(${effective_target} PRIVATE
+ $<TARGET_PROPERTY:${QT_CMAKE_EXPORT_NAMESPACE}::QmlPrivate,INTERFACE_INCLUDE_DIRECTORIES>
+ )
+endfunction()
+
+function(qt6_qml_type_registration)
+ message(FATAL_ERROR
+ "This function, previously available under Technical Preview, has been removed. "
+ "Please use qt6_add_qml_module() instead."
)
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_qml_type_registration)
- qt6_qml_type_registration(${ARGV})
+ message(FATAL_ERROR
+ "This function, previously available under Technical Preview, has been removed. "
+ "Please use qt_add_qml_module() instead."
+ )
endfunction()
endif()
-
-# Enable the _qt_internal_quick_compiler_process_resources function in qt6_add_resource()
-set(QT6_ADD_RESOURCE_DECLARATIVE_EXTENSIONS TRUE)
-
-# Inspect all files passed to a call to qt_add_resource. If there are any
-# files present, invoke the quick compiler and return the remaining resource
-# files that have not been processed in OUTPUT_REMAINING_RESOURCES as well as the new
-# name for the resource in OUTPUT_RESOURCE_NAME.
-function(_qt_internal_quick_compiler_process_resources target resource_name)
-
- cmake_parse_arguments(arg
- "" "PREFIX;OUTPUT_REMAINING_RESOURCES;OUTPUT_RESOURCE_NAME;OUTPUT_GENERATED_TARGET" "FILES" ${ARGN}
- )
-
- set(qml_files)
- set(resource_files)
- # scan for qml files
- foreach(file IN LISTS arg_FILES)
- # check whether this resource should not be processed by the qt quick
- # compiler
- get_source_file_property(skip_compiler_check ${file} QT_SKIP_QUICKCOMPILER)
- if (skip_compiler_check)
- list(APPEND resource_files ${file})
- continue()
+# Get Qt provided QML import paths.
+# The appending order is important. Build dirs are preferred over install dirs.
+function(_qt_internal_get_main_qt_qml_import_paths out_var)
+ set(qml_import_paths "")
+
+ # When building a qml module as part of a prefix Qt build that is not yet installed, add an
+ # extra import path which is the current module's build dir: we need
+ # this to ensure correct importing of QML modules when having a prefix-build
+ # with QLibraryInfo::path(QLibraryInfo::QmlImportsPath) pointing to the
+ # install location.
+ if(QT_BUILDING_QT AND QT_WILL_INSTALL)
+ set(build_dir_import_path "${QT_BUILD_DIR}/${INSTALL_QMLDIR}")
+ if(IS_DIRECTORY "${build_dir_import_path}")
+ list(APPEND qml_import_paths "${build_dir_import_path}")
endif()
+ endif()
- if (${file} MATCHES "\.js$"
- OR ${file} MATCHES "\.mjs$"
- OR ${file} MATCHES "\.qml$")
- list(APPEND qml_files ${file})
- endif()
- list(APPEND resource_files ${file})
- endforeach()
+ # We could have multiple additional installation prefixes: one per Qt repository (conan).
+ # Or just extra ones, that might be needed during cross-compilation or example building.
+ # Add those that have a "qml" subdirectory.
+ # The additional prefixes have priority over the main Qt prefix, so they come first.
+ if(NOT "${_qt_additional_packages_prefix_paths}" STREQUAL "")
+ __qt_internal_prefix_paths_to_roots(
+ additional_root_paths "${_qt_additional_packages_prefix_paths}")
+ foreach(root IN ITEMS ${additional_root_paths})
+ set(candidate "${root}/${QT6_INSTALL_QML}")
+ if(IS_DIRECTORY "${candidate}")
+ list(APPEND qml_import_paths "${candidate}")
+ endif()
+ endforeach()
+ endif()
- # Create a list of QML files for use with qmllint
- if(qml_files)
- get_target_property(qml_files_list ${target} QML_FILES)
- if(NOT qml_files_list)
- set(qml_files_list)
+ # Add the main Qt "<prefix>/qml" import path.
+ if(QT6_INSTALL_PREFIX)
+ set(main_import_path "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_QML}")
+ if(IS_DIRECTORY "${main_import_path}")
+ list(APPEND qml_import_paths "${main_import_path}")
endif()
+ endif()
- list(APPEND qml_files_list ${qml_files})
- set_target_properties(${target} PROPERTIES QML_FILES "${qml_files_list}")
+ set(${out_var} "${qml_import_paths}" PARENT_SCOPE)
+endfunction()
+
+function(_qt_internal_collect_target_qml_import_paths out_var target)
+ set(qml_import_paths "")
+ # Get custom import paths provided during qt_add_qml_module call.
+ get_target_property(qml_import_path ${target} QT_QML_IMPORT_PATH)
+ if(qml_import_path)
+ list(APPEND qml_import_paths ${qml_import_path})
endif()
- if (NOT TARGET ${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen AND qml_files)
- message(WARNING "QT6_PROCESS_RESOURCE: Qml files were detected but the qmlcachgen target is not defined. Consider adding QmlTools to your find_package command.")
+ # Facilitate self-import so we can find the qmldir file
+ get_target_property(module_out_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+ if(module_out_dir)
+ list(APPEND qml_import_paths "${module_out_dir}")
endif()
- if (TARGET ${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen AND qml_files)
- # Enable qt quick compiler support
- if (resource_files)
- set(chained_resource_name "${resource_name}_qmlcache")
- set(qml_resource_file "${CMAKE_CURRENT_BINARY_DIR}/.rcc/${chained_resource_name}.qrc")
- else()
- set(qml_resource_file "${CMAKE_CURRENT_BINARY_DIR}/.rcc/${resource_name}.qrc")
+ # Find qmldir files we copied to the build directory
+ if(NOT "${QT_QML_OUTPUT_DIRECTORY}" STREQUAL "")
+ if(EXISTS "${QT_QML_OUTPUT_DIRECTORY}")
+ list(APPEND qml_import_paths "${QT_QML_OUTPUT_DIRECTORY}")
endif()
+ else()
+ list(APPEND qml_import_paths "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+
+ set(${out_var} "${qml_import_paths}" PARENT_SCOPE)
+endfunction()
- get_target_property(import_path ${target} QT_QML_IMPORT_PATH)
- if (import_path)
- foreach(dir IN LISTS import_path)
- list(APPEND qmlcachegen_extra_args "-I" "${dir}")
+function(_qt_internal_collect_qml_import_paths out_var target)
+ _qt_internal_collect_target_qml_import_paths(qml_import_paths ${target})
+
+ # Add the Qt import paths last.
+ _qt_internal_get_main_qt_qml_import_paths(qt_main_import_paths)
+ list(APPEND qml_import_paths ${qt_main_import_paths})
+
+ set(${out_var} "${qml_import_paths}" PARENT_SCOPE)
+endfunction()
+
+# This function returns the path to the qmlimportscanner executable.
+# The are a few cases to handle:
+# When used in a user project, the tool should already be built and we can find the path
+# via the imported target.
+# When used in an example that is built as part of the qtdeclarative build (or top-level build),
+# there is no imported target yet. Here we have to differentiate whether the tool will be run at
+# build time or configure time.
+# If at configure time, we show an error, there is nothing to run yet. Such a setup is currently not
+# supported.
+# If at build time, we return the path where the built tool will be located after it is built.
+function(_qt_internal_find_qmlimportscanner_path out_path scan_at_configure_time)
+ # Find location of qmlimportscanner via the imported target.
+ set(tool_path "")
+ set(tool_name "qmlimportscanner")
+ set(import_scanner_target "${QT_CMAKE_EXPORT_NAMESPACE}::${tool_name}")
+ if(TARGET "${import_scanner_target}")
+ get_target_property(tool_path "${import_scanner_target}" IMPORTED_LOCATION)
+ if(NOT tool_path)
+ set(configs "RELWITHDEBINFO;RELEASE;MINSIZEREL;DEBUG")
+ foreach(config ${configs})
+ get_target_property(tool_path
+ "${import_scanner_target}" IMPORTED_LOCATION_${config})
+ if(tool_path)
+ break()
+ endif()
endforeach()
- list(APPEND qmlcachegen_extra_args "-I" "${QT_INSTALL_DIR}/${INSTALL_QMLDIR}")
endif()
+ endif()
- get_target_property(qmltypes ${target} QT_QML_MODULE_PLUGIN_TYPES_FILE)
- if (qmltypes)
- list(APPEND qmlcachegen_extra_args "-i" ${qmltypes})
- endif()
+ if(NOT QT_BUILDING_QT)
+ set(building_user_project TRUE)
+ else()
+ set(building_user_project FALSE)
+ endif()
- get_target_property(direct_calls ${target} QT_QMLCACHEGEN_DIRECT_CALLS)
- if (direct_calls)
- list(APPEND qmlcachegen_extra_args "--direct-calls")
- endif()
+ if(NOT EXISTS "${tool_path}"
+ AND (building_user_project OR scan_at_configure_time))
+ message(FATAL_ERROR "The qmlimportscanner tool could not be found.
+Possible reasons include:
+* The file was deleted, renamed, or moved to another location.
+* An install or uninstall procedure did not complete successfully.
+* The installation was faulty.
+")
+ endif()
- get_target_property(qmljs_runtime ${target} QT_QMLCACHEGEN_QMLJS_RUNTIME)
- if (qmljs_runtime)
- list(APPEND qmlcachegen_extra_args "--qmljs-runtime")
- endif()
+ # We are building Qt, the tool is not built yet and we need to run it at build time.
+ if(NOT tool_path)
+ qt_path_join(tool_path
+ "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}"
+ "${tool_name}${CMAKE_EXECUTABLE_SUFFIX}")
+ endif()
- foreach(file IN LISTS qml_files)
- get_filename_component(file_absolute ${file} ABSOLUTE)
- string(FIND "${file_absolute}" "${CMAKE_SOURCE_DIR}" start_index_of_source_dir)
- if (start_index_of_source_dir EQUAL 0)
- file(RELATIVE_PATH file_relative ${CMAKE_CURRENT_SOURCE_DIR} ${file_absolute})
- else()
- file(RELATIVE_PATH file_relative ${CMAKE_CURRENT_BINARY_DIR} ${file_absolute})
- endif()
- __qt_get_relative_resource_path_for_file(file_resource_path ${file})
- if (arg_PREFIX STREQUAL "/")
- # TO_CMAKE_PATH does not clean up cases such as //Foo
- set(file_resource_path "/${file_resource_path}")
- else()
- set(file_resource_path "${arg_PREFIX}/${file_resource_path}")
- endif()
- file(TO_CMAKE_PATH ${file_resource_path} file_resource_path)
- list(APPEND file_resource_paths ${file_resource_path})
- string(REGEX REPLACE "\.js$" "_js" compiled_file ${file_relative})
- string(REGEX REPLACE "\.mjs$" "_mjs" compiled_file ${compiled_file})
- string(REGEX REPLACE "\.qml$" "_qml" compiled_file ${compiled_file})
- string(REGEX REPLACE "[\$#\?]+" "_" compiled_file ${compiled_file})
- set(compiled_file "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/${resource_name}/${compiled_file}.cpp")
- get_filename_component(out_dir ${compiled_file} DIRECTORY)
- if(NOT EXISTS ${out_dir})
- file(MAKE_DIRECTORY ${out_dir})
- endif()
- add_custom_command(
- OUTPUT ${compiled_file}
- ${QT_TOOL_PATH_SETUP_COMMAND}
- COMMAND
- ${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen
- --resource-path "${file_resource_path}"
- --resource "${qml_resource_file}"
- ${qmlcachegen_extra_args}
- -o "${compiled_file}"
- "${file_absolute}"
- DEPENDS
- $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen>
- "${file_absolute}"
- "$<$<BOOL:${qmltypes}>:${qmltypes}>"
- "${qml_resource_file}"
- )
- target_sources(${target} PRIVATE ${compiled_file})
- set_source_files_properties(${compiled_file} PROPERTIES
- SKIP_AUTOGEN ON
- )
- endforeach()
+ set(${out_path} "${tool_path}" PARENT_SCOPE)
+endfunction()
- set(qmlcache_loader_list "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/${resource_name}/qml_loader_file_list.rsp")
- file(GENERATE
- OUTPUT ${qmlcache_loader_list}
- CONTENT "$<JOIN:${file_resource_paths},\n>"
- )
+function(_qt_internal_scan_qml_imports target imports_file_var when_to_scan)
+ if(NOT "${ARGN}" STREQUAL "")
+ message(FATAL_ERROR "Unknown/unexpected arguments: ${ARGN}")
+ endif()
- set(qmlcache_loader_file "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache/${resource_name}/qmlcache_loader.cpp")
- set(resource_name_arg "${resource_name}.qrc")
- if (chained_resource_name)
- set(resource_name_arg "${resource_name_arg}=${chained_resource_name}")
- endif()
+ if(when_to_scan STREQUAL "BUILD_PHASE")
+ set(scan_at_build_time TRUE)
+ set(scan_at_configure_time FALSE)
+ set(imports_file_infix "build")
+ elseif(when_to_scan STREQUAL "IMMEDIATELY")
+ set(scan_at_build_time FALSE)
+ set(scan_at_configure_time TRUE)
+ set(imports_file_infix "conf")
+ else()
+ message(FATAL_ERROR "Unexpected value for when_to_scan: ${when_to_scan}")
+ endif()
- add_custom_command(
- OUTPUT ${qmlcache_loader_file}
- ${QT_TOOL_PATH_SETUP_COMMAND}
- COMMAND
- ${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen
- --resource-name "${resource_name_arg}"
- -o "${qmlcache_loader_file}"
- "@${qmlcache_loader_list}"
- DEPENDS
- $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmlcachegen>
- "${qmlcache_loader_list}"
- )
+ _qt_internal_find_qmlimportscanner_path(tool_path "${scan_at_configure_time}")
- __qt_propagate_generated_resource(${target}
- ${resource_name}
- ${qmlcache_loader_file}
- output_target)
+ get_target_property(target_source_dir ${target} SOURCE_DIR)
+ get_target_property(target_binary_dir ${target} BINARY_DIR)
+ set(out_dir "${target_binary_dir}/.qt/qml_imports")
+
+ # Create separate files for scanning at build time vs configure time. Otherwise calling
+ # ninja clean will re-run qmlimportscanner directly after the clean, which is
+ # both weird and sometimes prints warnings due to the tool not finding qml files that were
+ # cleaned from the build dir.
+ set(file_base_name "${target}_${imports_file_infix}")
+
+ set(imports_file "${out_dir}/${file_base_name}.cmake")
+ set(${imports_file_var} "${imports_file}" PARENT_SCOPE)
+ file(MAKE_DIRECTORY ${out_dir})
+
+ set(cmd_args
+ -rootPath "${target_source_dir}"
+ -cmake-output
+ -output-file "${imports_file}"
+ )
- set(${arg_OUTPUT_GENERATED_TARGET} "${output_target}" PARENT_SCOPE)
+ _qt_internal_collect_qml_import_paths(qml_import_paths ${target})
+ list(REMOVE_DUPLICATES qml_import_paths)
- if (resource_files)
- set(resource_name ${chained_resource_name})
+ # Construct the -importPath arguments.
+ set(import_path_arguments)
+ foreach(path IN LISTS qml_import_paths)
+ if(EXISTS "${path}" OR scan_at_build_time)
+ list(APPEND import_path_arguments -importPath ${path})
+ else()
+ message(DEBUG "The import path ${path} is mentioned for ${target}, but it doesn't"
+ " exists.")
endif()
+ endforeach()
- # The generated qmlcache_loader source file uses private headers of Qml, so make sure
- # if the object library was created, it depends on the Qml target. If there's no target,
- # that means the target is a shared library and the sources are directly added to the target
- # via target_sources, so add dependency in that case as well.
- set(chosen_target "target") # shared library case
- if(output_target)
- set(chosen_target "output_target") # static library case.
- endif()
- target_link_libraries(${${chosen_target}} PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Qml)
- else()
- set(resource_files ${arg_FILES})
+ list(APPEND cmd_args ${import_path_arguments})
+
+ # All of the module's .qml files will be listed in one of the generated
+ # .qrc files, so there's no need to list the files individually. We provide
+ # the .qrc files instead because they have the additional information for
+ # each file's resource alias.
+ get_property(qrc_files TARGET ${target} PROPERTY _qt_generated_qrc_files)
+ if (qrc_files)
+ list(APPEND cmd_args "-qrcFiles" ${qrc_files})
+ endif()
+
+ # Use a response file to avoid command line length issues if we have a lot
+ # of arguments on the command line
+ string(LENGTH "${cmd_args}" length)
+ if(length GREATER 240)
+ set(rsp_file "${out_dir}/${file_base_name}.rsp")
+ list(JOIN cmd_args "\n" rsp_file_content)
+ file(WRITE ${rsp_file} "${rsp_file_content}")
+ set(cmd_args "@${rsp_file}")
endif()
- set(${arg_OUTPUT_REMAINING_RESOURCES} ${resource_files} PARENT_SCOPE)
- set(${arg_OUTPUT_RESOURCE_NAME} ${resource_name} PARENT_SCOPE)
+ _qt_internal_get_tool_wrapper_script_path(tool_wrapper)
+ set(import_scanner_args ${tool_wrapper} ${tool_path} ${cmd_args})
+
+ # Run qmlimportscanner to generate the cmake file that records the import entries
+ if(scan_at_build_time)
+ add_custom_command(
+ OUTPUT "${imports_file}"
+ COMMENT "Running qmlimportscanner for ${target}"
+ COMMAND ${import_scanner_args}
+ WORKING_DIRECTORY ${target_source_dir}
+ DEPENDS
+ ${tool_path}
+ ${qrc_files}
+ $<TARGET_PROPERTY:${target},QT_QML_MODULE_QML_FILES>
+ VERBATIM
+ )
+ add_custom_target(${target}_qmlimportscan DEPENDS "${imports_file}")
+ _qt_internal_assign_to_internal_targets_folder(${target}_qmlimportscan)
+ add_dependencies(${target} ${target}_qmlimportscan)
+ else()
+ message(VERBOSE "Running qmlimportscanner for ${target}.")
+ list(JOIN import_scanner_args " " import_scanner_args_string)
+ message(DEBUG "qmlimportscanner command: ${import_scanner_args_string}")
+ execute_process(
+ COMMAND ${import_scanner_args}
+ WORKING_DIRECTORY ${target_source_dir}
+ RESULT_VARIABLE result
+ )
+ if(result)
+ message(FATAL_ERROR
+ "Failed to scan target ${target} for QML imports: ${result}"
+ )
+ endif()
+ endif()
endfunction()
-include(CMakeParseArguments)
+# Parse the entry at the specified index, assuming the caller already included
+# the file generated by a call to _qt_internal_scan_qml_imports()
+macro(_qt_internal_parse_qml_imports_entry prefix index)
+ cmake_parse_arguments("${prefix}"
+ ""
+ "CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;LINKTARGET;PREFER"
+ "COMPONENTS;SCRIPTS"
+ ${qml_import_scanner_import_${index}}
+ )
+endmacro()
+
# This function is called as a finalizer in qt6_finalize_executable() for any
-# target that links against the Qml library for a statically built Qt.
+# target that links against the Qml library.
function(qt6_import_qml_plugins target)
- if(QT6_IS_SHARED_LIBS_BUILD)
+ if(NOT TARGET ${QT_CMAKE_EXPORT_NAMESPACE}::qmlimportscanner)
+ return()
+ endif()
+
+ get_target_property(is_imported ${QT_CMAKE_EXPORT_NAMESPACE}::qmlimportscanner IMPORTED)
+ if(NOT is_imported)
+ message(DEBUG "qt6_import_qml_plugins is called before qmlimportscanner is built."
+ " Skip calling qmlimportscanner because it doesn't yet exist.")
return()
endif()
# Protect against being called multiple times in case we are being called
# explicitly before the finalizer is invoked.
- get_target_property(alreadyImported ${target} _QT_QML_PLUGINS_IMPORTED)
- if(alreadyImported)
+ get_target_property(already_imported ${target} _QT_QML_PLUGINS_IMPORTED)
+ get_target_property(no_import_scan ${target} QT_QML_MODULE_NO_IMPORT_SCAN)
+ if(already_imported OR no_import_scan)
return()
endif()
set_target_properties(${target} PROPERTIES _QT_QML_PLUGINS_IMPORTED TRUE)
- set(options)
- set(oneValueArgs "PATH_TO_SCAN")
- set(multiValueArgs)
+ _qt_internal_scan_qml_imports(${target} imports_file IMMEDIATELY)
+ include("${imports_file}")
- cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
- if(NOT arg_PATH_TO_SCAN)
- set(arg_PATH_TO_SCAN "${CMAKE_CURRENT_SOURCE_DIR}")
- 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(added_plugins "")
+ set(plugins_to_link "")
+ set(plugin_inits_to_link "")
+
+ 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_PATH)
+ if(NOT entry_PLUGIN)
+ # Check if qml module is built within the build tree, and should have a plugin
+ # target, but its qmldir file is not generated yet.
+ get_property(dirs GLOBAL PROPERTY _qt_all_qml_output_dirs)
+ if(dirs)
+ list(FIND dirs "${entry_PATH}" index)
+ if(NOT index EQUAL -1)
+ get_property(qml_targets GLOBAL PROPERTY _qt_all_qml_targets)
+ list(GET qml_targets ${index} qml_module)
+ if(qml_module AND TARGET ${qml_module})
+ get_target_property(entry_LINKTARGET
+ ${qml_module} QT_QML_MODULE_PLUGIN_TARGET)
+ if(entry_LINKTARGET AND TARGET ${entry_LINKTARGET})
+ get_target_property(entry_PLUGIN ${entry_LINKTARGET}
+ OUTPUT_NAME)
+ endif()
+ endif()
+ endif()
+ endif()
+ endif()
- # Find location of qmlimportscanner.
- get_target_property(tool_path ${QT_CMAKE_EXPORT_NAMESPACE}::qmlimportscanner IMPORTED_LOCATION)
- if(NOT tool_path)
- set(configs "RELWITHDEBINFO;RELEASE;MINSIZEREL;DEBUG")
- foreach(config ${configs})
- get_target_property(tool_path Qt6::qmlimportscanner IMPORTED_LOCATION_${config})
- if(tool_path)
- break()
+ if(entry_PLUGIN)
+ # Sometimes a plugin appears multiple times with different versions.
+ # Make sure to process it only once.
+ list(FIND added_plugins "${entry_PLUGIN}" _index)
+ if(NOT _index EQUAL -1)
+ continue()
+ endif()
+ list(APPEND added_plugins "${entry_PLUGIN}")
+
+ # Link against the Qml plugin.
+ # For plugins provided by Qt, we assume those plugin targets are already defined
+ # (typically brought in via find_package(Qt6...) ).
+ # For other plugins, the targets can come from the project itself.
+ #
+ if(entry_LINKTARGET)
+ if(TARGET ${entry_LINKTARGET})
+ get_target_property(target_type ${entry_LINKTARGET} TYPE)
+ if(target_type STREQUAL "STATIC_LIBRARY")
+ list(APPEND plugins_to_link "${entry_LINKTARGET}")
+ endif()
+ else()
+ message(WARNING
+ "The qml plugin '${entry_PLUGIN}' is a dependency of '${target}', "
+ "but the link target it defines (${entry_LINKTARGET}) does not "
+ "exist in the current scope. The plugin will not be linked."
+ )
+ endif()
+ elseif(TARGET ${entry_PLUGIN})
+ get_target_property(target_type ${entry_PLUGIN} TYPE)
+ if(target_type STREQUAL "STATIC_LIBRARY")
+ list(APPEND plugins_to_link "${entry_PLUGIN}")
+ endif()
+ else()
+ # TODO: QTBUG-94605 Figure out if this is a reasonable scenario to support
+ message(WARNING
+ "The qml plugin '${entry_PLUGIN}' is a dependency of '${target}', "
+ "but there is no target by that name in the current scope. The plugin "
+ "will not be linked."
+ )
+ endif()
+ endif()
endif()
endforeach()
+
+ if(plugins_to_link)
+ # If ${target} is an executable or a shared library, link the plugins directly to
+ # the target.
+ # If ${target} is a static or INTERFACE library, the plugins should be propagated
+ # across those libraries to the end target (executable or shared library).
+ # The plugin initializers will be linked via usage requirements from the plugin target.
+ get_target_property(target_type ${target} TYPE)
+ if(target_type STREQUAL "EXECUTABLE" OR target_type STREQUAL "SHARED_LIBRARY"
+ OR target_type STREQUAL "MODULE_LIBRARY")
+ set(link_type "PRIVATE")
+ else()
+ set(link_type "INTERFACE")
+ endif()
+ target_link_libraries("${target}" ${link_type} ${plugins_to_link})
+ endif()
endif()
+endfunction()
- if(NOT EXISTS "${tool_path}")
- message(FATAL_ERROR "The package \"QmlImportScanner\" references the file
- \"${tool_path}\"
-but this file does not exist. Possible reasons include:
-* The file was deleted, renamed, or moved to another location.
-* An install or uninstall procedure did not complete successfully.
-* The installation package was faulty.
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_import_qml_plugins)
+ if(QT_DEFAULT_MAJOR_VERSION EQUAL 5)
+ qt5_import_qml_plugins(${ARGV})
+ elseif(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
+ qt6_import_qml_plugins(${ARGV})
+ endif()
+ endfunction()
+endif()
+
+function(_qt_internal_add_qml_deploy_info_finalizer target)
+ get_property(finalizer_added TARGET ${target} PROPERTY _qt_qml_deploy_finalizer_added)
+ if(NOT finalizer_added)
+ set_property(TARGET ${target} APPEND PROPERTY
+ INTERFACE_QT_EXECUTABLE_FINALIZERS
+ _qt_internal_generate_deploy_qml_imports_script
+ )
+ set_property(TARGET ${target} PROPERTY _qt_qml_deploy_finalizer_added TRUE)
+ endif()
+endfunction()
+
+# This function may be called as a finalizer in qt6_finalize_executable() for any
+# target that links against the Qml library for a shared Qt.
+function(_qt_internal_generate_deploy_qml_imports_script target)
+ if(NOT QT6_IS_SHARED_LIBS_BUILD)
+ return()
+ endif()
+ get_target_property(target_type ${target} TYPE)
+ # TODO: Handle Android where executables are module libraries instead
+ if(NOT target_type STREQUAL "EXECUTABLE")
+ return()
+ endif()
+
+ # Protect against being called multiple times in case we are being called
+ # explicitly before the finalizer is invoked.
+ get_target_property(already_generated ${target} _QT_QML_PLUGIN_SCAN_GENERATED)
+ get_target_property(no_import_scan ${target} QT_QML_MODULE_NO_IMPORT_SCAN)
+ if(already_generated OR no_import_scan)
+ return()
+ endif()
+ set_target_properties(${target} PROPERTIES _QT_QML_PLUGIN_SCAN_GENERATED TRUE)
+
+ # Defer actually running qmlimportscanner until build time. This keeps the
+ # configure step fast and takes advantage of the build step supporting
+ # parallel execution if there are multiple targets that need scanning.
+ _qt_internal_scan_qml_imports(${target} imports_file BUILD_PHASE)
+
+ set(is_bundle FALSE)
+ if(APPLE)
+ if(IOS)
+ message(FATAL_ERROR "Install support not available for iOS builds")
+ endif()
+ get_target_property(is_bundle ${target} MACOSX_BUNDLE)
+ endif()
+ set(is_bundle "$<BOOL:${is_bundle}>")
+
+ # For macOS app bundles, the directory layout must conform to Apple's
+ # requirements, so we hard-code the required structure. This assumes the
+ # app bundle is installed to the base dir with an install command like:
+ # install(TARGETS ${target} BUNDLE DESTINATION .)
+ set(bundle_qml_dir "$<TARGET_FILE_NAME:${target}>.app/Contents/Resources/qml")
+ set(bundle_plugins_dir "$<TARGET_FILE_NAME:${target}>.app/Contents/PlugIns")
+
+ _qt_internal_get_deploy_impl_dir(deploy_impl_dir)
+ string(MAKE_C_IDENTIFIER "${target}" target_id)
+ set(filename "${deploy_impl_dir}/deploy_qml_imports/${target_id}")
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ string(APPEND filename "-$<CONFIG>")
+ endif()
+ string(APPEND filename ".cmake")
+
+ # TODO: Fix macOS multi-config bundles to work.
+ file(GENERATE OUTPUT "${filename}" CONTENT
+"# Auto-generated deploy QML imports script for target \"${target}\".
+# Do not edit, all changes will be lost.
+# This file should only be included by qt6_deploy_qml_imports().
+
+set(__qt_opts $<${is_bundle}:BUNDLE>)
+if(arg_NO_QT_IMPORTS)
+ list(APPEND __qt_opts NO_QT_IMPORTS)
+endif()
+
+_qt_internal_deploy_qml_imports_for_target(
+ \${__qt_opts}
+ IMPORTS_FILE \"${imports_file}\"
+ PLUGINS_FOUND __qt_internal_plugins_found
+ QML_DIR \"$<IF:${is_bundle},${bundle_qml_dir},\${arg_QML_DIR}>\"
+ PLUGINS_DIR \"$<IF:${is_bundle},${bundle_plugins_dir},\${arg_PLUGINS_DIR}>\"
+)
+
+if(arg_PLUGINS_FOUND)
+ set(\${arg_PLUGINS_FOUND} \"\${__qt_internal_plugins_found}\" PARENT_SCOPE)
+endif()
+")
+
+endfunction()
+
+function(qt6_generate_deploy_qml_app_script)
+ # We take the target using a TARGET keyword instead of as the first
+ # positional argument so that we have a consistent signature with the
+ # qt6_generate_deploy_app_script() from qtbase. That function might accept
+ # an executable instead of a target in the future, but we can't because we
+ # need information associated with the target (scanning all its .qml files
+ # for imported QML modules).
+ set(no_value_options
+ NO_UNSUPPORTED_PLATFORM_ERROR
+ NO_TRANSLATIONS
+ NO_COMPILER_RUNTIME
+ MACOS_BUNDLE_POST_BUILD
+ DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
+ )
+ set(single_value_options
+ TARGET
+ OUTPUT_SCRIPT
+
+ # TODO: For backward compatibility / transitional use only,
+ # remove at some point
+ FILENAME_VARIABLE
+ )
+ set(qt_deploy_runtime_dependencies_options
+ # These options are forwarded as is to qt_deploy_runtime_dependencies.
+ PRE_INCLUDE_REGEXES
+ PRE_EXCLUDE_REGEXES
+ POST_INCLUDE_REGEXES
+ POST_EXCLUDE_REGEXES
+ POST_INCLUDE_FILES
+ POST_EXCLUDE_FILES
+ DEPLOY_TOOL_OPTIONS
+ )
+ set(multi_value_options
+ ${qt_deploy_runtime_dependencies_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()
+
+ # TODO: Remove when FILENAME_VARIABLE is fully removed
+ # Handle the slow deprecation of FILENAME_VARIABLE
+ if(arg_FILENAME_VARIABLE)
+ if(arg_OUTPUT_SCRIPT AND NOT arg_FILENAME_VARIABLE STREQUAL arg_OUTPUT_SCRIPT)
+ message(FATAL_ERROR
+ "Both FILENAME_VARIABLE and OUTPUT_SCRIPT were given and were different. "
+ "Only one of the two should be used."
+ )
+ endif()
+ message(AUTHOR_WARNING
+ "The FILENAME_VARIABLE keyword is deprecated and will be removed soon. "
+ "Please use OUTPUT_SCRIPT instead.")
+ set(arg_OUTPUT_SCRIPT "${arg_FILENAME_VARIABLE}")
+ unset(arg_FILENAME_VARIABLE)
+ endif()
+
+ if(NOT arg_OUTPUT_SCRIPT)
+ message(FATAL_ERROR "OUTPUT_SCRIPT must be specified")
+ endif()
+
+ # Check that the target was defer-finalized, and not immediately finalized when using
+ # CMake < 3.19. This is important because if it's immediately finalized, Qt::Qml is likely
+ # not in the dependency list, and thus _qt_internal_generate_deploy_qml_imports_script will
+ # not be executed, leading to an error at install time
+ # 'No QML imports information recorded for target X'.
+ # _qt_is_immediately_finalized is set by qt6_add_executable.
+ # TODO: Remove once minimum required CMAKE_VERSION is 3.19+.
+ get_target_property(is_immediately_finalized "${arg_TARGET}" _qt_is_immediately_finalized)
+ if(is_immediately_finalized)
+ message(FATAL_ERROR
+ "QML app deployment requires CMake version 3.19, or later, or manual executable "
+ "finalization. For manual finalization, pass the MANUAL_FINALIZATION option to "
+ "qt_add_executable() and then call qt_finalize_target(${arg_TARGET}) just before
+ calling qt_generate_deploy_qml_app_script().")
+ endif()
+
+ # Generate a descriptive deploy script name.
+ string(MAKE_C_IDENTIFIER "${arg_TARGET}" target_id)
+ set(deploy_script_name "qml_app_${target_id}")
+
+ get_target_property(is_bundle ${arg_TARGET} MACOSX_BUNDLE)
+
+ set(unsupported_platform_extra_message "")
+ 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(CMAKE_CROSSCOMPILING)
+ string(APPEND qt_build_type_string ", cross-compiled")
+ endif()
+
+ if(NOT is_bundle)
+ string(APPEND qt_build_type_string ", non-bundle app")
+ set(unsupported_platform_extra_message
+ "Executable targets have to be app bundles to use this command on Apple platforms.")
+ endif()
+
+ set(skip_message
+ "_qt_internal_show_skip_runtime_deploy_message(\"${qt_build_type_string}\"")
+ if(unsupported_platform_extra_message)
+ string(APPEND skip_message
+ "\n EXTRA_MESSAGE \"${unsupported_platform_extra_message}\"")
+ endif()
+ string(APPEND skip_message "\n)")
+
+ set(common_deploy_args "")
+ if(arg_NO_TRANSLATIONS)
+ string(APPEND common_deploy_args " NO_TRANSLATIONS\n")
+ endif()
+ if(arg_NO_COMPILER_RUNTIME)
+ string(APPEND common_deploy_args " NO_COMPILER_RUNTIME\n")
+ endif()
+
+ # Forward the arguments that are exactly the same for qt_deploy_runtime_dependencies.
+ foreach(var IN LISTS qt_deploy_runtime_dependencies_options)
+ if(NOT "${arg_${var}}" STREQUAL "")
+ list(APPEND common_deploy_args ${var} ${arg_${var}})
+ endif()
+ endforeach()
+
+ _qt_internal_should_skip_deployment_api(skip_deployment skip_reason)
+ _qt_internal_should_skip_post_build_deployment_api(skip_post_build_deployment
+ post_build_skip_reason)
+
+ if(APPLE AND NOT IOS AND QT6_IS_SHARED_LIBS_BUILD AND is_bundle)
+ # TODO: Consider handling non-bundle applications in the future using the generic cmake
+ # runtime dependency feature.
+
+ set(should_post_build FALSE)
+ if(arg_MACOS_BUNDLE_POST_BUILD AND NOT skip_post_build_deployment)
+ set(should_post_build TRUE)
+ endif()
+
+ # Generate the real deployment script when both post build step either deployment are
+ # enabled.
+ # If we skip deployment, but not the POST_BUILD step, we still need to generate the
+ # regular deploy script to run it during POST_BUILD time.
+ if(NOT skip_deployment OR should_post_build)
+ qt6_generate_deploy_script(
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT real_deploy_script
+ CONTENT "
+qt6_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found)
+if(NOT DEFINED __QT_DEPLOY_POST_BUILD)
+ qt6_deploy_runtime_dependencies(
+ EXECUTABLE $<TARGET_FILE_NAME:${arg_TARGET}>.app
+ ADDITIONAL_MODULES \${plugins_found}
+ ${common_deploy_args})
+endif()")
+ endif()
+
+ # Generate a no-op script either if we skip deployment or the post build step.
+ if(skip_deployment)
+ _qt_internal_generate_no_op_deploy_script(
+ FUNCTION_NAME "qt6_generate_deploy_qml_app_script"
+ SKIP_REASON "${skip_reason}"
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT no_op_deploy_script
+ )
+ endif()
+
+ if(skip_post_build_deployment)
+ _qt_internal_generate_no_op_deploy_script(
+ FUNCTION_NAME "qt6_generate_deploy_qml_app_script"
+ SKIP_REASON "${post_build_skip_reason}"
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT no_op_post_build_script
+ )
+ endif()
+
+ # Choose which deployment script to use during installation.
+ if(skip_deployment)
+ set(deploy_script "${no_op_deploy_script}")
+ else()
+ set(deploy_script "${real_deploy_script}")
+ endif()
+
+ # Choose which deployment script to use during the post build step.
+ if(should_post_build)
+ set(post_build_deploy_script "${real_deploy_script}")
+ elseif(skip_post_build_deployment)
+ # Explicitly asked to skip post build, show a no-op message.
+ set(post_build_deploy_script "${no_op_post_build_script}")
+ endif()
+
+ if(should_post_build OR skip_post_build_deployment)
+ # We must not deploy the runtime dependencies, otherwise we interfere
+ # with CMake's RPATH rewriting at install time. We only need the QML
+ # imports deployed to the bundle anyway, the build RPATHs will allow
+ # the regular libraries, frameworks and non-QML plugins to still be
+ # found, even if they are outside the app bundle.
+ add_custom_command(TARGET ${arg_TARGET} POST_BUILD
+ COMMAND ${CMAKE_COMMAND}
+ -D "QT_DEPLOY_PREFIX=$<TARGET_PROPERTY:${arg_TARGET},BINARY_DIR>"
+ -D "__QT_DEPLOY_IMPL_DIR=${deploy_impl_dir}"
+ -D "__QT_DEPLOY_POST_BUILD=TRUE"
+ -P "${post_build_deploy_script}"
+ VERBATIM
+ )
+ endif()
+ elseif(skip_deployment)
+ _qt_internal_generate_no_op_deploy_script(
+ FUNCTION_NAME "qt6_generate_deploy_qml_app_script"
+ SKIP_REASON "${skip_reason}"
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT deploy_script
+ )
+ elseif(WIN32 AND QT6_IS_SHARED_LIBS_BUILD)
+ qt6_generate_deploy_script(
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT deploy_script
+ CONTENT "
+qt6_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found)
+qt6_deploy_runtime_dependencies(
+ EXECUTABLE $<TARGET_FILE:${arg_TARGET}>
+ ADDITIONAL_MODULES \${plugins_found}
+ GENERATE_QT_CONF
+${common_deploy_args})")
+ elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING
+ AND QT6_IS_SHARED_LIBS_BUILD)
+ qt6_generate_deploy_script(
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT deploy_script
+ CONTENT "
+qt6_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found)
+qt6_deploy_runtime_dependencies(
+ EXECUTABLE $<TARGET_FILE:${arg_TARGET}>
+ ADDITIONAL_MODULES \${plugins_found}
+ GENERATE_QT_CONF
+${common_deploy_args})")
+ elseif((arg_NO_UNSUPPORTED_PLATFORM_ERROR OR
+ QT_INTERNAL_NO_UNSUPPORTED_PLATFORM_ERROR)
+ AND (arg_DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
+ OR QT_INTERNAL_DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM)
+ AND QT6_IS_SHARED_LIBS_BUILD)
+ # User project explicitly requested to deploy only user QML modules on a shared Qt libs
+ # platform where qt_deploy_runtime_dependencies does not work.
+ # This is useful for projects that will deploy the Qt QML and runtime libraries manually.
+ # This also offers a migration path to enable qt_deploy_runtime_dependencies for
+ # unsupported platforms without breaking projects that already handle runtime libs manually.
+ # But for it to work cleanly, projects will have to enable both
+ # NO_UNSUPPORTED_PLATFORM_ERROR and DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
+ # conditionally per platform.
+ qt6_generate_deploy_script(
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT deploy_script
+ CONTENT "
+${skip_message}
+qt6_deploy_qml_imports(TARGET ${arg_TARGET} NO_QT_IMPORTS)
+")
+ 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.
+ # 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}). "
+ ${unsupported_platform_extra_message}
+ )
+ else()
+ qt6_generate_deploy_script(
+ TARGET ${arg_TARGET}
+ NAME ${deploy_script_name}
+ OUTPUT_SCRIPT deploy_script
+ CONTENT "
+${skip_message}
+_qt_internal_show_skip_qml_runtime_deploy_message()
")
endif()
- # Find location of qml dir.
- # TODO: qt.prf implies that there might be more than one qml import path to pass to
- # qmlimportscanner.
- set(qml_path "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_QML}")
+ set(${arg_OUTPUT_SCRIPT} ${deploy_script} PARENT_SCOPE)
+endfunction()
- # Small macro to avoid duplicating code in two different loops.
- macro(_qt6_QmlImportScanner_parse_entry)
- set(entry_name "qml_import_scanner_import_${idx}")
- cmake_parse_arguments("entry"
- ""
- "CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;" ""
- ${${entry_name}})
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ macro(qt_generate_deploy_qml_app_script)
+ qt6_generate_deploy_qml_app_script(${ARGV})
endmacro()
+endif()
- # Run qmlimportscanner and include the generated cmake file.
- set(qml_imports_file_path
- "${CMAKE_CURRENT_BINARY_DIR}/Qt6_QmlPlugins_Imports_${target}.cmake")
+function(qt6_query_qml_module target)
- get_target_property(qrc_files ${target} _qt_generated_qrc_files)
- if (qrc_files)
- list(APPEND qrcFilesArguments "-qrcFiles")
- list(APPEND qrcFilesArguments ${qrc_files})
+ if(NOT TARGET ${target})
+ message(FATAL_ERROR "\"${target}\" is not a target")
endif()
- message(STATUS "Running qmlimportscanner to find used QML plugins. ")
- execute_process(COMMAND
- "${tool_path}" "${arg_PATH_TO_SCAN}" -importPath "${qml_path}"
- ${qrcFilesArguments}
- -cmake-output
- OUTPUT_FILE "${qml_imports_file_path}")
+ get_target_property(is_imported ${target} IMPORTED)
+ if(is_imported)
+ message(FATAL_ERROR
+ "Only targets built by the project can be used with this command, "
+ "but target \"${target}\" is imported."
+ )
+ endif()
- include("${qml_imports_file_path}" OPTIONAL RESULT_VARIABLE qml_imports_file_path_found)
- if(NOT qml_imports_file_path_found)
- message(FATAL_ERROR "Could not find ${qml_imports_file_path} which was supposed to be generated by qmlimportscanner.")
+ get_target_property(uri ${target} QT_QML_MODULE_URI)
+ if(NOT uri)
+ message(FATAL_ERROR
+ "Target \"${target}\" does not appear to be a QML module"
+ )
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)
- set(added_plugins "")
- foreach(idx RANGE "${qml_import_scanner_imports_count}")
- _qt6_QmlImportScanner_parse_entry()
- if(entry_PATH AND entry_PLUGIN)
- # Sometimes a plugin appears multiple times with different versions.
- # Make sure to process it only once.
- list(FIND added_plugins "${entry_PLUGIN}" _index)
- if(NOT _index EQUAL -1)
- continue()
+ set(no_value_options "")
+ set(single_value_options
+ URI
+ VERSION
+ PLUGIN_TARGET
+ MODULE_RESOURCE_PATH
+ TARGET_PATH
+ QMLDIR
+ TYPEINFO
+ QML_FILES
+ QML_FILES_DEPLOY_PATHS # relative to target path
+ QML_FILES_PREFIX_OVERRIDES
+ RESOURCES
+ RESOURCES_DEPLOY_PATHS # relative to target path
+ RESOURCES_PREFIX_OVERRIDES
+ )
+ 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(arg_URI)
+ set(${arg_URI} "${uri}" PARENT_SCOPE)
+ endif()
+
+ if(arg_VERSION)
+ get_property(version TARGET ${target} PROPERTY QT_QML_MODULE_VERSION)
+ set(${arg_VERSION} "${version}" PARENT_SCOPE)
+ endif()
+
+ if(arg_PLUGIN_TARGET)
+ # There might not be a plugin target, so return an empty string for that
+ get_property(plugin_target TARGET ${target} PROPERTY QT_QML_MODULE_PLUGIN_TARGET)
+ set(${arg_PLUGIN_TARGET} "${plugin_target}" PARENT_SCOPE)
+ endif()
+
+ if(arg_MODULE_RESOURCE_PATH)
+ # Note that QT_QML_MODULE_RESOURCE_PREFIX is not the RESOURCE_PREFIX
+ # passed to qt6_add_qml_module(). It is that plus the target path, which
+ # corresponds to what we mean by the MODULE_RESOURCE_PATH.
+ get_property(prefix TARGET ${target} PROPERTY QT_QML_MODULE_RESOURCE_PREFIX)
+ set(${arg_MODULE_RESOURCE_PATH} "${prefix}" PARENT_SCOPE)
+ endif()
+
+ string(REPLACE "." "/" target_path "${uri}")
+ if(arg_TARGET_PATH)
+ set(${arg_TARGET_PATH} "${target_path}" PARENT_SCOPE)
+ endif()
+
+ get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY)
+
+ if(arg_QMLDIR)
+ set(${arg_QMLDIR} "${output_dir}/qmldir" PARENT_SCOPE)
+ endif()
+
+ # This should always be set to something non-empty
+ # unless we've explicitly said NO_GENERATE_QMLTYPES
+ get_target_property(typeinfo ${target} QT_QML_MODULE_TYPEINFO)
+ if(arg_TYPEINFO AND typeinfo)
+ set(${arg_TYPEINFO} "${output_dir}/${typeinfo}" PARENT_SCOPE)
+ endif()
+
+ get_target_property(target_source_dir ${target} SOURCE_DIR)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)
+ set(scope_option TARGET_DIRECTORY ${target})
+ else()
+ set(scope_option "")
+ if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR AND
+ (arg_QML_FILES_DEPLOY_PATHS OR arg_RESOURCES_DEPLOY_PATHS))
+ # This isn't a fatal error because it will only be a problem if any
+ # qml or resource files actually have source file properties set.
+ message(WARNING
+ "Calling qt6_query_qml_module() from a different directory scope "
+ "to the one in which target \"${target}\" was created. "
+ "This requires CMake 3.18 or later to be robust, but you are using "
+ "CMake ${CMAKE_VERSION}. Deployment paths may not be correct."
+ )
+ endif()
+ endif()
+
+ # Because of how CMake lists work, in particular appending empty strings,
+ # we have to use a placeholder to represent empty values and then replace
+ # them at the end. If we don't do this, any list that starts with an empty
+ # value ends up discarding that empty value because it is indistinguishable
+ # from an empty list.
+ set(empty_placeholder "__qt_empty_placeholder__")
+
+ foreach(file_set IN ITEMS QML_FILES RESOURCES)
+ # NOTE: We converted these files to absolute paths already when storing them
+ get_target_property(files ${target} QT_QML_MODULE_${file_set})
+
+ if(arg_${file_set})
+ set(${arg_${file_set}} "${files}" PARENT_SCOPE)
+ endif()
+
+ if(arg_${file_set}_DEPLOY_PATHS OR arg_${file_set}_PREFIX_OVERRIDES)
+ set(deploy_paths "")
+ set(prefix_overrides "")
+ foreach(abs_file IN LISTS files)
+ # The QT_QML_MODULE_PREFIX_OVERRIDE is the PREFIX value that was passed to
+ # qt_target_qml_sources. It has no relation to the QT_QML_MODULE_RESOURCE_PREFIX
+ # property or the computed MODULE_RESOURCE_PATH variable above.
+ get_property(prefix_override SOURCE ${abs_file} ${scope_option}
+ PROPERTY QT_QML_MODULE_PREFIX_OVERRIDE
+ )
+ if("${prefix_override}" STREQUAL "")
+ list(APPEND prefix_overrides "${empty_placeholder}")
+ else()
+ list(APPEND prefix_overrides "${prefix_override}")
endif()
- list(APPEND added_plugins "${entry_PLUGIN}")
- # Link against the Qml plugin. The assumption is that all Qml plugins are already
- # find_package()'d by the Qml package, so we can safely link against the target.
- target_link_libraries("${target}" PRIVATE
- "${QT_CMAKE_EXPORT_NAMESPACE}::${entry_PLUGIN}")
+ # We can't provide a deploy path when the resource prefix is
+ # overridden. We still need to store an empty deploy path for it
+ # though so that the file lists all line up correctly.
+ if(NOT "${prefix_override}" STREQUAL "")
+ list(APPEND deploy_paths "${empty_placeholder}")
+ else()
+ # Careful how we check whether this property is set. Projects might
+ # use a resource alias that matches one of CMake's false constants,
+ # so we must use get_property(), not get_source_file_property(),
+ # then compare the result with an empty string.
+ get_property(alias
+ SOURCE ${abs_file} ${scope_option} PROPERTY QT_RESOURCE_ALIAS)
+ if(NOT "${alias}" STREQUAL "")
+ list(APPEND deploy_paths "${alias}")
+ else()
+ file(RELATIVE_PATH rel_file ${target_source_dir} ${abs_file})
+ list(APPEND deploy_paths "${rel_file}")
+ endif()
+ endif()
+ endforeach()
+ string(REPLACE "${empty_placeholder}" "" deploy_paths "${deploy_paths}")
+ string(REPLACE "${empty_placeholder}" "" prefix_overrides "${prefix_overrides}")
+ if(arg_${file_set}_DEPLOY_PATHS)
+ set(${arg_${file_set}_DEPLOY_PATHS} "${deploy_paths}" PARENT_SCOPE)
endif()
- endforeach()
+ if(arg_${file_set}_PREFIX_OVERRIDES)
+ set(${arg_${file_set}_PREFIX_OVERRIDES} "${prefix_overrides}" PARENT_SCOPE)
+ endif()
+ endif()
+ endforeach()
+endfunction()
+
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ macro(qt_query_qml_module)
+ qt6_query_qml_module(${ARGV})
+ endmacro()
+endif()
+
+
+function(_qt_internal_add_static_qml_plugin_dependencies plugin_target backing_target)
+ # Protect against multiple calls of qt_add_qml_plugin.
+ get_target_property(plugin_deps_added "${plugin_target}" _qt_extra_static_qml_plugin_deps_added)
+ if(plugin_deps_added)
+ return()
+ endif()
+ set_target_properties("${plugin_target}" PROPERTIES _qt_extra_static_qml_plugin_deps_added TRUE)
+
+ # Get the install plugin target name, which we will need for filtering later on.
+ if(TARGET "${backing_target}")
+ get_target_property(installed_plugin_target
+ "${backing_target}" _qt_qml_module_installed_plugin_target)
+ endif()
+
+ if(NOT backing_target STREQUAL plugin_target AND TARGET "${backing_target}")
+ set(has_backing_lib TRUE)
+ else()
+ set(has_backing_lib FALSE)
+ endif()
+
+ get_target_property(plugin_type ${plugin_target} TYPE)
+ set(skip_prl_marker "$<BOOL:QT_IS_PLUGIN_GENEX>")
- # Generate content for plugin initialization cpp file.
- set(added_imports "")
- set(qt_qml_import_cpp_file_content "")
- foreach(idx RANGE "${qml_import_scanner_imports_count}")
- _qt6_QmlImportScanner_parse_entry()
- if(entry_PLUGIN)
- if(entry_CLASSNAME)
- list(FIND added_imports "${entry_PLUGIN}" _index)
- if(_index EQUAL -1)
- string(APPEND qt_qml_import_cpp_file_content
- "Q_IMPORT_PLUGIN(${entry_CLASSNAME})\n")
- list(APPEND added_imports "${entry_PLUGIN}")
+ # If ${plugin_target} is a static qml plugin, recursively get its private dependencies (and its
+ # backing lib private deps), identify which of those are qml modules, extract any associated qml
+ # plugin target from those qml modules and make them dependencies of ${plugin_target}.
+ #
+ # E.g. this ensures that if a user project links directly to the static qtquick2plugin plugin
+ # target (note the plugin target, not the backing lib) it will automatically also link to
+ # Quick's transitive plugin dependencies: qmlplugin, modelsplugin and workerscriptplugin, in
+ # addition to the the Qml, QmlModels and QmlWorkerScript backing libraries.
+ #
+ # Note this logic is not specific to qtquick2plugin, it applies to all static qml plugins.
+ #
+ # This eliminates the needed boilerplate to link to the full transitive closure of qml plugins
+ # in user projects that don't want to use qmlimportscanner / qt_import_qml_plugins.
+ set(additional_plugin_deps "")
+
+ if(plugin_type STREQUAL "STATIC_LIBRARY")
+ set(all_private_deps "")
+
+ # We walk both plugin_target and backing_lib private deps because they can have differing
+ # dependencies and we want to consider all of them.
+ __qt_internal_collect_all_target_dependencies(
+ "${plugin_target}" plugin_private_deps)
+ if(plugin_private_deps)
+ list(APPEND all_private_deps ${plugin_private_deps})
+ endif()
+
+ if(has_backing_lib)
+ __qt_internal_collect_all_target_dependencies(
+ "${backing_target}" backing_lib_private_deps)
+ if(backing_lib_private_deps)
+ list(APPEND all_private_deps ${backing_lib_private_deps})
+ endif()
+ endif()
+
+ foreach(dep IN LISTS all_private_deps)
+ if(NOT TARGET "${dep}")
+ continue()
+ endif()
+ get_target_property(dep_type ${dep} TYPE)
+ if(dep_type STREQUAL "STATIC_LIBRARY")
+ set(associated_qml_plugin "")
+
+ # Check if the target has an associated imported qml plugin (like a Qt-provided
+ # one).
+ get_target_property(associated_qml_plugin_candidate ${dep}
+ _qt_qml_module_installed_plugin_target)
+
+ if(associated_qml_plugin_candidate AND TARGET "${associated_qml_plugin_candidate}")
+ set(associated_qml_plugin "${associated_qml_plugin_candidate}")
+ endif()
+
+ # Check if the target has an associated qml plugin that's built as part of the
+ # current project (non-installed one, so without a target namespace prefix).
+ get_target_property(associated_qml_plugin_candidate ${dep}
+ _qt_qml_module_plugin_target)
+
+ if(NOT associated_qml_plugin AND
+ associated_qml_plugin_candidate
+ AND TARGET "${associated_qml_plugin_candidate}")
+ set(associated_qml_plugin "${associated_qml_plugin_candidate}")
+ endif()
+
+ # We need to filter out adding the plugin_target as a dependency to itself,
+ # when walking the backing lib of the plugin_target.
+ if(associated_qml_plugin
+ AND NOT associated_qml_plugin STREQUAL plugin_target
+ AND NOT associated_qml_plugin STREQUAL installed_plugin_target)
+ # Abuse a genex marker, to skip the dependency to be added into prl files.
+ # TODO: Introduce a more generic marker name in qtbase specifically
+ # for skipping deps in prl file deps generation.
+ set(wrapped_associated_qml_plugin
+ "$<${skip_prl_marker}:$<TARGET_NAME:${associated_qml_plugin}>>")
+
+ if(NOT wrapped_associated_qml_plugin IN_LIST additional_plugin_deps)
+ list(APPEND additional_plugin_deps "${wrapped_associated_qml_plugin}")
endif()
- else()
- message(FATAL_ERROR
- "Plugin ${entry_PLUGIN} is missing a classname entry, please add one to the qmldir file.")
endif()
endif()
endforeach()
+ endif()
- # Write to the generated file, and include it as a source for the given target.
- set(generated_import_cpp_path
- "${CMAKE_CURRENT_BINARY_DIR}/Qt6_QmlPlugins_Imports_${target}.cpp")
- configure_file("${Qt6Qml_DIR}/Qt6QmlImportScannerTemplate.cpp.in"
- "${generated_import_cpp_path}"
- @ONLY)
- target_sources(${target} PRIVATE "${generated_import_cpp_path}")
+ if(additional_plugin_deps)
+ target_link_libraries(${plugin_target} PRIVATE ${additional_plugin_deps})
endif()
endfunction()
-if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
- function(qt_import_qml_plugins)
- if(QT_DEFAULT_MAJOR_VERSION EQUAL 5)
- qt5_import_qml_plugins(${ARGV})
- elseif(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
- qt6_import_qml_plugins(${ARGV})
- endif()
- endfunction()
-endif()
-
-# Wrapper around configure_file. Passes content collected in $directory-scoped property.
-function(_qt_internal_configure_qmldir directory)
- get_property(__qt_qmldir_content DIRECTORY "${directory}" PROPERTY _qt_internal_qmldir_content)
- configure_file(${ARGN} @ONLY)
-endfunction()
+# The function returns the output name of a qml plugin that will be used as library output
+# name and in a qmldir file as the 'plugin <plugin_output_name>' record.
+function(_qt_internal_get_qml_plugin_output_name out_var plugin_target)
+ cmake_parse_arguments(arg
+ ""
+ "BACKING_TARGET;TARGET_PATH;URI"
+ ""
+ ${ARGN}
+ )
+ set(plugin_name)
+ if(TARGET ${plugin_target})
+ get_target_property(plugin_name ${plugin_target} OUTPUT_NAME)
+ endif()
+ if(NOT plugin_name)
+ set(plugin_name "${plugin_target}")
+ endif()
-# Collects content for target $filepath and use deferred call of 'configure_file' to avoid
-# rebuilding of targets that depend on provided qmldir.
-# Note: For cmake versions < 3.19.0 plain 'file' function call will be used.
-function(_qt_internal_qmldir_defer_file command filepath content)
- if(${CMAKE_VERSION} VERSION_LESS "3.19.0")
- file(${ARGV})
- else()
- if("${command}" STREQUAL "WRITE")
- if("${__qt_qml_macros_module_base_dir}" STREQUAL "")
- message(FATAL_ERROR "Unable to configure qml module.
- \"find_package(Qt\${QT_VERSION_MAJOR} CONFIG COMPONENTS Qml)\" \
-is missing.")
+ if(ANDROID)
+ # In Android all plugins are stored in directly the /libs directory. This means that plugin
+ # names must be unique in scope of apk. To make this work we prepend uri-based prefix to
+ # each qml plugin in case if users don't use the manually written qmldir files.
+ get_target_property(no_generate_qmldir ${target} QT_QML_MODULE_NO_GENERATE_QMLDIR)
+ if(TARGET "${arg_BACKING_TARGET}")
+ get_target_property(no_generate_qmldir ${arg_BACKING_TARGET}
+ QT_QML_MODULE_NO_GENERATE_QMLDIR)
+
+ # Adjust Qml plugin names on Android similar to qml_plugin.prf which calls
+ # $$qt5LibraryTarget($$TARGET, "qml/$$TARGETPATH/").
+ # Example plugin names:
+ # qtdeclarative
+ # TARGET_PATH: QtQml/Models
+ # file name: libqml_QtQml_Models_modelsplugin_x86_64.so
+ # qtquickcontrols2
+ # TARGET_PATH: QtQuick/Controls.2/Material
+ # file name:
+ # libqml_QtQuick_Controls.2_Material_qtquickcontrols2materialstyleplugin_x86_64.so
+ if(NOT arg_TARGET_PATH)
+ get_target_property(arg_TARGET_PATH ${arg_BACKING_TARGET}
+ QT_QML_MODULE_TARGET_PATH)
endif()
- # Wrap with EVAL CODE to evaluate and expand arguments
- cmake_language(EVAL CODE
- "cmake_language(DEFER DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\" CALL
- \"_qt_internal_configure_qmldir\"
- \"${CMAKE_CURRENT_SOURCE_DIR}\"
- \"${__qt_qml_macros_module_base_dir}/Qt6qmldirTemplate.cmake.in\"
- \"${filepath}\")")
- set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
- PROPERTY _qt_internal_qmldir_content "${content}")
- elseif("${command}" STREQUAL "APPEND")
- set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
- APPEND_STRING
- PROPERTY _qt_internal_qmldir_content "${content}")
+ endif()
+ if(arg_TARGET_PATH)
+ string(REPLACE "/" "_" android_plugin_name_infix_name "${arg_TARGET_PATH}")
else()
- message(FATAL_ERROR "Unknown command ${command}. \
- _qt_internal_qmldir_defer_file only accepts \
- WRITE and APPEND commands.")
+ string(REPLACE "." "_" android_plugin_name_infix_name "${arg_URI}")
+ endif()
+
+ # If plugin supposed to use manually written qmldir file we don't prepend the uri-based
+ # prefix to the plugin output name. User should keep the file name of a QML plugin in
+ # qmldir the same as the name of plugin on a file system. Exception is the
+ # ABI-/platform-specific suffix that has the separate processing and should not be
+ # a part of plugin name in qmldir.
+ if(NOT no_generate_qmldir)
+ set(plugin_name
+ "qml_${android_plugin_name_infix_name}_${plugin_name}")
endif()
endif()
+
+ set(${out_var} "${plugin_name}" PARENT_SCOPE)
endfunction()
-# Adds a target called TARGET_qmllint that runs on all qml files compiled ahead-of-time.
-function(qt6_target_enable_qmllint target)
- get_target_property(target_source ${target} SOURCE_DIR)
- get_target_property(import_path ${target} QT_QML_IMPORT_PATH)
- get_target_property(files ${target} QML_FILES)
+# Used to add extra dependencies between ${target} and ${dep_target} qml plugins in a static
+# Qt build, without creating a dependency in the genereated qmake .prl files.
+# These dependencies make manual linking to static plugins a nicer experience for users that don't
+# want to use qt_import_qml_plugins.
+function(_qt_internal_add_qml_static_plugin_dependency target dep_target)
+ if(NOT BUILD_SHARED_LIBS)
+ # Abuse a genex marker, to skip the dependency to be added into prl files.
+ # TODO: Introduce a more generic marker name in qtbase specifically
+ # for skipping deps in prl file deps generation.
+ set(skip_prl_marker "$<BOOL:QT_IS_PLUGIN_GENEX>")
+ target_link_libraries("${target}" PRIVATE
+ "$<${skip_prl_marker}:$<TARGET_NAME:${dep_target}>>")
+ endif()
+endfunction()
- if(import_path)
- foreach(dir IN LISTS import_path)
- list(APPEND import_args "-I" "${dir}")
- endforeach()
- list(APPEND import_args "-I" "${QT_INSTALL_DIR}/${INSTALL_QMLDIR}")
+function(_qt_internal_collect_qml_module_dependencies target)
+ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0")
+ cmake_language(EVAL CODE
+ "cmake_language(DEFER CALL _qt_internal_collect_qml_module_dependencies_deferred \"${target}\")"
+ )
+ else()
+ _qt_internal_collect_qml_module_dependencies_deferred("${target}")
endif()
+endfunction()
- add_custom_target(${target}_qmllint
- ${QT_CMAKE_EXPORT_NAMESPACE}::qmllint ${files} ${include_args}
- WORKING_DIRECTORY ${target_source}
- )
+function(_qt_internal_collect_qml_module_dependencies_deferred target)
+ get_target_property(deps ${target} QT_QML_MODULE_DEPENDENCIES)
+ if(NOT deps)
+ return()
+ endif()
+ foreach(dep IN LISTS deps)
+ string(REPLACE " " ";" dep "${dep}")
+ list(GET dep 0 dep_module_uri)
+ get_property(qml_uris GLOBAL PROPERTY _qt_all_qml_uris)
+ list(FIND qml_uris "${dep_module_uri}" index)
+ if(index LESS 0)
+ continue()
+ endif()
+ get_property(qml_targets GLOBAL PROPERTY _qt_all_qml_targets)
+ list(GET qml_targets ${index} dep_module)
+ # Make the module target dependent on its non-imported QML dependencies.
+ if(TARGET "${dep_module}")
+ get_target_property(is_imported ${dep_module} IMPORTED)
+ if(NOT is_imported)
+ add_dependencies(${target} ${dep_module})
+ endif()
+ endif()
+ endforeach()
endfunction()