From 85708ed73790e8a0391ee958f5e7fd347e2edb42 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 2 Oct 2025 12:12:25 +0100 Subject: [PATCH 1/6] qt-build-utils: add QmlDir and QmlUri builders --- .gitignore | 2 +- crates/qt-build-utils/src/builder/mod.rs | 10 ++ crates/qt-build-utils/src/builder/qmldir.rs | 136 ++++++++++++++++++++ crates/qt-build-utils/src/builder/qmluri.rs | 74 +++++++++++ crates/qt-build-utils/src/lib.rs | 31 ++--- 5 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 crates/qt-build-utils/src/builder/mod.rs create mode 100644 crates/qt-build-utils/src/builder/qmldir.rs create mode 100644 crates/qt-build-utils/src/builder/qmluri.rs diff --git a/.gitignore b/.gitignore index 28c8063ba..c4da92783 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ .vscode/ **/target/ **/coverage/ -**/build*/ +**/build/ cargo/* **/mdbook/* **/mdbook-linkcheck/* diff --git a/crates/qt-build-utils/src/builder/mod.rs b/crates/qt-build-utils/src/builder/mod.rs new file mode 100644 index 000000000..0fec8a310 --- /dev/null +++ b/crates/qt-build-utils/src/builder/mod.rs @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +mod qmldir; +pub use qmldir::QmlDirBuilder; + +mod qmluri; +pub use qmluri::QmlUri; diff --git a/crates/qt-build-utils/src/builder/qmldir.rs b/crates/qt-build-utils/src/builder/qmldir.rs new file mode 100644 index 000000000..d57889f68 --- /dev/null +++ b/crates/qt-build-utils/src/builder/qmldir.rs @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::builder::qmluri::QmlUri; + +use std::io; + +/// QML module definition files builder +/// +/// A qmldir file is a plain-text file that contains the commands +pub struct QmlDirBuilder { + class_name: Option, + plugin: Option<(bool, String)>, + type_info: Option, + uri: QmlUri, +} + +impl QmlDirBuilder { + /// Construct a [QmlDirBuilder] using the give [QmlUriBuilder] for the + /// module identifier + pub fn new(uri: QmlUri) -> Self { + Self { + class_name: None, + plugin: None, + type_info: None, + uri, + } + } + + /// Writer the resultant qmldir text file contents + pub fn write(self, writer: &mut impl io::Write) -> io::Result<()> { + // Module is mandatory + writeln!(writer, "module {}", self.uri.as_dots())?; + + // Plugin, classname, and typeinfo are optional + if let Some((optional, name)) = self.plugin { + if optional { + writeln!(writer, "optional plugin {name}")?; + } else { + writeln!(writer, "plugin {name}")?; + } + } + + if let Some(name) = self.class_name { + writeln!(writer, "classname {name}")?; + } + + if let Some(file) = self.type_info { + writeln!(writer, "typeinfo {file}")?; + } + + // Prefer is always specified for now + writeln!(writer, "prefer :/qt/qml/{}/", self.uri.as_dirs()) + } + + /// 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. + // + // TODO: is required for C++ plugins, is it required when plugin? + pub fn class_name(mut self, class_name: impl Into) -> Self { + self.class_name = Some(class_name.into()); + self + } + + /// Declares a plugin to be made available by the module. + /// + /// optional denotes that the plugin itself does not contain any relevant code + /// and only serves to load a library it links to. If given, and if any types + /// for the module are already available, indicating that the library has been + /// loaded by some other means, QML will not load the plugin. + /// + /// name is the plugin library name. This is usually not the same as the file + /// name of the plugin binary, which is platform dependent. For example, the + /// library MyAppTypes would produce libMyAppTypes.so on Linux and MyAppTypes.dll + /// on Windows. + /// + /// Only zero or one plugin is supported, otherwise a panic will occur. + pub fn plugin(mut self, name: impl Into, optional: bool) -> Self { + // Only support zero or one plugin for now + // it is not recommended to have more than one anyway + if self.plugin.is_some() { + panic!("Only zero or one plugin is supported currently"); + } + + self.plugin = Some((optional, name.into())); + self + } + + /// 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. File is the (relative) file name of a + /// .qmltypes file. + pub fn type_info(mut self, file: impl Into) -> Self { + self.type_info = Some(file.into()); + self + } + + // TODO: add further optional entries + // object type declaration + // internal object type declaration + // javascript resource definition + // module dependencies declaration + // module import declaration + // designer support declaration +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn qml_dir() { + let mut result = Vec::new(); + QmlDirBuilder::new(QmlUri::new(["com", "kdab"])) + .class_name("C") + .plugin("P", true) + .type_info("T") + .write(&mut result) + .unwrap(); + assert_eq!( + String::from_utf8(result).unwrap(), + "module com.kdab +optional plugin P +classname C +typeinfo T +prefer :/qt/qml/com/kdab/ +" + ); + } +} diff --git a/crates/qt-build-utils/src/builder/qmluri.rs b/crates/qt-build-utils/src/builder/qmluri.rs new file mode 100644 index 000000000..34c082008 --- /dev/null +++ b/crates/qt-build-utils/src/builder/qmluri.rs @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +/// A builder for representing a QML uri +pub struct QmlUri { + uri: Vec, +} + +impl From<&str> for QmlUri { + fn from(value: &str) -> Self { + Self::new(value.split('.')) + } +} + +impl QmlUri { + /// Construct a [QmlUriBuilder] from a given string + /// + /// If the uri segments are not alphanumeric this will panic + pub fn new(uri: impl IntoIterator>) -> Self { + let uri: Vec<_> = uri.into_iter().map(Into::into).collect(); + + // Only allow alphanumeric uri parts for now + if uri + .iter() + .any(|part| part.chars().any(|c| !c.is_ascii_alphanumeric())) + { + panic!("QML uri parts must be alphanumeric"); + } + + Self { uri } + } + + /// Retrieve the QML uri in directory form + pub fn as_dirs(&self) -> String { + self.uri.join("/") + } + + /// Retrieve the QML uri in dot form + pub fn as_dots(&self) -> String { + self.uri.join(".") + } + + /// Retrieve the QML uri in underscore form + pub fn as_underscores(&self) -> String { + self.uri.join("_") + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn uri() { + assert_eq!(QmlUri::from("a.b.c").uri, ["a", "b", "c"]); + assert_eq!(QmlUri::new(["a", "b", "c"]).uri, ["a", "b", "c"]); + } + + #[test] + #[should_panic] + fn uri_invalid() { + QmlUri::new(["a,b"]); + } + + #[test] + fn as_n() { + let uri = QmlUri::new(["a", "b", "c"]); + assert_eq!(uri.as_dirs(), "a/b/c"); + assert_eq!(uri.as_dots(), "a.b.c"); + assert_eq!(uri.as_underscores(), "a_b_c"); + } +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index fffec4f9e..cece55add 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -15,6 +15,9 @@ #![allow(clippy::too_many_arguments)] +mod builder; +pub use builder::{QmlDirBuilder, QmlUri}; + mod error; pub use error::QtBuildError; @@ -150,7 +153,11 @@ impl QtBuild { qml_files: &[impl AsRef], qrc_files: &[impl AsRef], ) -> QmlModuleRegistrationFiles { - let qml_uri_dirs = uri.replace('.', "/"); + let qml_uri = QmlUri::new(uri.split('.')); + let qml_uri_dirs = qml_uri.as_dirs(); + let qml_uri_underscores = qml_uri.as_underscores(); + let plugin_type_info = "plugin.qmltypes"; + let plugin_class_name = format!("{}_plugin", qml_uri_underscores); let out_dir = env::var("OUT_DIR").unwrap(); let qt_build_utils_dir = PathBuf::from(format!("{out_dir}/qt-build-utils")); @@ -159,24 +166,18 @@ impl QtBuild { let qml_module_dir = qt_build_utils_dir.join("qml_modules").join(&qml_uri_dirs); std::fs::create_dir_all(&qml_module_dir).expect("Could not create QML module directory"); - let qml_uri_underscores = uri.replace('.', "_"); - let qmltypes_path = qml_module_dir.join("plugin.qmltypes"); - let plugin_class_name = format!("{qml_uri_underscores}_plugin"); + let qmltypes_path = qml_module_dir.join(plugin_type_info); // Generate qmldir file let qmldir_file_path = qml_module_dir.join("qmldir"); { - let mut qmldir = File::create(&qmldir_file_path).expect("Could not create qmldir file"); - write!( - qmldir, - "module {uri} -optional plugin {plugin_name} -classname {plugin_class_name} -typeinfo plugin.qmltypes -prefer :/qt/qml/{qml_uri_dirs}/ -" - ) - .expect("Could not write qmldir file"); + let mut file = File::create(&qmldir_file_path).expect("Could not create qmldir file"); + QmlDirBuilder::new(qml_uri) + .plugin(plugin_name, true) + .class_name(&plugin_class_name) + .type_info(plugin_type_info) + .write(&mut file) + .expect("Could not write qmldir file"); } // Generate .qrc file and run rcc on it From 5cddce82806f28b0a8612b4fab7ae4a78d1d21be Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 2 Oct 2025 15:46:53 +0100 Subject: [PATCH 2/6] qt-build-utils: add a QResourceBuilder --- crates/qt-build-utils/src/builder/mod.rs | 3 + .../qt-build-utils/src/builder/qresource.rs | 229 ++++++++++++++++++ crates/qt-build-utils/src/lib.rs | 71 +++--- 3 files changed, 267 insertions(+), 36 deletions(-) create mode 100644 crates/qt-build-utils/src/builder/qresource.rs diff --git a/crates/qt-build-utils/src/builder/mod.rs b/crates/qt-build-utils/src/builder/mod.rs index 0fec8a310..621975add 100644 --- a/crates/qt-build-utils/src/builder/mod.rs +++ b/crates/qt-build-utils/src/builder/mod.rs @@ -8,3 +8,6 @@ pub use qmldir::QmlDirBuilder; mod qmluri; pub use qmluri::QmlUri; + +mod qresource; +pub use qresource::{QResource, QResourceFile, QResources}; diff --git a/crates/qt-build-utils/src/builder/qresource.rs b/crates/qt-build-utils/src/builder/qresource.rs new file mode 100644 index 000000000..94ffde65f --- /dev/null +++ b/crates/qt-build-utils/src/builder/qresource.rs @@ -0,0 +1,229 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::{ + io, + path::{Path, PathBuf}, +}; + +/// An individial `` line within a [QResource] +pub struct QResourceFile { + alias: Option, + // TODO: compression + // TODO: empty + path: PathBuf, +} + +impl> From for QResourceFile { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl QResourceFile { + /// Construct a [QResourceFile] + pub fn new(path: impl AsRef) -> Self { + Self { + alias: None, + path: path.as_ref().to_path_buf(), + } + } + + /// Specify an alias for the [QResourceFile] + pub fn alias(mut self, alias: impl Into) -> Self { + self.alias = Some(alias.into()); + self + } + + fn write(self, writer: &mut impl io::Write) -> io::Result<()> { + let alias = self + .alias + .map(|alias| format!(" alias=\"{}\"", alias.escape_default())) + .unwrap_or_default(); + let path = self.path.to_string_lossy(); + write!(writer, "{path}") + } +} + +/// A `` block within a [QResources] +pub struct QResource { + language: Option, + prefix: Option, + files: Vec, +} + +impl Default for QResource { + fn default() -> Self { + Self::new() + } +} + +impl> From for QResource { + fn from(value: T) -> Self { + Self::new().file(value) + } +} + +impl QResource { + /// Construct a [QResource] + pub fn new() -> Self { + Self { + language: None, + prefix: None, + files: vec![], + } + } + + /// Add a [QResourceFile] to the [QResource] + pub fn file>(mut self, file: T) -> Self { + self.files.push(file.into()); + self + } + + /// Add multiple [QResourceFile] to the [QResource] + pub fn files>(mut self, files: impl IntoIterator) -> Self { + for file in files.into_iter() { + self.files.push(file.into()); + } + self + } + + /// Specify a language for the `` + pub fn language(mut self, language: impl Into) -> Self { + self.language = Some(language.into()); + self + } + + /// Specify a prefix for the `` + pub fn prefix(mut self, prefix: impl Into) -> Self { + self.prefix = Some(prefix.into()); + self + } + + fn write(self, writer: &mut impl io::Write) -> io::Result<()> { + let language = self + .language + .map(|language| format!(" language=\"{}\"", language.escape_default())) + .unwrap_or_default(); + let prefix = self + .prefix + .map(|prefix| format!(" prefix=\"{}\"", prefix.escape_default())) + .unwrap_or_default(); + + write!(writer, "")?; + for file in self.files.into_iter() { + file.write(writer)?; + } + write!(writer, "") + } +} + +/// A helper for building Qt resource collection files +pub struct QResources { + resources: Vec, +} + +impl Default for QResources { + fn default() -> Self { + Self::new() + } +} + +impl>> From for QResources { + fn from(value: T) -> Self { + Self::new().resource(QResource::new().files(value)) + } +} + +impl QResources { + /// Construct a [QResource] + pub fn new() -> Self { + Self { resources: vec![] } + } + + /// Add a [QResource] to the [QResources] + pub fn resource>(mut self, resource: T) -> Self { + self.resources.push(resource.into()); + self + } + + /// Add multiple [QResource] to the [QResources] + pub fn resources>(mut self, resources: impl IntoIterator) -> Self { + for resource in resources.into_iter() { + self.resources.push(resource.into()); + } + self + } + + /// Convert to a string representation + pub fn write(self, writer: &mut impl io::Write) -> io::Result<()> { + write!(writer, "")?; + for resource in self.resources.into_iter() { + resource.write(writer)?; + } + write!(writer, "") + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn resource_file() { + let mut result = Vec::new(); + QResourceFile::new("path") + .alias("alias") + .write(&mut result) + .unwrap(); + assert_eq!( + String::from_utf8(result).unwrap(), + "path" + ); + } + + #[test] + fn resource() { + let mut result = Vec::new(); + QResource::new() + .language("language") + .prefix("prefix") + .write(&mut result) + .unwrap(); + assert_eq!( + String::from_utf8(result).unwrap(), + "" + ); + } + + #[test] + fn resources() { + let mut result = Vec::new(); + QResources::new() + .resources(["a", "b"]) + .resource( + QResource::new() + .prefix("prefix") + .files(["c", "d"]) + .file(QResourceFile::new("e").alias("alias")), + ) + .write(&mut result) + .unwrap(); + assert_eq!( + String::from_utf8(result).unwrap(), + "abcde" + ); + } + + #[test] + fn resources_from_files() { + let mut result = Vec::new(); + QResources::from(["a", "b"]).write(&mut result).unwrap(); + assert_eq!( + String::from_utf8(result).unwrap(), + "ab" + ); + } +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index cece55add..83ccc6dac 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -16,7 +16,7 @@ #![allow(clippy::too_many_arguments)] mod builder; -pub use builder::{QmlDirBuilder, QmlUri}; +pub use builder::{QResource, QResourceFile, QResources, QmlDirBuilder, QmlUri}; mod error; pub use error::QtBuildError; @@ -47,7 +47,6 @@ mod utils; use std::{ env, fs::File, - io::Write, path::{Path, PathBuf}, }; @@ -184,41 +183,41 @@ impl QtBuild { let qrc_path = qml_module_dir.join(format!("qml_module_resources_{qml_uri_underscores}.qrc")); { - fn qrc_file_line(file_path: &impl AsRef) -> String { - let path_display = file_path.as_ref().display(); - format!( - " {}\n", - path_display, - std::fs::canonicalize(file_path) - .unwrap_or_else(|_| panic!("Could not canonicalize path {path_display}")) - .display() - ) - } - - let mut qml_files_qrc = String::new(); - for file_path in qml_files { - qml_files_qrc.push_str(&qrc_file_line(file_path)); - } - for file_path in qrc_files { - qml_files_qrc.push_str(&qrc_file_line(file_path)); - } - - let mut qrc = File::create(&qrc_path).expect("Could not create qrc file"); let qml_module_dir_str = qml_module_dir.to_str().unwrap(); - write!( - qrc, - r#" - - {qml_module_dir_str} - - -{qml_files_qrc} - {qml_module_dir_str}/qmldir - - -"# - ) - .expect("Could note write qrc file"); + let qml_uri_dirs_prefix = format!("/qt/qml/{qml_uri_dirs}"); + let mut qrc = File::create(&qrc_path).expect("Could not create qrc file"); + QResources::new() + .resource(QResource::new().prefix("/".to_string()).file( + QResourceFile::new(qml_module_dir_str).alias(qml_uri_dirs_prefix.clone()), + )) + .resource({ + let mut resource = QResource::new().prefix(qml_uri_dirs_prefix.clone()).file( + QResourceFile::new(format!("{qml_module_dir_str}/qmldir")) + .alias("qmldir".to_string()), + ); + + fn resource_add_path(resource: QResource, path: &Path) -> QResource { + let resolved = std::fs::canonicalize(path) + .unwrap_or_else(|_| { + panic!("Could not canonicalize path {}", path.display()) + }) + .display() + .to_string(); + resource + .file(QResourceFile::new(resolved).alias(path.display().to_string())) + } + + for path in qml_files { + resource = resource_add_path(resource, path.as_ref()); + } + for path in qrc_files { + resource = resource_add_path(resource, path.as_ref()); + } + + resource + }) + .write(&mut qrc) + .expect("Could note write qrc file"); } // Run qmlcachegen From a29341755a428bf79caab7a996f233cbadbc4463 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 2 Oct 2025 16:09:28 +0100 Subject: [PATCH 3/6] qt-build-utils: add QmlPluginCppBuilder for the extension plugin --- crates/qt-build-utils/src/builder/mod.rs | 3 + .../src/builder/qmlplugincpp.rs | 89 +++++++++++++++++++ crates/qt-build-utils/src/builder/qmluri.rs | 1 + crates/qt-build-utils/src/lib.rs | 64 +++---------- 4 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 crates/qt-build-utils/src/builder/qmlplugincpp.rs diff --git a/crates/qt-build-utils/src/builder/mod.rs b/crates/qt-build-utils/src/builder/mod.rs index 621975add..42197aca9 100644 --- a/crates/qt-build-utils/src/builder/mod.rs +++ b/crates/qt-build-utils/src/builder/mod.rs @@ -6,6 +6,9 @@ mod qmldir; pub use qmldir::QmlDirBuilder; +mod qmlplugincpp; +pub use qmlplugincpp::QmlPluginCppBuilder; + mod qmluri; pub use qmluri::QmlUri; diff --git a/crates/qt-build-utils/src/builder/qmlplugincpp.rs b/crates/qt-build-utils/src/builder/qmlplugincpp.rs new file mode 100644 index 000000000..ddbe5c324 --- /dev/null +++ b/crates/qt-build-utils/src/builder/qmlplugincpp.rs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::io; + +use crate::QmlUri; + +/// A builder for representing a QML Extension Plugin C++ code +pub struct QmlPluginCppBuilder { + plugin_class_name: String, + qml_cache: bool, + uri: QmlUri, +} + +impl QmlPluginCppBuilder { + /// Construct a [QmlPluginCppBuilder] from a uri and plugin class name + pub fn new(uri: QmlUri, plugin_class_name: impl Into) -> Self { + // TODO: validate plugin class name + + Self { + plugin_class_name: plugin_class_name.into(), + qml_cache: false, + uri, + } + } + + /// Whether to enable qmlcache methods + pub fn qml_cache(mut self, enabled: bool) -> Self { + self.qml_cache = enabled; + self + } + + /// Write the resultant QML extension plugin C++ contents + pub fn write(self, writer: &mut impl io::Write) -> io::Result<()> { + let plugin_class_name = self.plugin_class_name; + let qml_uri_underscores = self.uri.as_underscores(); + + let mut declarations = Vec::default(); + let mut usages = Vec::default(); + + let mut generate_usage = |return_type: &str, function_name: &str| { + declarations.push(format!("extern {return_type} {function_name}();")); + usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);")); + }; + + // This function is generated by qmltyperegistrar + generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}")); + generate_usage( + "int", + &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), + ); + + if self.qml_cache { + generate_usage( + "int", + &format!("qInitResources_qmlcache_{qml_uri_underscores}"), + ); + } + let declarations = declarations.join("\n"); + let usages = usages.join("\n"); + write!( + writer, + r#" +#include + +// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in +// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h +{declarations} + +class {plugin_class_name} : public QQmlEngineExtensionPlugin +{{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlEngineExtensionInterface") + +public: + {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent) + {{ + {usages} + }} +}}; + +// The moc-generated cpp file doesn't compile on its own; it needs to be #included here. +#include "moc_{plugin_class_name}.cpp.cpp" +"# + ) + } +} diff --git a/crates/qt-build-utils/src/builder/qmluri.rs b/crates/qt-build-utils/src/builder/qmluri.rs index 34c082008..00f43d9b7 100644 --- a/crates/qt-build-utils/src/builder/qmluri.rs +++ b/crates/qt-build-utils/src/builder/qmluri.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 /// A builder for representing a QML uri +#[derive(Clone)] pub struct QmlUri { uri: Vec, } diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 83ccc6dac..b5296c4b1 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -16,7 +16,9 @@ #![allow(clippy::too_many_arguments)] mod builder; -pub use builder::{QResource, QResourceFile, QResources, QmlDirBuilder, QmlUri}; +pub use builder::{ + QResource, QResourceFile, QResources, QmlDirBuilder, QmlPluginCppBuilder, QmlUri, +}; mod error; pub use error::QtBuildError; @@ -171,7 +173,7 @@ impl QtBuild { let qmldir_file_path = qml_module_dir.join("qmldir"); { let mut file = File::create(&qmldir_file_path).expect("Could not create qmldir file"); - QmlDirBuilder::new(qml_uri) + QmlDirBuilder::new(qml_uri.clone()) .plugin(plugin_name, true) .class_name(&plugin_class_name) .type_info(plugin_type_info) @@ -264,58 +266,12 @@ impl QtBuild { let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp")); let include_path; { - let mut declarations = Vec::default(); - let mut usages = Vec::default(); - - let mut generate_usage = |return_type: &str, function_name: &str| { - declarations.push(format!("extern {return_type} {function_name}();")); - usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);")); - }; - - // This function is generated by qmltyperegistrar - generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}")); - generate_usage( - "int", - &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), - ); - - if !qml_files.is_empty() && !qmlcachegen_file_paths.is_empty() { - generate_usage( - "int", - &format!("qInitResources_qmlcache_{qml_uri_underscores}"), - ); - } - let declarations = declarations.join("\n"); - let usages = usages.join("\n"); - - std::fs::write( - &qml_plugin_cpp_path, - format!( - r#" -#include - -// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in -// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h -{declarations} - -class {plugin_class_name} : public QQmlEngineExtensionPlugin -{{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlEngineExtensionInterface") - -public: - {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent) - {{ - {usages} - }} -}}; - -// The moc-generated cpp file doesn't compile on its own; it needs to be #included here. -#include "moc_{plugin_class_name}.cpp.cpp" -"#, - ), - ) - .expect("Failed to write plugin definition"); + let mut file = File::create(&qml_plugin_cpp_path) + .expect("Could not create plugin definition file"); + QmlPluginCppBuilder::new(qml_uri, plugin_class_name.clone()) + .qml_cache(!qml_files.is_empty() && !qmlcachegen_file_paths.is_empty()) + .write(&mut file) + .expect("Failed to write plugin definition"); let moc_product = self.moc().compile( &qml_plugin_cpp_path, From b4a8bcc8f2e16b3a1a030bf328e994f1bc278c8b Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Mon, 6 Oct 2025 16:55:15 +0100 Subject: [PATCH 4/6] qt-build-utils: move builder into qml/ and qrc/ --- crates/qt-build-utils/src/lib.rs | 11 ++++++----- crates/qt-build-utils/src/{builder => qml}/mod.rs | 3 --- crates/qt-build-utils/src/{builder => qml}/qmldir.rs | 2 +- .../src/{builder => qml}/qmlplugincpp.rs | 0 crates/qt-build-utils/src/{builder => qml}/qmluri.rs | 0 .../src/{builder/qresource.rs => qrc.rs} | 0 6 files changed, 7 insertions(+), 9 deletions(-) rename crates/qt-build-utils/src/{builder => qml}/mod.rs (82%) rename crates/qt-build-utils/src/{builder => qml}/qmldir.rs (99%) rename crates/qt-build-utils/src/{builder => qml}/qmlplugincpp.rs (100%) rename crates/qt-build-utils/src/{builder => qml}/qmluri.rs (100%) rename crates/qt-build-utils/src/{builder/qresource.rs => qrc.rs} (100%) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index b5296c4b1..847304649 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -15,11 +15,6 @@ #![allow(clippy::too_many_arguments)] -mod builder; -pub use builder::{ - QResource, QResourceFile, QResources, QmlDirBuilder, QmlPluginCppBuilder, QmlUri, -}; - mod error; pub use error::QtBuildError; @@ -38,6 +33,12 @@ mod parse_cflags; mod platform; pub use platform::QtPlatformLinker; +mod qml; +pub use qml::{QmlDirBuilder, QmlPluginCppBuilder, QmlUri}; + +mod qrc; +pub use qrc::{QResource, QResourceFile, QResources}; + mod tool; pub use tool::{ MocArguments, MocProducts, QmlCacheArguments, QmlCacheProducts, QtTool, QtToolMoc, diff --git a/crates/qt-build-utils/src/builder/mod.rs b/crates/qt-build-utils/src/qml/mod.rs similarity index 82% rename from crates/qt-build-utils/src/builder/mod.rs rename to crates/qt-build-utils/src/qml/mod.rs index 42197aca9..0992522e7 100644 --- a/crates/qt-build-utils/src/builder/mod.rs +++ b/crates/qt-build-utils/src/qml/mod.rs @@ -11,6 +11,3 @@ pub use qmlplugincpp::QmlPluginCppBuilder; mod qmluri; pub use qmluri::QmlUri; - -mod qresource; -pub use qresource::{QResource, QResourceFile, QResources}; diff --git a/crates/qt-build-utils/src/builder/qmldir.rs b/crates/qt-build-utils/src/qml/qmldir.rs similarity index 99% rename from crates/qt-build-utils/src/builder/qmldir.rs rename to crates/qt-build-utils/src/qml/qmldir.rs index d57889f68..204d39d5c 100644 --- a/crates/qt-build-utils/src/builder/qmldir.rs +++ b/crates/qt-build-utils/src/qml/qmldir.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::builder::qmluri::QmlUri; +use crate::QmlUri; use std::io; diff --git a/crates/qt-build-utils/src/builder/qmlplugincpp.rs b/crates/qt-build-utils/src/qml/qmlplugincpp.rs similarity index 100% rename from crates/qt-build-utils/src/builder/qmlplugincpp.rs rename to crates/qt-build-utils/src/qml/qmlplugincpp.rs diff --git a/crates/qt-build-utils/src/builder/qmluri.rs b/crates/qt-build-utils/src/qml/qmluri.rs similarity index 100% rename from crates/qt-build-utils/src/builder/qmluri.rs rename to crates/qt-build-utils/src/qml/qmluri.rs diff --git a/crates/qt-build-utils/src/builder/qresource.rs b/crates/qt-build-utils/src/qrc.rs similarity index 100% rename from crates/qt-build-utils/src/builder/qresource.rs rename to crates/qt-build-utils/src/qrc.rs From 31d15935330bc240a9f7c9cae241745abdc6fb68 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Mon, 6 Oct 2025 17:09:02 +0100 Subject: [PATCH 5/6] qt-build-utils: allow underscores in QML uris --- crates/qt-build-utils/src/qml/qmluri.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/qt-build-utils/src/qml/qmluri.rs b/crates/qt-build-utils/src/qml/qmluri.rs index 00f43d9b7..55026025d 100644 --- a/crates/qt-build-utils/src/qml/qmluri.rs +++ b/crates/qt-build-utils/src/qml/qmluri.rs @@ -23,11 +23,11 @@ impl QmlUri { let uri: Vec<_> = uri.into_iter().map(Into::into).collect(); // Only allow alphanumeric uri parts for now - if uri - .iter() - .any(|part| part.chars().any(|c| !c.is_ascii_alphanumeric())) - { - panic!("QML uri parts must be alphanumeric"); + if uri.iter().any(|part| { + part.chars() + .any(|c| !(c.is_ascii_alphanumeric() || c == '_')) + }) { + panic!("QML uri parts must be alphanumeric: {uri:?}"); } Self { uri } @@ -67,9 +67,9 @@ mod test { #[test] fn as_n() { - let uri = QmlUri::new(["a", "b", "c"]); - assert_eq!(uri.as_dirs(), "a/b/c"); - assert_eq!(uri.as_dots(), "a.b.c"); - assert_eq!(uri.as_underscores(), "a_b_c"); + let uri = QmlUri::new(["a", "b", "c_d"]); + assert_eq!(uri.as_dirs(), "a/b/c_d"); + assert_eq!(uri.as_dots(), "a.b.c_d"); + assert_eq!(uri.as_underscores(), "a_b_c_d"); } } From 9aabf6c1915925167e9abbe5fe7a6ced1f28843d Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Mon, 6 Oct 2025 17:25:03 +0100 Subject: [PATCH 6/6] qt-build-utils: fix docs pointing to old QmlUri name --- crates/qt-build-utils/src/qml/qmldir.rs | 2 +- crates/qt-build-utils/src/qml/qmluri.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/qt-build-utils/src/qml/qmldir.rs b/crates/qt-build-utils/src/qml/qmldir.rs index 204d39d5c..c32d252a5 100644 --- a/crates/qt-build-utils/src/qml/qmldir.rs +++ b/crates/qt-build-utils/src/qml/qmldir.rs @@ -18,7 +18,7 @@ pub struct QmlDirBuilder { } impl QmlDirBuilder { - /// Construct a [QmlDirBuilder] using the give [QmlUriBuilder] for the + /// Construct a [QmlDirBuilder] using the give [QmlUri] for the /// module identifier pub fn new(uri: QmlUri) -> Self { Self { diff --git a/crates/qt-build-utils/src/qml/qmluri.rs b/crates/qt-build-utils/src/qml/qmluri.rs index 55026025d..a5ed791a6 100644 --- a/crates/qt-build-utils/src/qml/qmluri.rs +++ b/crates/qt-build-utils/src/qml/qmluri.rs @@ -16,7 +16,7 @@ impl From<&str> for QmlUri { } impl QmlUri { - /// Construct a [QmlUriBuilder] from a given string + /// Construct a [QmlUri] from a given string /// /// If the uri segments are not alphanumeric this will panic pub fn new(uri: impl IntoIterator>) -> Self {