qt_build_utils/qml/
qmlplugincpp.rs

1// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use std::io;
7
8use crate::{Initializer, QmlUri};
9
10/// The build type of a Qt plugin
11#[non_exhaustive]
12#[derive(Clone, Copy, PartialEq, Eq)]
13pub enum PluginType {
14    /// A statically linked plugin
15    Static,
16    /// A dynamically linked plugin (sometimes called a MODULE plugin in Qt/CMake)
17    ///
18    /// Note: There can only be one dynamic plugin per dynamic library!
19    Dynamic,
20}
21
22/// A builder for representing a QML Extension Plugin C++ code
23pub struct QmlPluginCppBuilder {
24    plugin_class_name: String,
25    qml_cache: bool,
26    uri: QmlUri,
27    plugin_type: PluginType,
28}
29
30impl QmlPluginCppBuilder {
31    /// Construct a [QmlPluginCppBuilder] from a uri and plugin class name
32    pub fn new(uri: QmlUri, plugin_class_name: impl Into<String>) -> Self {
33        // TODO: validate plugin class name
34
35        Self {
36            plugin_class_name: plugin_class_name.into(),
37            qml_cache: false,
38            plugin_type: PluginType::Static,
39            uri,
40        }
41    }
42
43    /// Whether to enable qmlcache methods
44    pub fn qml_cache(mut self, enabled: bool) -> Self {
45        self.qml_cache = enabled;
46        self
47    }
48
49    /// Whether this is a static or dynamic plugin
50    ///
51    /// Note: For static plugins the QT_STATICPLUGIN definition should be set when compiling, for
52    /// dynamic plugins the QT_PLUGIN definition should be set when compiling.
53    /// See also: <https://doc.qt.io/qt-6/plugins-howto.html#creating-static-plugins>
54    ///
55    /// Note: There can only be one dynamic plugin per dynamic library!
56    pub fn plugin_type(mut self, plugin_type: PluginType) -> Self {
57        self.plugin_type = plugin_type;
58        self
59    }
60
61    /// Write the resultant QML extension plugin C++ contents
62    pub fn write(self, writer: &mut impl io::Write) -> io::Result<Initializer> {
63        let plugin_class_name = self.plugin_class_name;
64        let qml_uri_underscores = self.uri.as_underscores();
65
66        let mut declarations = Vec::default();
67        let mut usages = Vec::default();
68
69        let mut generate_usage = |return_type: &str, function_name: &str| {
70            declarations.push(format!("extern {return_type} {function_name}();"));
71            usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);"));
72        };
73
74        // This function is generated by qmltyperegistrar
75        generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}"));
76        generate_usage(
77            "int",
78            &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"),
79        );
80
81        if self.qml_cache {
82            generate_usage(
83                "int",
84                &format!("qInitResources_qmlcache_{qml_uri_underscores}"),
85            );
86        }
87        let declarations = declarations.join("\n");
88        let usages = usages.join("\n");
89        let init_fn_name = format!("init_cxx_qt_qml_module_{plugin_class_name}");
90        write!(
91            writer,
92            r#"
93#include <QtQml/qqmlextensionplugin.h>
94
95// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in
96// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h
97{declarations}
98
99class {plugin_class_name} : public QQmlEngineExtensionPlugin
100{{
101    Q_OBJECT
102    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlEngineExtensionInterface")
103
104public:
105    {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent)
106    {{
107        {usages}
108    }}
109}};
110
111extern "C" {{
112    // "drive-by initialization" causes the plugin class to be included in static linking scenarios
113    // Any function that is called from within this file causes the entire object file to be linked in.
114    // Therefore we provide an empty function that can be called at any point to ensure this file is linked in.
115    bool {init_fn_name}() {{
116        return true;
117    }}
118}}
119
120// The moc-generated cpp file doesn't compile on its own; it needs to be #included here.
121#include "moc_{plugin_class_name}.cpp.cpp"
122"#
123        )?;
124        let initializer = match self.plugin_type {
125            PluginType::Static => Initializer {
126                file: None,
127                init_call: None,
128                init_declaration: Some(format!(
129                    "#include <QtPlugin>\nQ_IMPORT_PLUGIN({plugin_class_name});"
130                )),
131            },
132            PluginType::Dynamic => Initializer::default_signature(&init_fn_name),
133        };
134        Ok(initializer)
135    }
136}