aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-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
5 files changed, 452 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