Skip to content

Commit 487f144

Browse files
CXX-Qt-build: Simplify QmlModule (#1342)
* Remove QRC files from QML modules This is for the most part already covered by the .qrc function in CxxQtBuilder. * CXX-Qt-Build: Move QmlModule to builder pattern * CXX-Qt-Build: Pass QmlModule into new As we now only allow a single QML module per builder, we can just pass it directly into the new function. * CXX-Qt-build: Add CxxQtBuilder::qrc_resources This is our version of qt_add_resources and replaces the previous qrc_files function in QmlModule * build: Use QmlUri everywhere * Remove duplicate qrc file from demo_threading * Warn about duplicate Rust files in builder This seems to have caused the linker errors we experienced in CI. Also fix the demo_threading build script, which had this issue. * cargo_without_cmake: Remove useless qrc_files call
1 parent 6bc6686 commit 487f144

File tree

24 files changed

+378
-335
lines changed

24 files changed

+378
-335
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
//! This modules contains information about the paths where artifacts are placed by cxx-qt-build.
77
8+
use qt_build_utils::QmlUri;
9+
810
use crate::{crate_name, module_name_from_uri};
911
use std::io::Result;
1012
use std::{
@@ -57,7 +59,7 @@ pub(crate) fn crate_target() -> PathBuf {
5759
}
5860

5961
/// The target directory, namespaced by QML module
60-
pub(crate) fn module_target(module_uri: &str) -> PathBuf {
62+
pub(crate) fn module_target(module_uri: &QmlUri) -> PathBuf {
6163
module_export(module_uri).unwrap_or_else(|| {
6264
out()
6365
// Use a short name due to the Windows file path limit!
@@ -73,7 +75,7 @@ pub(crate) fn module_target(module_uri: &str) -> PathBuf {
7375
///
7476
/// TODO: This may conflict if two dependencies are building QML modules with the same name!
7577
/// We should probably include a lockfile here to avoid this.
76-
pub(crate) fn module_export(module_uri: &str) -> Option<PathBuf> {
78+
pub(crate) fn module_export(module_uri: &QmlUri) -> Option<PathBuf> {
7779
// In contrast to crate_export, we don't need to check for the specific crate here.
7880
// QML modules should always be exported.
7981
env::var("CXX_QT_EXPORT_DIR")

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

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

3333
mod qml_modules;
34-
use qml_modules::OwningQmlModule;
3534
pub use qml_modules::QmlModule;
3635

3736
pub use qt_build_utils::MocArguments;
3837
use qt_build_utils::MocProducts;
38+
use qt_build_utils::QResources;
39+
use qt_build_utils::QmlUri;
3940
use quote::ToTokens;
4041
use semver::Version;
4142
use std::{
@@ -296,9 +297,11 @@ fn generate_cxxqt_cpp_files(
296297
generated_file_paths
297298
}
298299

299-
pub(crate) fn module_name_from_uri(module_uri: &str) -> String {
300+
pub(crate) fn module_name_from_uri(module_uri: &QmlUri) -> String {
300301
// Note: We need to make sure this matches the conversion done in CMake!
301-
module_uri.replace('.', "_")
302+
// TODO: Replace with as_dirs so qmlls/qmllint can resolve the path
303+
// TODO: This needs an update to cxx-qt-cmake
304+
module_uri.as_underscores()
302305
}
303306

304307
pub(crate) fn crate_name() -> String {
@@ -321,7 +324,7 @@ fn crate_init_key() -> String {
321324
format!("crate_{}", crate_name().replace('-', "_"))
322325
}
323326

324-
fn qml_module_init_key(module_uri: &str) -> String {
327+
fn qml_module_init_key(module_uri: &QmlUri) -> String {
325328
format!("qml_module_{}", module_name_from_uri(module_uri))
326329
}
327330

@@ -362,9 +365,10 @@ pub struct CxxQtBuilder {
362365
rust_sources: Vec<PathBuf>,
363366
qobject_headers: Vec<QObjectHeaderOpts>,
364367
qrc_files: Vec<PathBuf>,
368+
qrc_resources: Vec<QResources>,
365369
init_files: Vec<qt_build_utils::Initializer>,
366370
qt_modules: HashSet<String>,
367-
qml_module: Option<OwningQmlModule>,
371+
qml_module: Option<QmlModule>,
368372
cc_builder: cc::Build,
369373
include_prefix: String,
370374
crate_include_root: Option<String>,
@@ -403,6 +407,7 @@ impl CxxQtBuilder {
403407
rust_sources: vec![],
404408
qobject_headers: vec![],
405409
qrc_files: vec![],
410+
qrc_resources: vec![],
406411
init_files: vec![],
407412
qt_modules,
408413
qml_module: None,
@@ -413,20 +418,52 @@ impl CxxQtBuilder {
413418
}
414419
}
415420

421+
/// Create a new CxxQtBuilder for building the specified [QmlModule].
422+
///
423+
/// The QmlModule struct's `qml_files` are registered with the [Qt Resource System](https://doc.qt.io/qt-6/resources.html) in
424+
/// the [default QML import path](https://doc.qt.io/qt-6/qtqml-syntax-imports.html#qml-import-path) `qrc:/qt/qml/uri/of/module/`.
425+
/// Additional resources such as images can be added to the Qt resources for the QML module by using the appropriate functions on CxxQtBuilder.
426+
///
427+
/// When using Qt 6, this will [run qmlcachegen](https://doc.qt.io/qt-6/qtqml-qtquick-compiler-tech.html)
428+
/// to compile the specified `.qml` files ahead-of-time.
429+
///
430+
/// ```no_run
431+
/// use cxx_qt_build::{CxxQtBuilder, QmlModule};
432+
///
433+
/// CxxQtBuilder::new_qml_module(QmlModule::new("com.kdab.cxx_qt.demo").qml_files(["qml/main.qml"]))
434+
/// .files(["src/cxxqt_object.rs"])
435+
/// .build();
436+
/// ```
437+
pub fn new_qml_module(module: QmlModule) -> Self {
438+
let mut builder = Self::new();
439+
builder.qml_module = Some(module);
440+
builder
441+
}
442+
416443
/// Specify rust file paths to parse through the cxx-qt marco
417444
/// Relative paths are treated as relative to the path of your crate's Cargo.toml file
418-
pub fn file(mut self, rust_source: impl AsRef<Path>) -> Self {
419-
let rust_source = rust_source.as_ref().to_path_buf();
420-
println!("cargo::rerun-if-changed={}", rust_source.display());
421-
self.rust_sources.push(rust_source);
422-
self
445+
pub fn file(self, rust_source: impl AsRef<Path>) -> Self {
446+
self.files(std::iter::once(rust_source))
423447
}
424448

425449
/// Specify multiple rust file paths to parse through the cxx-qt marco.
426450
///
427451
/// 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| {
452+
pub fn files(mut self, rust_sources: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
453+
let rust_sources = rust_sources.into_iter().collect::<Vec<_>>();
454+
for source in rust_sources.iter() {
455+
let source = source.as_ref().to_owned();
456+
if self.rust_sources.contains(&source) {
457+
// Duplicate rust files are likely to cause confusing linker errors later on
458+
// Warn the user about it so that debugging may be easier.
459+
println!(
460+
"cargo::warning=CxxQtBuilder::file(s): Duplicate rust file: {}",
461+
source.display()
462+
);
463+
}
464+
}
465+
466+
let rust_sources = rust_sources.into_iter().map(|p| {
430467
let p = p.as_ref().to_path_buf();
431468
println!("cargo::rerun-if-changed={}", p.display());
432469
p
@@ -496,16 +533,58 @@ impl CxxQtBuilder {
496533
/// .build();
497534
/// ```
498535
///
499-
/// ⚠️ In CMake projects, the .qrc file is typically added to the `SOURCES` of the target.
500-
/// Prefer this to adding the qrc file through cxx-qt-build.
501-
/// When using CMake, the qrc file will **not** be built by cxx-qt-build!
536+
/// Note: In CMake projects, the .qrc file is typically added to the `SOURCES` of the target.
537+
/// This can be done as an alternative to using this function.
502538
pub fn qrc(mut self, qrc_file: impl AsRef<Path>) -> Self {
503539
let qrc_file = qrc_file.as_ref();
504540
self.qrc_files.push(qrc_file.to_path_buf());
505541
println!("cargo::rerun-if-changed={}", qrc_file.display());
506542
self
507543
}
508544

545+
/// Include resources (files) listed in a [QResources] struct into the binary
546+
/// with [Qt's resource system](https://doc.qt.io/qt-6/resources.html).
547+
///
548+
/// See [QResources] and [qt_build_utils::QResource] for details on how to specify resources.
549+
///
550+
/// If a [QmlModule] was specified when constructing the [CxxQtBuilder], any resources that do
551+
/// not have a prefix specified will automatically be given a prefix based on the QML module's
552+
/// [default QML import path](https://doc.qt.io/qt-6/qtqml-syntax-imports.html#qml-import-path)
553+
/// `qrc:/qt/qml/uri/of/module/`
554+
///
555+
/// Note: A list of strings or paths can be converted into a [QResources], so it is possibly to
556+
/// just specify a list of strings like this:
557+
///
558+
/// ```no_run
559+
/// # use cxx_qt_build::CxxQtBuilder;
560+
/// CxxQtBuilder::new()
561+
/// .qrc_resources(["images/image.png", "images/logo.png"])
562+
/// .build();
563+
/// ```
564+
///
565+
/// Note: In CMake projects, the resources are typically added via [qt_add_resources](https://doc.qt.io/qt-6/qt-add-resources.html)
566+
/// This can be done as an alternative to using this function.
567+
pub fn qrc_resources(mut self, qrc_resources: impl Into<QResources>) -> Self {
568+
let mut qrc_resources = qrc_resources.into();
569+
if let Some(qml_module) = &self.qml_module {
570+
let prefix = format!("/qt/qml/{}", qml_module.uri.as_dirs());
571+
for resource in qrc_resources.get_resources_mut() {
572+
if resource.get_prefix().is_none() {
573+
*resource = resource.clone().prefix(prefix.clone());
574+
}
575+
}
576+
}
577+
for file in qrc_resources
578+
.get_resources()
579+
.flat_map(|resource| resource.get_files())
580+
{
581+
println!("cargo::rerun-if-changed={}", file.get_path().display());
582+
}
583+
584+
self.qrc_resources.push(qrc_resources);
585+
self
586+
}
587+
509588
/// Link additional [Qt modules](https://doc.qt.io/qt-6/qtmodules.html).
510589
/// Specify their names without the `Qt` prefix, for example `"Widgets"`.
511590
/// The `Core` module and any modules from dependencies are linked automatically; there is no need to specify them.
@@ -532,47 +611,6 @@ impl CxxQtBuilder {
532611
self
533612
}
534613

535-
/// Register a QML module at build time. The `rust_files` of the [QmlModule] struct
536-
/// should contain `#[cxx_qt::bridge]` modules with QObject types annotated with `#[qml_element]`.
537-
///
538-
/// The QmlModule struct's `qml_files` are registered with the [Qt Resource System](https://doc.qt.io/qt-6/resources.html) in
539-
/// the [default QML import path](https://doc.qt.io/qt-6/qtqml-syntax-imports.html#qml-import-path) `qrc:/qt/qml/uri/of/module/`.
540-
/// Additional resources such as images can be added to the Qt resources for the QML module by specifying
541-
/// the `qrc_files` field.
542-
///
543-
/// When using Qt 6, this will [run qmlcachegen](https://doc.qt.io/qt-6/qtqml-qtquick-compiler-tech.html)
544-
/// to compile the specified `.qml` files ahead-of-time.
545-
///
546-
/// ```no_run
547-
/// use cxx_qt_build::{CxxQtBuilder, QmlModule};
548-
///
549-
/// CxxQtBuilder::new()
550-
/// .qml_module(QmlModule::<&str, &str> {
551-
/// uri: "com.kdab.cxx_qt.demo",
552-
/// qml_files: &["qml/main.qml"],
553-
/// ..Default::default()
554-
/// })
555-
/// .files(["src/cxxqt_object.rs"])
556-
/// .build();
557-
/// ```
558-
pub fn qml_module<A: AsRef<Path>, B: AsRef<Path>>(
559-
mut self,
560-
qml_module: QmlModule<A, B>,
561-
) -> CxxQtBuilder {
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-
);
569-
}
570-
571-
let qml_module = OwningQmlModule::from(qml_module);
572-
self.qml_module = Some(qml_module);
573-
self
574-
}
575-
576614
/// Specify a C++ header containing a Q_OBJECT macro to run [moc](https://doc.qt.io/qt-6/moc.html) on.
577615
/// This allows building QObject C++ subclasses besides the ones autogenerated by cxx-qt.
578616
pub fn qobject_header(mut self, opts: impl Into<QObjectHeaderOpts>) -> Self {
@@ -740,7 +778,7 @@ impl CxxQtBuilder {
740778
}
741779

742780
if let Some(uri) = moc_arguments.get_uri() {
743-
if uri != qml_module.uri {
781+
if *uri != qml_module.uri {
744782
panic!(
745783
"URI for QObject header {path} ({uri}) conflicts with QML Module URI ({qml_module_uri})",
746784
path = path.display(),
@@ -868,7 +906,6 @@ impl CxxQtBuilder {
868906
// But make sure it still works
869907
&module_name_from_uri(&qml_module.uri),
870908
&qml_module.qml_files,
871-
&qml_module.qrc_files,
872909
);
873910
if let Some(qmltyperegistrar) = qml_module_registration_files.qmltyperegistrar {
874911
cc_builder.file(qmltyperegistrar);
@@ -894,11 +931,7 @@ impl CxxQtBuilder {
894931
cc_builder.define("QT_STATICPLUGIN", None);
895932

896933
// If any of the files inside the qml module change, then trigger a rerun
897-
for path in qml_module
898-
.qml_files
899-
.iter()
900-
.chain(qml_module.qrc_files.iter())
901-
{
934+
for path in qml_module.qml_files {
902935
println!("cargo::rerun-if-changed={}", path.display());
903936
}
904937

@@ -1036,6 +1069,28 @@ extern "C" bool {init_fun}() {{
10361069
}
10371070
}
10381071

1072+
fn generate_qrc_files_from_resources(&mut self) {
1073+
let qrc_dir = dir::crate_target().join("qrc");
1074+
std::fs::create_dir_all(&qrc_dir)
1075+
.expect("Failed to create directory for QRC generation: {qrc_dir}");
1076+
1077+
let new_qrc_files = self
1078+
.qrc_resources
1079+
.drain(..)
1080+
.enumerate()
1081+
.map(|(index, resources)| {
1082+
let path = qrc_dir.join(format!("resources_{index}.qrc"));
1083+
let mut file =
1084+
File::create(&path).expect("Failed to create .qrc file for Resources");
1085+
resources
1086+
.write(&mut file)
1087+
.expect("Failed to write .qrc file for Resources");
1088+
path
1089+
});
1090+
1091+
self.qrc_files.extend(new_qrc_files);
1092+
}
1093+
10391094
fn generate_cpp_from_qrc_files(
10401095
&mut self,
10411096
qtbuild: &mut qt_build_utils::QtBuild,
@@ -1138,12 +1193,13 @@ extern "C" bool {init_fun}() {{
11381193
// the metatypes_json generated by moc needs to be passed to qmltyperegistrar
11391194
let module_initializers = self.build_qml_modules(&mut qtbuild, &moc_products);
11401195

1141-
let qrc_files = self.generate_cpp_from_qrc_files(&mut qtbuild);
1196+
self.generate_qrc_files_from_resources();
1197+
let qrc_initializers = self.generate_cpp_from_qrc_files(&mut qtbuild);
11421198

11431199
let dependency_initializers = dependencies::initializers(&dependencies);
11441200
let private_initializers = dependency_initializers
11451201
.into_iter()
1146-
.chain(qrc_files)
1202+
.chain(qrc_initializers)
11471203
.chain(module_initializers)
11481204
.chain(self.init_files.iter().cloned())
11491205
.collect::<Vec<_>>();

0 commit comments

Comments
 (0)