@@ -31,11 +31,12 @@ pub use opts::CxxQtBuildersOpts;
3131pub use opts:: QObjectHeaderOpts ;
3232
3333mod qml_modules;
34- use qml_modules:: OwningQmlModule ;
3534pub use qml_modules:: QmlModule ;
3635
3736pub use qt_build_utils:: MocArguments ;
3837use qt_build_utils:: MocProducts ;
38+ use qt_build_utils:: QResources ;
39+ use qt_build_utils:: QmlUri ;
3940use quote:: ToTokens ;
4041use semver:: Version ;
4142use 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
304307pub ( 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