aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjen Hiemstra <ahiemstra@heimr.nl>2021-08-02 15:01:13 +0200
committerArjen Hiemstra <ahiemstra@heimr.nl>2022-01-18 12:09:57 +0000
commit3813fd1bc97fa6bb2189cc9586f77be4c30478d6 (patch)
treea579dccf4ba1e0bccc58da42c3c39a97a569303b
parentf4049c5429afc3e195a60984922b7cb7276d908f (diff)
downloadextra-cmake-modules-3813fd1bc97fa6bb2189cc9586f77be4c30478d6.tar.gz
extra-cmake-modules-3813fd1bc97fa6bb2189cc9586f77be4c30478d6.tar.bz2
Introduce ECMQmlModule.cmake
This contains some helper functions to make it easier to create QML modules through CMake. It takes care of several things that currently need to be done manually. It adds four tests for the four primary ways that it can be used, either as shared/static library and with or without C++ plugin.
-rw-r--r--modules/ECMQmlModule.cmake419
-rw-r--r--modules/ECMQmlModule.cpp.in12
-rw-r--r--modules/ECMQmlModule.cpp.in.license2
-rw-r--r--modules/ECMQmlModule.h.in17
-rw-r--r--modules/ECMQmlModule.h.in.license2
-rw-r--r--tests/CMakeLists.txt44
-rw-r--r--tests/ECMQmlModuleTest/CMakeLists.txt32
-rw-r--r--tests/ECMQmlModuleTest/QmlModule.qml11
-rw-r--r--tests/ECMQmlModuleTest/check.cmake.in64
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_depends6
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_depends.license2
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_full5
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_full.license2
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_qmlonly3
-rw-r--r--tests/ECMQmlModuleTest/qmldir_expected_qmlonly.license2
-rw-r--r--tests/ECMQmlModuleTest/qmlmodule.cpp14
-rw-r--r--tests/ECMQmlModuleTest/qmlmodule.h21
17 files changed, 658 insertions, 0 deletions
diff --git a/modules/ECMQmlModule.cmake b/modules/ECMQmlModule.cmake
new file mode 100644
index 00000000..75bc464a
--- /dev/null
+++ b/modules/ECMQmlModule.cmake
@@ -0,0 +1,419 @@
+#
+# SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+#[========================================================================[.rst:
+ECMQmlModule
+------------
+
+This file contains helper functions to make it easier to create QML modules. It
+takes care of a number of things that often need to be repeated. It also takes
+care of special handling of QML modules between shared and static builds. When
+building a static version of a QML module, the relevant QML source files are
+bundled into the static library. When using a shared build, the QML plugin and
+relevant QML files are copied to the target's RUNTIME_OUTPUT_DIRECTORY to make
+it easier to run things directly from the build directory.
+
+Example usage:
+
+.. code-block:: cmake
+
+ ecm_add_qml_module(ExampleModule URI "org.example.Example" VERSION 1.4)
+
+ target_sources(ExampleModule PRIVATE ExamplePlugin.cpp)
+ target_link_libraries(ExampleModule PRIVATE Qt::Quick)
+
+ ecm_target_qml_sources(ExampleModule SOURCES ExampleItem.qml)
+ ecm_target_qml_sources(ExampleModule SOURCES AnotherExampleItem.qml VERSION 1.5)
+
+ ecm_finalize_qml_module(ExampleModule DESTINATION ${KDE_INSTALL_QMLDIR})
+
+
+::
+ ecm_add_qml_module(<target name> URI <module uri> [VERSION <module version>] [NO_PLUGIN] [CLASSNAME <class name>])
+
+This will declare a new CMake target called ``<target name>``. The ``URI``
+argument is required and should be a proper QML module URI. The URI is used,
+among others, to generate a subdirectory where the module will be installed to.
+
+If the ``VERSION`` argument is specified, it is used to initialize the default
+version that is used by ``ecm_target_qml_sources`` when adding QML files. If it
+is not specified, a default of 1.0 is used. Additionally, if a version greater
+than or equal to 2.0 is specified, the major version is appended to the
+installation path of the module.
+
+If the option ``NO_PLUGIN`` is set, a target is declared that is not expected to
+contain any C++ QML plugin.
+
+If the optional ``CLASSNAME`` argument is supplied, it will be used as class
+name in the generated QMLDIR file. If it is not specified, the target name will
+be used instead.
+
+You can add C++ and QML source files to the target using ``target_sources`` and
+``ecm_target_qml_sources``, respectively.
+
+Since 5.87.0
+
+::
+ ecm_add_qml_module_dependencies(<target> DEPENDS <module string> [<module string> ...])
+
+Add the list of dependencies specified by the ``DEPENDS`` argument to be listed
+as dependencies in the generated QMLDIR file of ``<target>``.
+
+Since 5.87.0
+
+::
+ ecm_target_qml_sources(<target> SOURCES <source.qml> [<source.qml> ...] [VERSION <version>] [PATH <path>] [PRIVATE])
+
+Add the list of QML files specified by the ``SOURCES`` argument as source files
+to the QML module target ``<target>``.
+
+If the optional ``VERSION`` argument is specified, all QML files will be added
+with the specified version. If it is not specified, they will use the version of
+the QML module target.
+
+If the optional ``PRIVATE`` argument is specified, the QML files will be
+included in the target but not in the generated qmldir file. Any version
+argument will be ignored.
+
+The optional ``PATH`` argument declares a subdirectory of the module where the
+files should be copied to. By default, files will be copied to the module root.
+
+This function will fail if ``<target>`` is not a QML module target or any of the
+specified files do not exist.
+
+Since 5.87.0
+
+::
+ ecm_finalize_qml_module(<target> DESTINATION <QML install destination>)
+
+Finalize the specified QML module target. This must be called after all other
+setup (like adding sources) on the target has been done. It will perform a
+number of tasks:
+
+- It will generate a qmldir file from the QML files added to the target. If the
+ module has a C++ plugin, this will also be included in the qmldir file.
+- If ``BUILD_SHARED_LIBS`` is off, a QRC file is generated from the QML files
+ added to the target. This QRC file will be included when compiling the C++ QML
+ module. The built static library will be installed in a subdirection of
+ ``DESTINATION`` based on the QML module's uri. Note that if ``NO_PLUGIN`` is
+ set, a C++ QML plugin will be generated to include the QRC files.
+- If ``BUILD_SHARED_LIBS`` in on, all generated files, QML sources and the C++
+ plugin will be installed in a subdirectory of ``DESTINATION`` based upon the
+ QML module's uri. In addition, these files will also be copied to the target's
+ ``RUNTIME_OUTPUT_DIRECTORY`` in a similar subdirectory.
+
+This function will fail if ``<target>`` is not a QML module target.
+
+Since 5.87.0
+
+#]========================================================================]
+
+include(CMakeParseArguments)
+
+set(_ECM_QMLMODULE_STATIC_QMLONLY_H "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.h.in")
+set(_ECM_QMLMODULE_STATIC_QMLONLY_CPP "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.cpp.in")
+
+set(_ECM_QMLMODULE_PROPERTY_URI "_ecm_qml_uri")
+set(_ECM_QMLMODULE_PROPERTY_QMLDIR "_ecm_qmldir_file")
+set(_ECM_QMLMODULE_PROPERTY_FILES "_ecm_qml_files")
+set(_ECM_QMLMODULE_PROPERTY_QMLONLY "_ecm_qml_only")
+set(_ECM_QMLMODULE_PROPERTY_VERSION "_ecm_qml_version")
+set(_ECM_QMLMODULE_PROPERTY_PRIVATE "_ecm_qml_private")
+set(_ECM_QMLMODULE_PROPERTY_PATH "_ecm_qml_path")
+set(_ECM_QMLMODULE_PROPERTY_CLASSNAME "_ecm_qml_classname")
+set(_ECM_QMLMODULE_PROPERTY_DEPENDS "_ecm_qml_depends")
+
+macro(_ecm_qmlmodule_verify_qml_target ARG_TARGET)
+ if (NOT TARGET ${ARG_TARGET})
+ message(FATAL_ERROR "Target ${ARG_TARGET} does not exist")
+ endif()
+ get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
+ if ("${_qml_uri}" STREQUAL "" OR "${_qml_uri}" STREQUAL "${_ECM_QMLMODULE_PROPERTY_URI}-NOTFOUND")
+ message(FATAL_ERROR "Target ${ARG_TARGET} is not a QML plugin target")
+ endif()
+endmacro()
+
+function(_ecm_qmlmodule_generate_qmldir ARG_TARGET)
+ get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
+ get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
+ get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})
+ get_target_property(_qml_classname ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_CLASSNAME})
+
+ set(_qmldir_template "# This file was automatically generated by ECMQmlModule and should not be modified")
+
+ string(APPEND _qmldir_template "\nmodule ${_qml_uri}")
+
+ if (NOT ${_qml_only})
+ string(APPEND _qmldir_template "\nplugin ${ARG_TARGET}")
+
+ if ("${_qml_classname}" STREQUAL "")
+ string(APPEND _qmldir_template "\nclassname ${ARG_TARGET}")
+ else()
+ string(APPEND _qmldir_template "\nclassname ${_qml_classname}")
+ endif()
+ endif()
+
+ get_target_property(_qml_depends ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_DEPENDS})
+ if (NOT "${_qml_depends}" STREQUAL "")
+ foreach(_depends ${_qml_depends})
+ string(APPEND _qmldir_template "\ndepends ${_depends}")
+ endforeach()
+ endif()
+
+ foreach(_file ${_qml_files})
+ get_filename_component(_filename ${_file} NAME)
+ get_filename_component(_classname ${_file} NAME_WE)
+ get_property(_version SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION})
+ get_property(_private SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE})
+ if (NOT _private)
+ string(APPEND _qmldir_template "\n${_classname} ${_version} ${_filename}")
+ endif()
+ endforeach()
+
+ string(APPEND _qmldir_template "\n")
+
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir" "${_qmldir_template}")
+ set_target_properties(${ARG_TARGET} PROPERTIES _ecm_qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir")
+endfunction()
+
+function(_ecm_qmlmodule_generate_qrc ARG_TARGET)
+ get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
+ get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
+ get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
+
+ string(REPLACE "." "/" _qml_prefix ${_qml_uri})
+
+ set(_qrc_template "<!-- This file was automatically generated by ECMQmlModule and should not be modified -->")
+
+ string(APPEND _qrc_template "\n<RCC>\n<qresource prefix=\"${_qml_prefix}\">")
+ string(APPEND _qrc_template "\n<file alias=\"qmldir\">${_qmldir_file}</file>")
+
+ foreach(_file ${_qml_files})
+ get_filename_component(_filename ${_file} NAME)
+ get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})
+
+ set(_file_path "${_filename}")
+ if (NOT "${_path}" STREQUAL "")
+ set(_file_path "${_path}/${_filename}")
+ endif()
+
+ string(APPEND _qrc_template "\n<file alias=\"${_file_path}\">${_file}</file>")
+ endforeach()
+
+ string(APPEND _qrc_template "\n</qresource>\n</RCC>\n")
+
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc" "${_qrc_template}")
+ qt_add_resources(_qrc_output "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc")
+
+ target_sources(${ARG_TARGET} PRIVATE ${_qrc_output})
+endfunction()
+
+function(ecm_add_qml_module ARG_TARGET)
+ cmake_parse_arguments(PARSE_ARGV 1 ARG "NO_PLUGIN" "URI;VERSION;CLASSNAME" "")
+
+ if ("${ARG_TARGET}" STREQUAL "")
+ message(FATAL_ERROR "ecm_add_qml_module called without a valid target name.")
+ endif()
+
+ if ("${ARG_URI}" STREQUAL "")
+ message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
+ endif()
+
+ string(FIND "${ARG_URI}" " " "_space")
+ if (${_space} GREATER_EQUAL 0)
+ message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
+ endif()
+
+ if (${BUILD_SHARED_LIBS} AND ${ARG_NO_PLUGIN})
+ # CMake doesn't like library targets without sources, so if we have no
+ # C++ sources, use a plain target that we can add all the install stuff
+ # to.
+ add_custom_target(${ARG_TARGET} ALL)
+ else()
+ add_library(${ARG_TARGET})
+ endif()
+
+ if ("${ARG_VERSION}" STREQUAL "")
+ set(ARG_VERSION "1.0")
+ endif()
+
+ set_target_properties(${ARG_TARGET} PROPERTIES
+ ${_ECM_QMLMODULE_PROPERTY_URI} "${ARG_URI}"
+ ${_ECM_QMLMODULE_PROPERTY_FILES} ""
+ ${_ECM_QMLMODULE_PROPERTY_QMLONLY} "${ARG_NO_PLUGIN}"
+ ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}"
+ ${_ECM_QMLMODULE_PROPERTY_CLASSNAME} "${ARG_CLASSNAME}"
+ ${_ECM_QMLMODULE_PROPERTY_DEPENDS} ""
+ )
+endfunction()
+
+function(ecm_add_qml_module_dependencies ARG_TARGET)
+ cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "DEPENDS")
+
+ _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
+
+ if ("${ARG_DEPENDS}" STREQUAL "")
+ message(FATAL_ERROR "ecm_add_qml_module_dependencies called without required argument DEPENDS")
+ endif()
+
+ set_target_properties(${ARG_TARGET} PROPERTIES ${_ECM_QMLMODULE_PROPERTY_DEPENDS} "${ARG_DEPENDS}")
+endfunction()
+
+function(ecm_target_qml_sources ARG_TARGET)
+ cmake_parse_arguments(PARSE_ARGV 1 ARG "PRIVATE" "VERSION;PATH" "SOURCES")
+
+ _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
+
+ if ("${ARG_SOURCES}" STREQUAL "")
+ message(FATAL_ERROR "ecm_target_qml_sources called without required argument SOURCES")
+ endif()
+
+ if ("${ARG_VERSION}" STREQUAL "")
+ get_target_property(ARG_VERSION ${ARG_TARGET} "_ecm_qml_version")
+ endif()
+
+ foreach(_file ${ARG_SOURCES})
+ # Check if a given file exists, but only for files that are not
+ # automatically generated.
+ set(_path "${_file}")
+ get_source_file_property(_generated ${_file} GENERATED)
+ if (NOT _generated)
+ if (IS_ABSOLUTE ${_path})
+ # Assume absolute paths are generated, which may not always be
+ # true but is fairly likely.
+ set(_generated TRUE)
+ else()
+ string(FIND ${_file} ${CMAKE_BINARY_DIR} _in_binary_dir)
+ if (${_in_binary_dir} GREATER_EQUAL 0)
+ # Assume anything in binary dir is generated.
+ set(_generated TRUE)
+ else()
+ set(_path "${CMAKE_CURRENT_SOURCE_DIR}/${_file}")
+ endif()
+ endif()
+ endif()
+
+ if (NOT ${_generated} AND NOT EXISTS ${_path})
+ message(FATAL_ERROR "ecm_target_qml_sources called with nonexistent file ${_file}")
+ endif()
+
+ if (NOT ARG_PRIVATE)
+ set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}")
+ else()
+ set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE} TRUE)
+ endif()
+ set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH} "${ARG_PATH}")
+ set_property(TARGET ${ARG_TARGET}
+ APPEND PROPERTY ${_ECM_QMLMODULE_PROPERTY_FILES} ${_path}
+ )
+ endforeach()
+endfunction()
+
+function(ecm_finalize_qml_module ARG_TARGET)
+ cmake_parse_arguments(PARSE_ARGV 1 ARG "" "DESTINATION" "")
+
+ _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
+
+ if ("${ARG_DESTINATION}" STREQUAL "")
+ message(FATAL_ERROR "ecm_finalize_qml_module called without required argument DESTINATION")
+ endif()
+
+ _ecm_qmlmodule_generate_qmldir(${ARG_TARGET})
+
+ get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
+ get_target_property(_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})
+
+ string(REPLACE "." "/" _plugin_path "${_qml_uri}")
+
+ if (${_version} VERSION_GREATER_EQUAL 2.0)
+ string(REGEX MATCH "^([0-9]+)" _version_major ${_version})
+ set(_plugin_path "${_plugin_path}.${_version_major}")
+ endif()
+
+ get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})
+
+ if (NOT BUILD_SHARED_LIBS)
+ _ecm_qmlmodule_generate_qrc(${ARG_TARGET})
+
+ if (${_qml_only})
+ # If we do not have any C++ sources for the target, we need a way to
+ # compile the generated QRC file. So generate a very plain C++ QML
+ # plugin that we include in the target.
+ configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_H} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.h @ONLY)
+ configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_CPP} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp @ONLY)
+
+ target_sources(${ARG_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp)
+ if (TARGET Qt::Qml)
+ target_link_libraries(${ARG_TARGET} PRIVATE Qt::Qml)
+ else()
+ target_link_libraries(${ARG_TARGET} PRIVATE Qt5::Qml)
+ endif()
+ endif()
+
+ install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})
+
+ return()
+ endif()
+
+ get_target_property(_runtime_output_dir ${ARG_TARGET} RUNTIME_OUTPUT_DIRECTORY)
+ if (NOT ${_runtime_output_dir})
+ if ("${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" STREQUAL "")
+ set(_runtime_output_dir ${CMAKE_CURRENT_BINARY_DIR})
+ else()
+ set(_runtime_output_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+ endif()
+ endif()
+
+ add_custom_command(
+ TARGET ${ARG_TARGET}
+ POST_BUILD
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${_runtime_output_dir}/${_plugin_path}
+ BYPRODUCTS ${_runtime_output_dir}/${_plugin_path}
+ )
+
+ get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
+ install(FILES ${_qmldir_file} DESTINATION ${ARG_DESTINATION}/${_plugin_path} RENAME "qmldir")
+
+ add_custom_command(
+ TARGET ${ARG_TARGET}
+ POST_BUILD
+ WORKING_DIRECTORY ${_runtime_output_dir}
+ COMMAND ${CMAKE_COMMAND} -E copy ${_qmldir_file} ${_plugin_path}/qmldir
+ )
+
+ if (NOT ${_qml_only})
+ install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})
+
+ add_custom_command(
+ TARGET ${ARG_TARGET}
+ POST_BUILD
+ WORKING_DIRECTORY ${_runtime_output_dir}
+ COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${ARG_TARGET}> ${_plugin_path}
+ )
+ endif()
+
+ get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
+ foreach(_file ${_qml_files})
+ get_filename_component(_filename ${_file} NAME)
+ get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})
+
+ set(_file_path "${_plugin_path}/")
+ if (NOT "${_path}" STREQUAL "")
+ set(_file_path "${_plugin_path}/${_path}/")
+ endif()
+
+ install(FILES ${_file} DESTINATION ${ARG_DESTINATION}/${_file_path})
+
+ add_custom_command(
+ TARGET ${ARG_TARGET}
+ POST_BUILD
+ WORKING_DIRECTORY ${_runtime_output_dir}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${_file_path}
+ COMMAND ${CMAKE_COMMAND} -E copy ${_file} ${_file_path}
+ BYPRODUCTS ${_file_path}/${_filename}
+ )
+ endforeach()
+endfunction()
diff --git a/modules/ECMQmlModule.cpp.in b/modules/ECMQmlModule.cpp.in
new file mode 100644
index 00000000..07be0bc3
--- /dev/null
+++ b/modules/ECMQmlModule.cpp.in
@@ -0,0 +1,12 @@
+// This file is autogenerated by ECMQmlModule to support static QML-only plugins.
+
+#include "QmlModule.h"
+
+#include <QQmlEngine>
+
+void QmlModule::registerTypes(const char* uri)
+{
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("@_qml_uri@"));
+
+ Q_INIT_RESOURCE(@ARG_TARGET@);
+}
diff --git a/modules/ECMQmlModule.cpp.in.license b/modules/ECMQmlModule.cpp.in.license
new file mode 100644
index 00000000..8ebd85e2
--- /dev/null
+++ b/modules/ECMQmlModule.cpp.in.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+SPDX-License-Identifier: CC0
diff --git a/modules/ECMQmlModule.h.in b/modules/ECMQmlModule.h.in
new file mode 100644
index 00000000..6dd830de
--- /dev/null
+++ b/modules/ECMQmlModule.h.in
@@ -0,0 +1,17 @@
+// This file is autogenerated by ECMQmlModule to support static QML-only plugins.
+
+#ifndef QMLMODULE_H
+#define QMLMODULE_H
+
+#include <QQmlExtensionPlugin>
+
+class QmlModule : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+ void registerTypes(const char* uri) override;
+};
+
+#endif
diff --git a/modules/ECMQmlModule.h.in.license b/modules/ECMQmlModule.h.in.license
new file mode 100644
index 00000000..8ebd85e2
--- /dev/null
+++ b/modules/ECMQmlModule.h.in.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+SPDX-License-Identifier: CC0
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b2143ec9..7ffcfd03 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -209,6 +209,50 @@ set_package_properties(
)
if (TARGET Qt5::Quick)
add_test_macro(ECMQMLModules dummy)
+
+ set(ECMQmlModuleTest.static_full_EXTRA_OPTIONS
+ --build-target install
+ --build-options -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/static_full/install
+ -DBUILD_SHARED_LIBS=OFF
+ )
+ add_test_variant(ECMQmlModuleTest.static_full ECMQmlModuleTest
+ ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/static_full/check.cmake"
+ )
+ set(ECMQmlModuleTest.shared_full_EXTRA_OPTIONS
+ --build-target install
+ --build-options -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_full/install
+ -DBUILD_SHARED_LIBS=ON
+ )
+ add_test_variant(ECMQmlModuleTest.shared_full ECMQmlModuleTest
+ ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_full/check.cmake"
+ )
+ set(ECMQmlModuleTest.static_qmlonly_EXTRA_OPTIONS
+ --build-target install
+ --build-options -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/static_qmlonly/install
+ -DBUILD_SHARED_LIBS=OFF
+ -DQML_ONLY=ON
+ )
+ add_test_variant(ECMQmlModuleTest.static_qmlonly ECMQmlModuleTest
+ ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/static_qmlonly/check.cmake"
+ )
+ set(ECMQmlModuleTest.shared_qmlonly_EXTRA_OPTIONS
+ --build-target install
+ --build-options -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_qmlonly/install
+ -DBUILD_SHARED_LIBS=ON
+ -DQML_ONLY=ON
+ )
+ add_test_variant(ECMQmlModuleTest.shared_qmlonly ECMQmlModuleTest
+ ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_qmlonly/check.cmake"
+ )
+ set(ECMQmlModuleTest.shared_depends_EXTRA_OPTIONS
+ --build-target install
+ --build-options -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_depends/install
+ -DBUILD_SHARED_LIBS=ON
+ -DDEPENDS=ON
+ )
+ add_test_variant(ECMQmlModuleTest.shared_depends ECMQmlModuleTest
+ ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/ECMQmlModuleTest/shared_depends/check.cmake"
+ )
endif()
set(ECMConfiguredInstallTest_EXTRA_OPTIONS
diff --git a/tests/ECMQmlModuleTest/CMakeLists.txt b/tests/ECMQmlModuleTest/CMakeLists.txt
new file mode 100644
index 00000000..49d76594
--- /dev/null
+++ b/tests/ECMQmlModuleTest/CMakeLists.txt
@@ -0,0 +1,32 @@
+#
+# SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+project(extra-cmake-modules)
+cmake_minimum_required(VERSION 3.5)
+
+set(ECM_MODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../modules)
+set(CMAKE_MODULE_PATH "${ECM_FIND_MODULE_DIR}" "${ECM_MODULE_DIR}")
+
+find_package(Qt5 REQUIRED COMPONENTS Qml)
+include(ECMQmlModule)
+
+if(QML_ONLY)
+ ecm_add_qml_module(TestModule URI Test NO_PLUGIN)
+else()
+ ecm_add_qml_module(TestModule URI Test)
+ target_sources(TestModule PRIVATE qmlmodule.cpp)
+ target_link_libraries(TestModule Qt5::Qml)
+endif()
+
+if (DEPENDS)
+ ecm_add_qml_module_dependencies(TestModule DEPENDS OtherTest)
+endif()
+
+ecm_target_qml_sources(TestModule SOURCES QmlModule.qml)
+
+ecm_finalize_qml_module(TestModule DESTINATION "test")
+
+# this will be run by CTest
+configure_file(check.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/check.cmake" @ONLY)
diff --git a/tests/ECMQmlModuleTest/QmlModule.qml b/tests/ECMQmlModuleTest/QmlModule.qml
new file mode 100644
index 00000000..ce16c9db
--- /dev/null
+++ b/tests/ECMQmlModuleTest/QmlModule.qml
@@ -0,0 +1,11 @@
+/**
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import QtQuick 2.15
+
+Item {
+
+}
diff --git a/tests/ECMQmlModuleTest/check.cmake.in b/tests/ECMQmlModuleTest/check.cmake.in
new file mode 100644
index 00000000..af64a120
--- /dev/null
+++ b/tests/ECMQmlModuleTest/check.cmake.in
@@ -0,0 +1,64 @@
+#
+# SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+set(SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@")
+set(INSTALL_DIR "@CMAKE_INSTALL_PREFIX@/test")
+set(SHARED "@BUILD_SHARED_LIBS@")
+set(QML_ONLY "@QML_ONLY@")
+set(DEPENDS "@DEPENDS@")
+
+function(check_file_exists file)
+ if (NOT EXISTS ${file})
+ message(FATAL_ERROR "File \"${file}\" does not exist")
+ endif()
+endfunction()
+
+function (check_file_contents)
+ cmake_parse_arguments(ARGS "" "GENERATED;EXPECTED" "" ${ARGN})
+
+ if (NOT EXISTS "${ARGS_GENERATED}")
+ message(FATAL_ERROR "${ARGS_GENERATED} was not generated")
+ endif()
+ file(READ "${ARGS_GENERATED}" generated_contents)
+ if (NOT EXISTS "${ARGS_EXPECTED}")
+ message(FATAL_ERROR "Original ${ARGS_EXPECTED} was not found")
+ endif()
+ file(READ "${ARGS_EXPECTED}" original_contents)
+ if (NOT "${generated_contents}" STREQUAL "${original_contents}")
+ message(FATAL_ERROR "${generated_file} contains '${generated_contents}' instead of '${original_contents}'")
+ endif()
+endfunction()
+
+if (SHARED)
+ check_file_contents(
+ GENERATED "${INSTALL_DIR}/Test/QmlModule.qml"
+ EXPECTED "${SOURCE_DIR}/QmlModule.qml"
+ )
+
+ if (NOT QML_ONLY AND NOT DEPENDS)
+ check_file_exists("${INSTALL_DIR}/Test/libTestModule.so")
+
+ check_file_contents(
+ GENERATED "${INSTALL_DIR}/Test/qmldir"
+ EXPECTED "${SOURCE_DIR}/qmldir_expected_full"
+ )
+ endif()
+
+ if (QML_ONLY AND NOT DEPENDS)
+ check_file_contents(
+ GENERATED "${INSTALL_DIR}/Test/qmldir"
+ EXPECTED "${SOURCE_DIR}/qmldir_expected_qmlonly"
+ )
+ endif()
+
+ if (DEPENDS)
+ check_file_contents(
+ GENERATED "${INSTALL_DIR}/Test/qmldir"
+ EXPECTED "${SOURCE_DIR}/qmldir_expected_depends"
+ )
+ endif()
+else()
+ check_file_exists("${INSTALL_DIR}/Test/libTestModule.a")
+endif()
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_depends b/tests/ECMQmlModuleTest/qmldir_expected_depends
new file mode 100644
index 00000000..2f373e2f
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_depends
@@ -0,0 +1,6 @@
+# This file was automatically generated by ECMQmlModule and should not be modified
+module Test
+plugin TestModule
+classname TestModule
+depends OtherTest
+QmlModule 1.0 QmlModule.qml
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_depends.license b/tests/ECMQmlModuleTest/qmldir_expected_depends.license
new file mode 100644
index 00000000..8ebd85e2
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_depends.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+SPDX-License-Identifier: CC0
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_full b/tests/ECMQmlModuleTest/qmldir_expected_full
new file mode 100644
index 00000000..9a13246c
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_full
@@ -0,0 +1,5 @@
+# This file was automatically generated by ECMQmlModule and should not be modified
+module Test
+plugin TestModule
+classname TestModule
+QmlModule 1.0 QmlModule.qml
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_full.license b/tests/ECMQmlModuleTest/qmldir_expected_full.license
new file mode 100644
index 00000000..8ebd85e2
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_full.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+SPDX-License-Identifier: CC0
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_qmlonly b/tests/ECMQmlModuleTest/qmldir_expected_qmlonly
new file mode 100644
index 00000000..01823063
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_qmlonly
@@ -0,0 +1,3 @@
+# This file was automatically generated by ECMQmlModule and should not be modified
+module Test
+QmlModule 1.0 QmlModule.qml
diff --git a/tests/ECMQmlModuleTest/qmldir_expected_qmlonly.license b/tests/ECMQmlModuleTest/qmldir_expected_qmlonly.license
new file mode 100644
index 00000000..8ebd85e2
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmldir_expected_qmlonly.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+SPDX-License-Identifier: CC0
diff --git a/tests/ECMQmlModuleTest/qmlmodule.cpp b/tests/ECMQmlModuleTest/qmlmodule.cpp
new file mode 100644
index 00000000..a1552a86
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmlmodule.cpp
@@ -0,0 +1,14 @@
+/**
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "qmlmodule.h"
+
+#include <QtQml/QQmlEngine>
+
+void QmlModule::registerTypes(const char* uri)
+{
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("Test"));
+}
diff --git a/tests/ECMQmlModuleTest/qmlmodule.h b/tests/ECMQmlModuleTest/qmlmodule.h
new file mode 100644
index 00000000..49cd83a9
--- /dev/null
+++ b/tests/ECMQmlModuleTest/qmlmodule.h
@@ -0,0 +1,21 @@
+/**
+ * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef QMLMODULE_H
+#define QMLMODULE_H
+
+#include <QtQml/QQmlExtensionPlugin>
+
+class QmlModule : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+ void registerTypes(const char* uri) override;
+};
+
+#endif