From 8cdbcee614dbb34d4ac770bee1734c18ea27fa12 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 30 Oct 2020 11:27:36 +0100 Subject: CMake: Allow building pure QML modules not backed by C++ sources When no C++ sources are passed to qt_internal_add_qml_plugin (a pure QML module), create a C++ backed Qt plugin anyway. In such a case, generate a dummy plugin.cpp containing a QQmlEngineExtensionPlugin subclass. The class name is autogenerated from the QML import URI. The class constructor will call the qmltyperegistrar generated void qml_register_types_foo() function, to ensure the needed module versions are registered for the QML module. Change-Id: I19959dafdf0dc837c6037e7dc1d549b7420110a7 Reviewed-by: Fabian Kosmale --- src/qml/CMakeLists.txt | 6 ++++ src/qml/Qt6QmlBuildInternals.cmake | 34 ++++++++++--------- src/qml/Qt6QmlMacros.cmake | 68 ++++++++++++++++++++++++++++++++++--- src/qml/Qt6QmlPluginTemplate.cpp.in | 23 +++++++++++++ 4 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 src/qml/Qt6QmlPluginTemplate.cpp.in diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index 68e5a94734..596a8fdfc0 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -668,4 +668,10 @@ qt_copy_or_install(FILES "${CMAKE_CURRENT_LIST_DIR}/${INSTALL_CMAKE_NAMESPACE}${target}ImportScannerTemplate.cpp.in" DESTINATION "${config_install_dir}" ) + +# Install pure QML Plugin template cpp file. +qt_copy_or_install(FILES + "${CMAKE_CURRENT_LIST_DIR}/${INSTALL_CMAKE_NAMESPACE}${target}PluginTemplate.cpp.in" + DESTINATION "${config_install_dir}" +) # special case end diff --git a/src/qml/Qt6QmlBuildInternals.cmake b/src/qml/Qt6QmlBuildInternals.cmake index d25c987fc9..f6f1c98ecb 100644 --- a/src/qml/Qt6QmlBuildInternals.cmake +++ b/src/qml/Qt6QmlBuildInternals.cmake @@ -9,7 +9,6 @@ include_guard(GLOBAL) # in an IDE. Finally, it will also create a custom ${target}_qmltypes which # can be used to generate the respective plugins.qmltypes file. # -# CPP_PLUGIN: Whether this qml module has any c++ source files. # URI: Module's uri. # TARGET_PATH: Expected installation path for the Qml Module. Equivalent # to the module's URI where '.' is replaced with '/'. Use this to override the @@ -79,22 +78,15 @@ function(qt_internal_add_qml_module target) ${ARGV} ) - # If we have no sources, but qml files, create a custom target so the - # qml file will be visibile in an IDE. - if (arg_SOURCES) - qt_internal_add_plugin(${target} - TYPE - qml_plugin - QML_TARGET_PATH - "${arg_TARGET_PATH}" - ${plugin_args} - ) - endif() - + qt_internal_add_plugin(${target} + TYPE + qml_plugin + QML_TARGET_PATH + "${arg_TARGET_PATH}" + ${plugin_args} + ) - if (arg_CPP_PLUGIN OR arg_SOURCES) - set(no_create_option DO_NOT_CREATE_TARGET) - endif() + set(no_create_option DO_NOT_CREATE_TARGET) if (arg_CLASSNAME) set(classname_arg CLASSNAME ${arg_CLASSNAME}) @@ -120,6 +112,15 @@ function(qt_internal_add_qml_module target) set(install_qmltypes_arg INSTALL_QMLTYPES) endif() + + # Because qt_internal_add_qml_module does not propagate its SOURCES option to + # qt6_add_qml_module, but only to qt_internal_add_plugin, we need a way to tell + # qt6_add_qml_module if it should generate a dummy plugin cpp file. Otherwise we'd generate + # a dummy plugin.cpp file twice and thus cause duplicate symbol issues. + if (NOT arg_SOURCES) + set(pure_qml_module "PURE_MODULE") + endif() + qt_path_join(qml_module_install_dir ${QT_INSTALL_DIR} "${INSTALL_QMLDIR}/${arg_TARGET_PATH}") set(qml_module_build_dir "") @@ -140,6 +141,7 @@ function(qt_internal_add_qml_module target) ${classname_arg} ${generate_qmltypes_arg} ${install_qmltypes_arg} + ${pure_qml_module} RESOURCE_PREFIX "/qt-project.org/imports" TARGET_PATH ${arg_TARGET_PATH} URI ${arg_URI} diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 51fe4cac14..89afeb1d69 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -2,6 +2,8 @@ # Q6QmlMacros # +set(__qt_qml_macros_module_base_dir "${CMAKE_CURRENT_LIST_DIR}") + # # Create a Qml Module. Arguments: # @@ -51,7 +53,7 @@ # 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 targets) +# (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) @@ -84,10 +86,12 @@ # 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. +# # 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 @@ -96,6 +100,7 @@ function(qt6_add_qml_module target) SKIP_TYPE_REGISTRATION PLUGIN_OPTIONAL INSTALL_QML_FILES + PURE_MODULE ) if (QT_BUILDING_QT) @@ -142,11 +147,18 @@ function(qt6_add_qml_module target) message(FATAL_ERROR "qt6_add_qml_module called with an invalid version argument: '${arg_VERSION}'. Expected version style: VersionMajor.VersionMinor.") endif() - if (NOT BUILD_SHARED_LIBS AND NOT arg_CLASSNAME) - message(FATAL_ERROR "qt6_add_qml_module Static builds of Qml modules require a class name, none was provided. Please specify one using the CLASSNAME argument.") + # 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) endif() - if (arg_DO_NOT_CREATE_TARGET AND NOT TARGET ${target}) + 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") endif() @@ -160,6 +172,9 @@ function(qt6_add_qml_module target) 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) @@ -183,6 +198,10 @@ function(qt6_add_qml_module target) 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/"). @@ -459,6 +478,45 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) 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. + string(REGEX REPLACE "[^A-Za-z0-9]" "_" escaped_uri "${uri}") + + 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") + + 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") + endif() + + set(qt_qml_plugin_constructor_content + "volatile auto registration = &${register_types_function_name}; + Q_UNUSED(registration); +") + + 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}") + + configure_file("${template_path}" "${generated_cpp_file_path}" @ONLY) + + target_sources("${target}" PRIVATE "${generated_cpp_file_path}") + target_link_libraries("${target}" PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Qml) + + set(${out_class_name} "${qt_qml_plugin_class_name}" PARENT_SCOPE) + + # Enable AUTOMOC explicitly, because the generated cpp file expects to include its moc-ed + # output file. + set_property(TARGET "${target}" PROPERTY AUTOMOC ON) +endfunction() + # # 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 diff --git a/src/qml/Qt6QmlPluginTemplate.cpp.in b/src/qml/Qt6QmlPluginTemplate.cpp.in new file mode 100644 index 0000000000..9788932743 --- /dev/null +++ b/src/qml/Qt6QmlPluginTemplate.cpp.in @@ -0,0 +1,23 @@ +// This file is autogenerated by CMake. +// It facilitates usage of a pure QML module as a static QML plugin. + +#include + +@qt_qml_plugin_intro@ + +class @qt_qml_plugin_class_name@ : public QQmlEngineExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid) + +public: + @qt_qml_plugin_class_name@(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent) + { + Q_UNUSED(parent); + @qt_qml_plugin_constructor_content@ + } +}; + +@qt_qml_plugin_outro@ + +#include "@qt_qml_plugin_moc_include_name@" -- cgit v1.2.3