Skip to content

Commit f9e0926

Browse files
CXX-Qt-Build: Remove Rust files from QML module struct (#1341)
* Allow only a single QML module per CxxQtBuilder * Remove Rust files from QML module This simplifies building QML modules and reduces the API footprint. * Update Changelog
1 parent ef8a3c9 commit f9e0926

File tree

13 files changed

+114
-129
lines changed

13 files changed

+114
-129
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- CXX-Qt-build: Improved compile time and propagation of initializers between crates
2929
- CXX-Qt-build: Multi-crate projects are now possible with Cargo and CMake (see `examples/qml_multi_crates`)
3030
- CXX-Qt-build: Allow forcing initialization of crates/QML modules (`cxx_qt::init_crate!`/`cxx_qt::init_qml_module!`)
31+
- CXX-Qt-build: `CxxQtBuilder::files` to add multiple files
3132
- Add pure virtual function specified through the `#[cxx_pure]` attribute
3233
- Add wrappers for up and down casting, for all types which inherit from QObject, available for &T, &mut T and Pin<&mut T>
3334
- `#[base = T]` is now supported in `extern "C++Qt"` blocks
@@ -43,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4344

4445
- CXX-Qt-build: Interface no longer includes compiler definitions (<https://github.com/KDAB/cxx-qt/issues/1165>)
4546
- CXX-Qt-build: Interface no longer includes initializers
47+
- CXX-Qt-build: QML modules no longer include Rust files (use `CxxQtBuilder::files` instead)
48+
- CXX-Qt-build: Only allow one QML module per `CxxQtBuilder`
4649

4750
## [0.7.2](https://github.com/KDAB/cxx-qt/compare/v0.7.1...v0.7.2) - 2025-04-28
4851

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

Lines changed: 72 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use qml_modules::OwningQmlModule;
3535
pub use qml_modules::QmlModule;
3636

3737
pub use qt_build_utils::MocArguments;
38+
use qt_build_utils::MocProducts;
3839
use quote::ToTokens;
3940
use semver::Version;
4041
use std::{
@@ -324,15 +325,6 @@ fn qml_module_init_key(module_uri: &str) -> String {
324325
format!("qml_module_{}", module_name_from_uri(module_uri))
325326
}
326327

327-
fn panic_duplicate_file_and_qml_module(
328-
path: impl AsRef<Path>,
329-
uri: &str,
330-
version_major: usize,
331-
version_minor: usize,
332-
) {
333-
panic!("CXX-Qt bridge Rust file {} specified in QML module {uri} (version {version_major}.{version_minor}), but also specified via CxxQtBuilder::file. Bridge files must be specified via CxxQtBuilder::file or CxxQtBuilder::qml_module, but not both.", path.as_ref().display());
334-
}
335-
336328
/// Run cxx-qt's C++ code generator on Rust modules marked with the `cxx_qt::bridge` macro, compile
337329
/// the code, and link to Qt. This is the complement of the `cxx_qt::bridge` macro, which the Rust
338330
/// compiler uses to generate the corresponding Rust code. No dependencies besides Qt, a C++17 compiler,
@@ -372,7 +364,7 @@ pub struct CxxQtBuilder {
372364
qrc_files: Vec<PathBuf>,
373365
init_files: Vec<qt_build_utils::Initializer>,
374366
qt_modules: HashSet<String>,
375-
qml_modules: Vec<OwningQmlModule>,
367+
qml_module: Option<OwningQmlModule>,
376368
cc_builder: cc::Build,
377369
include_prefix: String,
378370
crate_include_root: Option<String>,
@@ -413,7 +405,7 @@ impl CxxQtBuilder {
413405
qrc_files: vec![],
414406
init_files: vec![],
415407
qt_modules,
416-
qml_modules: vec![],
408+
qml_module: None,
417409
cc_builder: cc::Build::new(),
418410
include_prefix: crate_name(),
419411
crate_include_root: Some(String::new()),
@@ -425,21 +417,24 @@ impl CxxQtBuilder {
425417
/// Relative paths are treated as relative to the path of your crate's Cargo.toml file
426418
pub fn file(mut self, rust_source: impl AsRef<Path>) -> Self {
427419
let rust_source = rust_source.as_ref().to_path_buf();
428-
for qml_module in &self.qml_modules {
429-
if qml_module.rust_files.contains(&rust_source) {
430-
panic_duplicate_file_and_qml_module(
431-
&rust_source,
432-
&qml_module.uri,
433-
qml_module.version_major,
434-
qml_module.version_minor,
435-
);
436-
}
437-
}
438420
println!("cargo::rerun-if-changed={}", rust_source.display());
439421
self.rust_sources.push(rust_source);
440422
self
441423
}
442424

425+
/// Specify multiple rust file paths to parse through the cxx-qt marco.
426+
///
427+
/// See also: [Self::file]
428+
pub fn files(mut self, rust_source: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
429+
let rust_sources = rust_source.into_iter().map(|p| {
430+
let p = p.as_ref().to_path_buf();
431+
println!("cargo::rerun-if-changed={}", p.display());
432+
p
433+
});
434+
self.rust_sources.extend(rust_sources);
435+
self
436+
}
437+
443438
#[doc(hidden)]
444439
pub fn initializer(mut self, initializer: qt_build_utils::Initializer) -> Self {
445440
if let Some(ref init_file) = initializer.file {
@@ -552,30 +547,29 @@ impl CxxQtBuilder {
552547
/// use cxx_qt_build::{CxxQtBuilder, QmlModule};
553548
///
554549
/// CxxQtBuilder::new()
555-
/// .qml_module(QmlModule {
550+
/// .qml_module(QmlModule::<&str, &str> {
556551
/// uri: "com.kdab.cxx_qt.demo",
557-
/// rust_files: &["src/cxxqt_object.rs"],
558552
/// qml_files: &["qml/main.qml"],
559553
/// ..Default::default()
560554
/// })
555+
/// .files(["src/cxxqt_object.rs"])
561556
/// .build();
562557
/// ```
563558
pub fn qml_module<A: AsRef<Path>, B: AsRef<Path>>(
564559
mut self,
565560
qml_module: QmlModule<A, B>,
566561
) -> CxxQtBuilder {
567-
let qml_module = OwningQmlModule::from(qml_module);
568-
for path in &qml_module.rust_files {
569-
if self.rust_sources.contains(path) {
570-
panic_duplicate_file_and_qml_module(
571-
path,
572-
&qml_module.uri,
573-
qml_module.version_major,
574-
qml_module.version_minor,
575-
);
576-
}
562+
if let Some(module) = &self.qml_module {
563+
panic!(
564+
"Duplicate QML module registration!\n\
565+
The QML module with URI '{}' (version {}.{}) was already registered.\n\
566+
Only one QML module can be registered per crate.",
567+
module.uri, module.version_major, module.version_minor
568+
);
577569
}
578-
self.qml_modules.push(qml_module);
570+
571+
let qml_module = OwningQmlModule::from(qml_module);
572+
self.qml_module = Some(qml_module);
579573
self
580574
}
581575

@@ -728,19 +722,42 @@ impl CxxQtBuilder {
728722
}
729723
}
730724

731-
fn moc_qobject_headers(&mut self, qtbuild: &mut qt_build_utils::QtBuild) {
732-
for QObjectHeaderOpts {
725+
/// Returns the list of Moc products. Especially the qml_metatypes.json files are needed for
726+
/// the QML module generation later
727+
fn moc_qobject_headers(&mut self, qtbuild: &mut qt_build_utils::QtBuild) -> Vec<MocProducts> {
728+
self.qobject_headers.iter().map(|QObjectHeaderOpts {
733729
path,
734730
moc_arguments,
735-
} in &self.qobject_headers
731+
}|
736732
{
737-
let moc_products = qtbuild.moc().compile(path, moc_arguments.clone());
733+
734+
let mut moc_arguments = moc_arguments.clone();
735+
if let Some(qml_module) = &self.qml_module {
736+
// Ensure that the generated QObject header is in the include path
737+
// so that qmltyperegistar can include them later
738+
if let Some(dir) = path.parent() {
739+
self.cc_builder.include(dir);
740+
}
741+
742+
if let Some(uri) = moc_arguments.get_uri() {
743+
if uri != qml_module.uri {
744+
panic!(
745+
"URI for QObject header {path} ({uri}) conflicts with QML Module URI ({qml_module_uri})",
746+
path = path.display(),
747+
qml_module_uri = qml_module.uri);
748+
}
749+
}
750+
moc_arguments = moc_arguments.uri(qml_module.uri.clone());
751+
}
752+
let moc_products = qtbuild.moc().compile(path, moc_arguments);
738753
// Include the moc folder
739754
if let Some(dir) = moc_products.cpp.parent() {
740755
self.cc_builder.include(dir);
741756
}
742-
self.cc_builder.file(moc_products.cpp);
743-
}
757+
self.cc_builder.file(moc_products.cpp.clone());
758+
moc_products
759+
})
760+
.collect()
744761
}
745762

746763
fn generate_cpp_files_from_cxxqt_bridges(
@@ -791,19 +808,15 @@ impl CxxQtBuilder {
791808
fn build_qml_modules(
792809
&mut self,
793810
qtbuild: &mut qt_build_utils::QtBuild,
794-
generated_header_dir: impl AsRef<Path>,
795-
header_prefix: &str,
811+
moc_products: &[MocProducts],
796812
) -> Vec<qt_build_utils::Initializer> {
797813
let mut initializer_functions = Vec::new();
798814
// Extract qml_modules out of self so we don't have to hold onto `self` for the duration of
799815
// the loop.
800-
let qml_modules: Vec<_> = self.qml_modules.drain(..).collect();
801-
for qml_module in qml_modules {
816+
if let Some(qml_module) = self.qml_module.take() {
802817
dir::clean(dir::module_target(&qml_module.uri))
803818
.expect("Failed to clean qml module export directory!");
804819

805-
let mut qml_metatypes_json = Vec::new();
806-
807820
// Check that all rust files are within the same directory
808821
//
809822
// Note we need to do this as moc generates an inputFile which only
@@ -813,8 +826,8 @@ impl CxxQtBuilder {
813826
// This can also be observed when using qt_add_qml_module, if a class
814827
// has a QML_ELEMENT the file must be in the same directory as the
815828
// CMakeLists and cannot be a relative path to a sub directory.
816-
let dirs = qml_module
817-
.rust_files
829+
let dirs = self
830+
.rust_sources
818831
.iter()
819832
.map(|file| {
820833
if let Some(parent) = file.parent() {
@@ -840,34 +853,10 @@ impl CxxQtBuilder {
840853
let cc_builder = &mut self.cc_builder;
841854
qtbuild.cargo_link_libraries(cc_builder);
842855

843-
let mut moc_include_paths = HashSet::new();
844-
for files in generate_cxxqt_cpp_files(
845-
&qml_module.rust_files,
846-
&generated_header_dir,
847-
header_prefix,
848-
) {
849-
cc_builder.file(files.plain_cpp);
850-
if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header)
851-
{
852-
// Ensure that the generated QObject header is in the include path
853-
// so that qmltyperegistar can include them later
854-
if let Some(dir) = qobject_header.parent() {
855-
moc_include_paths.insert(dir.to_path_buf());
856-
}
857-
858-
cc_builder.file(&qobject);
859-
let moc_products = qtbuild.moc().compile(
860-
qobject_header,
861-
MocArguments::default().uri(qml_module.uri.clone()),
862-
);
863-
// Include the moc folder
864-
if let Some(dir) = moc_products.cpp.parent() {
865-
moc_include_paths.insert(dir.to_path_buf());
866-
}
867-
cc_builder.file(moc_products.cpp);
868-
qml_metatypes_json.push(moc_products.metatypes_json);
869-
}
870-
}
856+
let qml_metatypes_json: Vec<PathBuf> = moc_products
857+
.iter()
858+
.map(|products| products.metatypes_json.clone())
859+
.collect();
871860

872861
let qml_module_registration_files = qtbuild.register_qml_module(
873862
&qml_metatypes_json,
@@ -895,11 +884,6 @@ impl CxxQtBuilder {
895884
// Add any include paths the qml module registration needs
896885
// this is most likely the moc folder for the plugin
897886
if let Some(include_path) = qml_module_registration_files.include_path {
898-
moc_include_paths.insert(include_path);
899-
}
900-
901-
// Ensure that all include paths from moc folders that are required
902-
for include_path in &moc_include_paths {
903887
cc_builder.include(include_path);
904888
}
905889

@@ -910,12 +894,11 @@ impl CxxQtBuilder {
910894
cc_builder.define("QT_STATICPLUGIN", None);
911895

912896
// If any of the files inside the qml module change, then trigger a rerun
913-
for path in qml_module.qml_files.iter().chain(
914-
qml_module
915-
.rust_files
916-
.iter()
917-
.chain(qml_module.qrc_files.iter()),
918-
) {
897+
for path in qml_module
898+
.qml_files
899+
.iter()
900+
.chain(qml_module.qrc_files.iter())
901+
{
919902
println!("cargo::rerun-if-changed={}", path.display());
920903
}
921904

@@ -1149,12 +1132,11 @@ extern "C" bool {init_fun}() {{
11491132
// Generate files
11501133
self.generate_cpp_files_from_cxxqt_bridges(&header_root, &self.include_prefix.clone());
11511134

1152-
self.moc_qobject_headers(&mut qtbuild);
1135+
let moc_products = self.moc_qobject_headers(&mut qtbuild);
11531136

11541137
// Bridges for QML modules are handled separately because
11551138
// the metatypes_json generated by moc needs to be passed to qmltyperegistrar
1156-
let module_initializers =
1157-
self.build_qml_modules(&mut qtbuild, &header_root, &self.include_prefix.clone());
1139+
let module_initializers = self.build_qml_modules(&mut qtbuild, &moc_products);
11581140

11591141
let qrc_files = self.generate_cpp_from_qrc_files(&mut qtbuild);
11601142

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ where
2020
pub version_major: usize,
2121
/// The minor version of the QML module
2222
pub version_minor: usize,
23-
/// The `.rs` files containing a `#[cxx_qt::bridge]` module with at least one QObject type annotated with `#[qml_element]`
24-
pub rust_files: &'a [A],
2523
/// `.qml` files included in the module
2624
pub qml_files: &'a [B],
2725
/// Other QRC resources (such as images) included in the module
@@ -45,7 +43,6 @@ where
4543
uri: "com.example.cxx_qt_module",
4644
version_major: 1,
4745
version_minor: 0,
48-
rust_files: &[],
4946
qml_files: &[],
5047
qrc_files: &[],
5148
}
@@ -59,7 +56,6 @@ pub(crate) struct OwningQmlModule {
5956
pub uri: String,
6057
pub version_major: usize,
6158
pub version_minor: usize,
62-
pub rust_files: Vec<PathBuf>,
6359
pub qml_files: Vec<PathBuf>,
6460
pub qrc_files: Vec<PathBuf>,
6561
}
@@ -74,7 +70,6 @@ impl<A: AsRef<Path>, B: AsRef<Path>> From<QmlModule<'_, A, B>> for OwningQmlModu
7470
uri: other.uri.to_owned(),
7571
version_major: other.version_major,
7672
version_minor: other.version_minor,
77-
rust_files: collect_pathbuf_vec(other.rust_files),
7873
qml_files: collect_pathbuf_vec(other.qml_files),
7974
qrc_files: collect_pathbuf_vec(other.qrc_files),
8075
}

crates/qt-build-utils/src/tool/moc.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ impl MocArguments {
3333
self
3434
}
3535

36+
/// Returns the assigned URI, if any.
37+
pub fn get_uri(&self) -> Option<&str> {
38+
self.uri.as_deref()
39+
}
40+
3641
/// Additional include path to pass to moc
3742
pub fn include_path(mut self, include_path: PathBuf) -> Self {
3843
self.include_paths.push(include_path);

examples/cargo_without_cmake/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ fn main() {
1414
// - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib.
1515
// - Qt Qml requires linking Qt Network on macOS
1616
.qt_module("Network")
17-
.qml_module(QmlModule {
17+
.qml_module(QmlModule::<&str, &str> {
1818
uri: "com.kdab.cxx_qt.demo",
19-
rust_files: &["src/cxxqt_object.rs"],
2019
qml_files: &["qml/main.qml"],
2120
..Default::default()
2221
})
22+
.files(["src/cxxqt_object.rs"])
2323
.build();
2424
}
2525
// ANCHOR_END: book_cargo_executable_build_rs

examples/demo_threading/rust/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ fn main() {
88
CxxQtBuilder::new()
99
.qml_module(QmlModule {
1010
uri: "com.kdab.energy",
11-
rust_files: &["src/lib.rs"],
1211
qml_files: &[
1312
"../qml/Button.qml",
1413
"../qml/MainWindow.qml",
@@ -46,5 +45,6 @@ fn main() {
4645
],
4746
..Default::default()
4847
})
48+
.files(["src/lib.rs"])
4949
.build();
5050
}

examples/qml_basics/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
use cxx_qt_build::{CxxQtBuilder, QmlModule};
77
fn main() {
88
CxxQtBuilder::new()
9-
.qml_module(QmlModule {
9+
.qml_module(QmlModule::<&str, &str> {
1010
uri: "com.kdab.tutorial",
1111
qml_files: &["qml/main.qml"],
12-
rust_files: &["src/main.rs"],
1312
..Default::default()
1413
})
14+
.files(["src/main.rs"])
1515
.qt_module("Network")
1616
.build();
1717
}

0 commit comments

Comments
 (0)