Skip to content

Commit e1e15e0

Browse files
Introduce QmlFile to specify per-file settings (#1356)
* Introduce QmlFile to specify per-file settings e.g. which version the file is and whether it is a singleton. * Use Rust edition 2021, not 2024 Our CI tests on our MSRV, which does not (yet) support 2024 edition * Fix clippy lint * Always create crate header dir in export directory * Fix `cargo test` for sub3 crate Before it failed with undefined symbols as the cxx-qt/cxx-qt-lib dependencies were not actually needed. * Fix CMake crate inclusion in examples
1 parent bcf918d commit e1e15e0

File tree

19 files changed

+270
-74
lines changed

19 files changed

+270
-74
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ members = [
2121
"examples/qml_multi_crates/rust/main",
2222
"examples/qml_multi_crates/rust/sub1",
2323
"examples/qml_multi_crates/rust/sub2",
24+
"examples/qml_multi_crates/rust/sub3",
2425
"examples/span-inspector",
2526

2627
"tests/basic_cxx_only/rust",

crates/cxx-qt-build/src/lib.rs

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ pub use opts::CxxQtBuildersOpts;
3131
pub use opts::QObjectHeaderOpts;
3232

3333
mod qml_modules;
34-
pub use qml_modules::QmlModule;
34+
pub use qml_modules::{QmlFile, QmlModule, QmlUri};
3535

3636
pub use qt_build_utils::MocArguments;
3737
use qt_build_utils::MocProducts;
3838
use qt_build_utils::QResources;
39-
use qt_build_utils::QmlUri;
4039
use quote::ToTokens;
4140
use std::{
4241
collections::HashSet,
@@ -433,10 +432,12 @@ impl CxxQtBuilder {
433432
/// .files(["src/cxxqt_object.rs"])
434433
/// .build();
435434
/// ```
435+
///
436+
/// Note: This will automatically add the `Qml` Qt module to the build (see [Self::qt_module]).
436437
pub fn new_qml_module(module: QmlModule) -> Self {
437438
let mut builder = Self::new();
438439
builder.qml_module = Some(module);
439-
builder
440+
builder.qt_module("Qml")
440441
}
441442

442443
/// Specify rust file paths to parse through the cxx-qt marco
@@ -881,8 +882,8 @@ impl CxxQtBuilder {
881882
cc_builder.define("QT_STATICPLUGIN", None);
882883

883884
// If any of the files inside the qml module change, then trigger a rerun
884-
for path in qml_module.qml_files {
885-
println!("cargo::rerun-if-changed={}", path.display());
885+
for file in qml_module.qml_files {
886+
println!("cargo::rerun-if-changed={}", file.path().display());
886887
}
887888

888889
let module_init_key = qml_module_init_key(&qml_module.uri);
@@ -1110,33 +1111,41 @@ extern "C" bool {init_fun}() {{
11101111
// to the generated files without any namespacing.
11111112
include_paths.push(header_root.join(&self.include_prefix));
11121113

1113-
const MAX_INCLUDE_DEPTH: usize = 6;
1114-
let crate_header_dir = self.crate_include_root.as_ref().map(|subdir| {
1115-
dir::manifest()
1116-
.expect("Could not find crate directory!")
1117-
.join(subdir)
1118-
});
1119-
if let Some(crate_header_dir) = crate_header_dir {
1120-
utils::best_effort_copy_headers(
1121-
crate_header_dir.as_path(),
1122-
header_root.join(crate_name()).as_path(),
1123-
MAX_INCLUDE_DEPTH,
1124-
// Emit rerun-if-changed for this directory as it is be part of the crate root it
1125-
// should not contain any generated files which may cause unwanted reruns.
1126-
true,
1127-
);
1128-
}
1129-
for include_dir in &self.additional_include_dirs {
1130-
utils::best_effort_copy_headers(
1131-
include_dir,
1132-
header_root.join(crate_name()).as_path(),
1133-
MAX_INCLUDE_DEPTH,
1134-
// Do not emit rerun-if-changed for this directory as it may not be part of the crate root
1135-
// and we do not know if these headers are generated or not.
1136-
// If they are generated by the build script, they should not be marked with
1137-
// rerun-if-changed, because they would cause unwanted reruns.
1138-
false,
1139-
);
1114+
// Export the generated headers for this crate
1115+
{
1116+
const MAX_INCLUDE_DEPTH: usize = 6;
1117+
let crate_header_dir = self.crate_include_root.as_ref().map(|subdir| {
1118+
dir::manifest()
1119+
.expect("Could not find crate directory!")
1120+
.join(subdir)
1121+
});
1122+
let crate_header_export_dir = header_root.join(crate_name());
1123+
// Make sure to always create the crate header root, as it is automatically added to
1124+
// the manifest's exported include prefixes.
1125+
std::fs::create_dir_all(&crate_header_export_dir)
1126+
.expect("Could not create crate header root directory");
1127+
if let Some(crate_header_dir) = crate_header_dir {
1128+
utils::best_effort_copy_headers(
1129+
crate_header_dir.as_path(),
1130+
&crate_header_export_dir,
1131+
MAX_INCLUDE_DEPTH,
1132+
// Emit rerun-if-changed for this directory as it is be part of the crate root it
1133+
// should not contain any generated files which may cause unwanted reruns.
1134+
true,
1135+
);
1136+
}
1137+
for include_dir in &self.additional_include_dirs {
1138+
utils::best_effort_copy_headers(
1139+
include_dir,
1140+
&crate_header_export_dir,
1141+
MAX_INCLUDE_DEPTH,
1142+
// Do not emit rerun-if-changed for this directory as it may not be part of the crate root
1143+
// and we do not know if these headers are generated or not.
1144+
// If they are generated by the build script, they should not be marked with
1145+
// rerun-if-changed, because they would cause unwanted reruns.
1146+
false,
1147+
);
1148+
}
11401149
}
11411150

11421151
Self::setup_cc_builder(&mut self.cc_builder, &include_paths);

crates/cxx-qt-build/src/qml_modules.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
//! This Rust module contains structs for registering QML modules.
77
8-
use std::path::{Path, PathBuf};
9-
10-
use qt_build_utils::QmlUri;
8+
pub use qt_build_utils::{QmlFile, QmlUri};
119

1210
/// This is a description of a QML module for building by the [crate::CxxQtBuilder].
1311
///
@@ -19,7 +17,7 @@ pub struct QmlModule {
1917
pub(crate) uri: QmlUri,
2018
pub(crate) version_major: usize,
2119
pub(crate) version_minor: usize,
22-
pub(crate) qml_files: Vec<PathBuf>,
20+
pub(crate) qml_files: Vec<QmlFile>,
2321
pub(crate) depends: Vec<String>,
2422
}
2523

@@ -66,14 +64,26 @@ impl QmlModule {
6664
///
6765
/// Additional resources such as images can be added to the Qt resources for the QML module by specifying
6866
/// the `qrc_files` field.
69-
pub fn qml_file(self, file: impl AsRef<Path>) -> Self {
67+
///
68+
/// If the Qml file starts is uppercase, it will be treated as a QML component and registered in the `qmldir` file.
69+
/// See [qt_build_utils::QmlFile] for more information on configuring the behavior of QML files.
70+
///
71+
/// Note that if no version is specified for the QML file, the version of the QML module will
72+
/// be used automatically.
73+
pub fn qml_file(self, file: impl Into<QmlFile>) -> Self {
7074
self.qml_files([file])
7175
}
7276

7377
/// Add multiple QML files to the module, see [Self::qml_file].
74-
pub fn qml_files(mut self, files: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
75-
self.qml_files
76-
.extend(files.into_iter().map(|p| p.as_ref().to_path_buf()));
78+
pub fn qml_files(mut self, files: impl IntoIterator<Item = impl Into<QmlFile>>) -> Self {
79+
self.qml_files.extend(files.into_iter().map(|p| {
80+
let qml_file = p.into();
81+
if qml_file.get_version().is_none() {
82+
qml_file.version(self.version_major, self.version_minor)
83+
} else {
84+
qml_file
85+
}
86+
}));
7787
self
7888
}
7989
}

crates/qt-build-utils/src/lib.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ mod platform;
3737
pub use platform::QtPlatformLinker;
3838

3939
mod qml;
40-
pub use qml::{QmlDirBuilder, QmlPluginCppBuilder, QmlUri};
40+
pub use qml::{QmlDirBuilder, QmlFile, QmlPluginCppBuilder, QmlUri};
4141

4242
mod qrc;
4343
pub use qrc::{QResource, QResourceFile, QResources};
@@ -156,7 +156,7 @@ impl QtBuild {
156156
version_major: usize,
157157
version_minor: usize,
158158
plugin_name: &str,
159-
qml_files: &[impl AsRef<Path>],
159+
qml_files: &[QmlFile],
160160
depends: impl IntoIterator<Item = impl Into<String>>,
161161
) -> QmlModuleRegistrationFiles {
162162
let qml_uri_dirs = uri.as_dirs();
@@ -176,16 +176,19 @@ impl QtBuild {
176176
// Generate qmldir file
177177
let qmldir_file_path = qml_module_dir.join("qmldir");
178178
{
179-
let qml_type_files = qml_files.iter().filter(|path| {
180-
// Qt by default only includes uppercase files in the qmldir file.
181-
// Mirror this behavior.
182-
path.as_ref()
183-
.file_name()
184-
.and_then(OsStr::to_str)
185-
.and_then(|file_name| file_name.chars().next())
186-
.map(char::is_uppercase)
187-
.unwrap_or_default()
188-
});
179+
let qml_type_files = qml_files
180+
.iter()
181+
.filter(|file| {
182+
// Qt by default only includes uppercase files in the qmldir file.
183+
// Mirror this behavior.
184+
file.path()
185+
.file_name()
186+
.and_then(OsStr::to_str)
187+
.and_then(|file_name| file_name.chars().next())
188+
.map(char::is_uppercase)
189+
.unwrap_or_default()
190+
})
191+
.cloned();
189192
let mut file = File::create(&qmldir_file_path).expect("Could not create qmldir file");
190193
QmlDirBuilder::new(uri.clone())
191194
.depends(depends)
@@ -226,8 +229,8 @@ impl QtBuild {
226229
.file(QResourceFile::new(resolved).alias(path.display().to_string()))
227230
}
228231

229-
for path in qml_files {
230-
resource = resource_add_path(resource, path.as_ref());
232+
for file in qml_files {
233+
resource = resource_add_path(resource, file.path());
231234
}
232235
resource
233236
})
@@ -250,7 +253,7 @@ impl QtBuild {
250253
let mut qml_resource_paths = Vec::new();
251254
for file in qml_files {
252255
let result = QtToolQmlCacheGen::new(self.qt_installation.as_ref())
253-
.compile(qml_cache_args.clone(), file);
256+
.compile(qml_cache_args.clone(), file.path());
254257
qmlcachegen_file_paths.push(result.qml_cache_path);
255258
qml_resource_paths.push(result.qml_resource_path);
256259
}

crates/qt-build-utils/src/qml/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ pub use qmlplugincpp::QmlPluginCppBuilder;
1111

1212
mod qmluri;
1313
pub use qmluri::QmlUri;
14+
15+
mod qmlfile;
16+
pub use qmlfile::QmlFile;

crates/qt-build-utils/src/qml/qmldir.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6-
use crate::QmlUri;
6+
use crate::qml::{QmlFile, QmlUri};
77

88
use std::ffi::OsStr;
99
use std::io;
10-
use std::path::{Path, PathBuf};
1110

1211
/// QML module definition files builder
1312
///
@@ -18,7 +17,7 @@ pub struct QmlDirBuilder {
1817
plugin: Option<(bool, String)>,
1918
type_info: Option<String>,
2019
uri: QmlUri,
21-
qml_files: Vec<PathBuf>,
20+
qml_files: Vec<QmlFile>,
2221
}
2322

2423
impl QmlDirBuilder {
@@ -66,23 +65,39 @@ impl QmlDirBuilder {
6665

6766
for qml_file in &self.qml_files {
6867
let is_qml_file = qml_file
68+
.path()
6969
.extension()
7070
.map(|ext| ext.eq_ignore_ascii_case("qml"))
7171
.unwrap_or_default();
7272

7373
if !is_qml_file {
74-
panic!("QML file does not end with .qml: {}", qml_file.display(),);
74+
panic!(
75+
"QML file does not end with .qml: {}",
76+
qml_file.path().display(),
77+
);
7578
}
7679

77-
let path = qml_file.display();
80+
let path = qml_file.path().display();
7881
let qml_component_name = qml_file
82+
.path()
7983
.file_stem()
8084
.and_then(OsStr::to_str)
8185
.expect("Could not get qml file stem");
8286

87+
let singleton = if qml_file.is_singleton() {
88+
"singleton "
89+
} else {
90+
""
91+
};
92+
8393
// Qt6 simply uses version 254.0 if no specific version is provided
84-
// Until we support versions of individal qml files, we will use 254.0
85-
writeln!(writer, "{qml_component_name} 254.0 {path}",)
94+
let version = if let Some((major, minor)) = qml_file.get_version() {
95+
format!("{}.{}", major, minor)
96+
} else {
97+
"254.0".to_string()
98+
};
99+
100+
writeln!(writer, "{singleton}{qml_component_name} {version} {path}",)
86101
.expect("Could not write qmldir file");
87102
}
88103

@@ -139,11 +154,9 @@ impl QmlDirBuilder {
139154
}
140155

141156
/// Declares a list of .qml files that are part of the module.
142-
pub fn qml_files(mut self, qml_files: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
143-
self.qml_files = qml_files
144-
.into_iter()
145-
.map(|p| p.as_ref().to_owned())
146-
.collect();
157+
pub fn qml_files(mut self, qml_files: impl IntoIterator<Item = impl Into<QmlFile>>) -> Self {
158+
self.qml_files
159+
.extend(qml_files.into_iter().map(|p| p.into()));
147160
self
148161
}
149162

@@ -176,7 +189,11 @@ mod test {
176189
.depends(["QtQuick", "com.kdab.a"])
177190
.plugin("P", true)
178191
.type_info("T")
179-
.qml_files(&["qml/Test.qml"])
192+
.qml_files(["qml/Test.qml"])
193+
.qml_files([QmlFile::from("qml/MySingleton.qml")
194+
.singleton(true)
195+
.version(1, 0)])
196+
.qml_files([QmlFile::from("../AnotherFile.qml").version(2, 123)])
180197
.write(&mut result)
181198
.unwrap();
182199
assert_eq!(
@@ -189,6 +206,8 @@ depends QtQuick
189206
depends com.kdab.a
190207
prefer :/qt/qml/com/kdab/
191208
Test 254.0 qml/Test.qml
209+
singleton MySingleton 1.0 qml/MySingleton.qml
210+
AnotherFile 2.123 ../AnotherFile.qml
192211
"
193212
);
194213
}

0 commit comments

Comments
 (0)