1#![deny(missing_docs)]
9
10mod cfg_evaluator;
15
16mod diagnostics;
17use diagnostics::{Diagnostic, GeneratedError};
18
19pub mod dir;
20use dir::INCLUDE_VERB;
21
22mod dependencies;
23pub use dependencies::Interface;
24use dependencies::{Dependency, Manifest};
25
26mod opts;
27pub use opts::CxxQtBuildersOpts;
28pub use opts::QObjectHeaderOpts;
29
30mod qml_modules;
31use qml_modules::OwningQmlModule;
32pub use qml_modules::QmlModule;
33
34pub use qt_build_utils::MocArguments;
35use qt_build_utils::SemVer;
36use quote::ToTokens;
37use std::{
38 collections::HashSet,
39 env,
40 fs::File,
41 io::Write,
42 path::{Path, PathBuf},
43};
44
45use cxx_qt_gen::{
46 parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks,
47 GeneratedRustBlocks, Parser,
48};
49
50struct GeneratedCppFilePaths {
59 plain_cpp: PathBuf,
60 qobject: Option<PathBuf>,
61 qobject_header: Option<PathBuf>,
62}
63
64struct GeneratedCpp {
65 cxx_qt: Option<CppFragment>,
66 cxx: cxx_gen::GeneratedCode,
67 file_ident: String,
68}
69
70impl GeneratedCpp {
71 pub fn new(
73 rust_file_path: impl AsRef<Path>,
74 relative_path: impl AsRef<Path>,
75 include_prefix: &str,
76 ) -> Result<Self, Diagnostic> {
77 let to_diagnostic = |err| Diagnostic::new(rust_file_path.as_ref().to_owned(), err);
78
79 let rust_file_path = rust_file_path.as_ref();
80
81 let file = parse_qt_file(rust_file_path)
82 .map_err(GeneratedError::from)
83 .map_err(to_diagnostic)?;
84
85 let mut cxx_qt = None;
86 let mut tokens = proc_macro2::TokenStream::new();
87
88 for attr in &file.attrs {
90 tokens.extend(attr.into_token_stream());
91 }
92
93 let file_ident = relative_path
97 .as_ref()
98 .with_extension("")
100 .to_string_lossy()
101 .to_string();
102
103 let include_ident = format!("{include_prefix}/{file_ident}");
105
106 let mut found_bridge = false;
108 for item in &file.items {
109 match item {
110 CxxQtItem::Cxx(m) => {
111 if found_bridge {
113 panic!(
114 "Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
115 The file {} has more than one of these.",
116 rust_file_path.display());
117 }
118 found_bridge = true;
119
120 tokens.extend(m.into_token_stream());
121 }
122 CxxQtItem::CxxQt(m) => {
123 if found_bridge {
125 panic!(
126 "Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
127 The file {} has more than one of these.",
128 rust_file_path.display());
129 }
130 found_bridge = true;
131
132 let parser = Parser::from(m.clone())
133 .map_err(GeneratedError::from)
134 .map_err(to_diagnostic)?;
135 let generated_cpp = GeneratedCppBlocks::from(&parser)
136 .map_err(GeneratedError::from)
137 .map_err(to_diagnostic)?;
138 let generated_rust = GeneratedRustBlocks::from(&parser)
139 .map_err(GeneratedError::from)
140 .map_err(to_diagnostic)?;
141
142 cxx_qt = Some(write_cpp(&generated_cpp, &include_ident));
145 let rust_tokens = write_rust(&generated_rust, Some(&include_ident));
146
147 tokens.extend(rust_tokens);
150 }
151 CxxQtItem::Item(item) => {
152 tokens.extend(item.into_token_stream());
153 }
154 }
155 }
156
157 let mut opt = cxx_gen::Opt::default();
158 opt.cfg_evaluator = Box::new(cfg_evaluator::CargoEnvCfgEvaluator);
159 let cxx = cxx_gen::generate_header_and_cc(tokens, &opt)
160 .map_err(GeneratedError::from)
161 .map_err(to_diagnostic)?;
162
163 Ok(GeneratedCpp {
164 cxx_qt,
165 cxx,
166 file_ident,
167 })
168 }
169
170 pub fn write_to_directories(
172 self,
173 cpp_directory: impl AsRef<Path>,
174 header_directory: impl AsRef<Path>,
175 ) -> GeneratedCppFilePaths {
176 let cpp_directory = cpp_directory.as_ref();
177 let header_directory = header_directory.as_ref();
178
179 let mut cpp_file_paths = GeneratedCppFilePaths {
180 plain_cpp: PathBuf::new(),
181 qobject: None,
182 qobject_header: None,
183 };
184 if let Some(cxx_qt_generated) = &self.cxx_qt {
185 let header_path = PathBuf::from(format!(
186 "{}/{}.cxxqt.h",
187 header_directory.display(),
188 self.file_ident
189 ));
190 if let Some(directory) = header_path.parent() {
191 std::fs::create_dir_all(directory)
192 .expect("Could not create directory to write cxx-qt generated files");
193 }
194 let mut header =
195 File::create(&header_path).expect("Could not create cxx-qt header file");
196 let header_generated = match cxx_qt_generated {
197 CppFragment::Pair { header, source: _ } => header,
198 CppFragment::Header(header) => header,
199 CppFragment::Source(_) => panic!("Unexpected call for source fragment."),
200 };
201 header
202 .write_all(header_generated.as_bytes())
203 .expect("Could not write cxx-qt header file");
204 cpp_file_paths.qobject_header = Some(header_path);
205
206 let cpp_path = PathBuf::from(format!(
207 "{}/{}.cxxqt.cpp",
208 cpp_directory.display(),
209 self.file_ident
210 ));
211 if let Some(directory) = cpp_path.parent() {
212 std::fs::create_dir_all(directory)
213 .expect("Could not create directory to write cxx-qt generated files");
214 }
215 let mut cpp = File::create(&cpp_path).expect("Could not create cxx-qt source file");
216 let source_generated = match cxx_qt_generated {
217 CppFragment::Pair { header: _, source } => source,
218 CppFragment::Header(_) => panic!("Unexpected call for header fragment."),
219 CppFragment::Source(source) => source,
220 };
221 cpp.write_all(source_generated.as_bytes())
222 .expect("Could not write cxx-qt source file");
223 cpp_file_paths.qobject = Some(cpp_path);
224 }
225
226 let header_path = PathBuf::from(format!(
227 "{}/{}.cxx.h",
228 header_directory.display(),
229 self.file_ident
230 ));
231 if let Some(directory) = header_path.parent() {
232 std::fs::create_dir_all(directory)
233 .expect("Could not create directory to write cxx-qt generated header files");
234 }
235 let mut header = File::create(header_path).expect("Could not create cxx header file");
236 header
237 .write_all(&self.cxx.header)
238 .expect("Could not write cxx header file");
239
240 let cpp_path = PathBuf::from(format!(
241 "{}/{}.cxx.cpp",
242 cpp_directory.display(),
243 self.file_ident
244 ));
245 if let Some(directory) = cpp_path.parent() {
246 std::fs::create_dir_all(directory)
247 .expect("Could not create directory to write cxx-qt generated source files");
248 }
249 let mut cpp = File::create(&cpp_path).expect("Could not create cxx source file");
250 cpp.write_all(&self.cxx.implementation)
251 .expect("Could not write cxx source file");
252 cpp_file_paths.plain_cpp = cpp_path;
253
254 cpp_file_paths
255 }
256}
257
258fn generate_cxxqt_cpp_files(
260 rs_source: &[impl AsRef<Path>],
261 header_dir: impl AsRef<Path>,
262 include_prefix: &str,
263) -> Vec<GeneratedCppFilePaths> {
264 let cxx_qt_dir = dir::gen();
265 std::fs::create_dir_all(&cxx_qt_dir).expect("Failed to create cxx-qt-gen directory!");
266 std::fs::write(cxx_qt_dir.join("include-prefix.txt"), include_prefix).expect("");
267
268 let header_dir = header_dir.as_ref().join(include_prefix);
269 let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
270
271 let mut generated_file_paths: Vec<GeneratedCppFilePaths> = Vec::with_capacity(rs_source.len());
272 for rs_path in rs_source {
273 let path = manifest_dir.join(rs_path);
274 println!("cargo::rerun-if-changed={}", path.to_string_lossy());
275
276 let generated_code = match GeneratedCpp::new(&path, rs_path, include_prefix) {
277 Ok(v) => v,
278 Err(diagnostic) => {
279 diagnostic.report();
280 std::process::exit(1);
281 }
282 };
283 generated_file_paths.push(generated_code.write_to_directories(&cxx_qt_dir, &header_dir));
284 }
285
286 generated_file_paths
287}
288
289pub(crate) fn module_name_from_uri(module_uri: &str) -> String {
290 module_uri.replace('.', "_")
292}
293
294pub(crate) fn crate_name() -> String {
295 env::var("CARGO_PKG_NAME").unwrap()
296}
297
298pub(crate) fn link_name() -> Option<String> {
299 env::var("CARGO_MANIFEST_LINKS").ok()
300}
301
302fn qt_modules_import() -> Option<String> {
303 env::var("CXX_QT_QT_MODULES").ok()
304}
305
306fn static_lib_name() -> String {
307 format!("{}-cxxqt-generated", crate_name())
308}
309
310fn panic_duplicate_file_and_qml_module(
311 path: impl AsRef<Path>,
312 uri: &str,
313 version_major: usize,
314 version_minor: usize,
315) {
316 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());
317}
318
319#[derive(Default)]
352pub struct CxxQtBuilder {
353 rust_sources: Vec<PathBuf>,
354 qobject_headers: Vec<QObjectHeaderOpts>,
355 qrc_files: Vec<PathBuf>,
356 qt_modules: HashSet<String>,
357 qml_modules: Vec<OwningQmlModule>,
358 cc_builder: cc::Build,
359 public_interface: Option<Interface>,
360 include_prefix: String,
361 initializers: Vec<String>,
362}
363
364impl CxxQtBuilder {
365 pub fn new() -> Self {
367 let mut qt_modules = HashSet::new();
368
369 if let Some(modules) = qt_modules_import() {
371 qt_modules.extend(
372 modules
373 .split(",")
375 .map(|module| {
378 if let Some((_, end)) = module.rsplit_once("::") {
379 end
380 } else {
381 module
382 }
383 })
384 .map(str::to_owned),
385 );
386 } else {
387 qt_modules.insert("Core".to_owned());
390 }
391
392 Self {
393 rust_sources: vec![],
394 qobject_headers: vec![],
395 qrc_files: vec![],
396 qt_modules,
397 qml_modules: vec![],
398 cc_builder: cc::Build::new(),
399 initializers: vec![],
400 public_interface: None,
401 include_prefix: crate_name(),
402 }
403 }
404
405 pub fn library(interface_definition: Interface) -> Self {
410 let mut this = Self::new();
411 this.public_interface = Some(interface_definition);
412
413 if link_name().is_none() {
414 panic!("Building a Cxx-Qt based library requires setting a `links` field in the Cargo.toml file.\nConsider adding:\n\tlinks = \"{}\"\nto your Cargo.toml\n", crate_name());
415 }
416
417 this
418 }
419
420 pub fn file(mut self, rust_source: impl AsRef<Path>) -> Self {
423 let rust_source = rust_source.as_ref().to_path_buf();
424 for qml_module in &self.qml_modules {
425 if qml_module.rust_files.contains(&rust_source) {
426 panic_duplicate_file_and_qml_module(
427 &rust_source,
428 &qml_module.uri,
429 qml_module.version_major,
430 qml_module.version_minor,
431 );
432 }
433 }
434 println!("cargo::rerun-if-changed={}", rust_source.display());
435 self.rust_sources.push(rust_source);
436 self
437 }
438
439 pub fn qrc(mut self, qrc_file: impl AsRef<Path>) -> Self {
453 let qrc_file = qrc_file.as_ref();
454 self.qrc_files.push(qrc_file.to_path_buf());
455 println!("cargo::rerun-if-changed={}", qrc_file.display());
456 self
457 }
458
459 pub fn qt_module(mut self, module: &str) -> Self {
468 if qt_modules_import().is_some() && !self.qt_modules.contains(module) {
470 panic!("Qt module mismatch between cxx-qt-build and CMake!\n\
471 Qt module '{module}' was not specified in CMake!\n\
472 When building with CMake, all Qt modules must be specified with the QT_MODULES argument in cxx_qt_import_crate");
473 }
474
475 self.qt_modules.insert(module.to_owned());
476 self
477 }
478
479 pub fn include_prefix(mut self, prefix: &str) -> Self {
481 prefix.clone_into(&mut self.include_prefix);
482 self
483 }
484
485 pub fn qml_module<A: AsRef<Path>, B: AsRef<Path>>(
509 mut self,
510 qml_module: QmlModule<A, B>,
511 ) -> CxxQtBuilder {
512 let qml_module = OwningQmlModule::from(qml_module);
513 for path in &qml_module.rust_files {
514 if self.rust_sources.contains(path) {
515 panic_duplicate_file_and_qml_module(
516 path,
517 &qml_module.uri,
518 qml_module.version_major,
519 qml_module.version_minor,
520 );
521 }
522 }
523 self.qml_modules.push(qml_module);
524 self
525 }
526
527 pub fn qobject_header(mut self, opts: impl Into<QObjectHeaderOpts>) -> Self {
530 let opts = opts.into();
531 println!("cargo::rerun-if-changed={}", opts.path.display());
532 self.qobject_headers.push(opts);
533 self
534 }
535
536 pub fn cc_builder(mut self, mut callback: impl FnMut(&mut cc::Build)) -> Self {
552 callback(&mut self.cc_builder);
553 self
554 }
555
556 fn define_cfg_variable(key: String, value: Option<&str>) {
557 if let Some(value) = value {
558 println!("cargo::rustc-cfg={key}=\"{value}\"");
559 } else {
560 println!("cargo::rustc-cfg={key}");
561 }
562 let variable_cargo = format!("CARGO_CFG_{}", key);
563 env::set_var(variable_cargo, value.unwrap_or("true"));
564 }
565
566 fn define_cfg_check_variable(key: String, values: Option<Vec<&str>>) {
567 if let Some(values) = values {
568 let values = values
569 .iter()
570 .map(|value| format!("\"{}\"", value.escape_default()))
572 .collect::<Vec<_>>()
573 .join(", ");
574 println!("cargo::rustc-check-cfg=cfg({key}, values({values}))");
575 } else {
576 println!("cargo::rustc-check-cfg=cfg({key})");
577 }
578 }
579
580 fn define_qt_version_cfg_variables(version: &SemVer) {
581 CxxQtBuilder::define_cfg_check_variable(
583 "cxxqt_qt_version_major".to_owned(),
584 Some(vec!["5", "6"]),
585 );
586 CxxQtBuilder::define_cfg_variable(
589 "cxxqt_qt_version_major".to_string(),
590 Some(version.major.to_string().as_str()),
591 );
592
593 for major in 5..=7 {
595 CxxQtBuilder::define_cfg_check_variable(
596 format!("cxxqt_qt_version_at_least_{major}"),
597 None,
598 );
599
600 for minor in 0..=99 {
601 CxxQtBuilder::define_cfg_check_variable(
602 format!("cxxqt_qt_version_at_least_{major}_{minor}"),
603 None,
604 );
605 }
606 }
607
608 for minor in 0..=version.minor {
609 let qt_version_at_least =
610 format!("cxxqt_qt_version_at_least_{}_{}", version.major, minor);
611 CxxQtBuilder::define_cfg_variable(qt_version_at_least.to_string(), None);
612 }
613
614 for major in 5..=version.major {
616 let at_least_qt_major_version = format!("cxxqt_qt_version_at_least_{}", major);
617 CxxQtBuilder::define_cfg_variable(at_least_qt_major_version, None);
618 }
619 }
620
621 fn write_common_headers() {
622 let header_root = dir::header_root();
623 std::fs::create_dir_all(header_root.join("rust"))
625 .expect("Could not create cxx header directory");
626 let h_path = header_root.join("rust").join("cxx.h");
627 {
630 std::fs::write(h_path, cxx_gen::HEADER).expect("Failed to write cxx.h");
631 }
632 }
633
634 fn include_dependency(&mut self, dependency: &Dependency) {
638 let header_root = dir::header_root();
639 let dependency_root = dependency.path.join("include");
640 for include_prefix in &dependency.manifest.exported_include_prefixes {
641 let source = dependency_root.join(include_prefix);
643 let dest = header_root.join(include_prefix);
644
645 match dir::symlink_or_copy_directory(source, dest) {
646 Ok(true) => (),
647 Ok(false) => {
648 panic!(
649 "Conflicting include_prefixes for {include_prefix}!\nDependency {dep_name} conflicts with existing include path",
650 dep_name = dependency.manifest.name,
651 );
652 }
653 Err(e) => {
654 panic!("Could not {INCLUDE_VERB} for include_prefix {include_prefix}: {e:?}");
655 }
656 }
657 }
658 }
659
660 fn setup_cc_builder(
661 builder: &mut cc::Build,
662 include_paths: &[impl AsRef<Path>],
663 defines: &[(String, Option<String>)],
664 ) {
665 builder.cpp(true);
667 builder.std("c++17");
668 builder.flag_if_supported("/Zc:__cplusplus");
670 builder.flag_if_supported("/permissive-");
671 builder.flag_if_supported("/bigobj");
672 builder.flag_if_supported("-Wa,-mbig-obj");
674
675 for (variable, value) in defines {
677 builder.define(variable, value.as_deref());
678 }
679
680 for include_path in include_paths {
681 builder.include(include_path);
682 }
683 }
684
685 fn moc_qobject_headers(&mut self, qtbuild: &mut qt_build_utils::QtBuild) {
686 for QObjectHeaderOpts {
687 path,
688 moc_arguments,
689 } in &self.qobject_headers
690 {
691 let moc_products = qtbuild.moc(path, moc_arguments.clone());
692 if let Some(dir) = moc_products.cpp.parent() {
694 self.cc_builder.include(dir);
695 }
696 self.cc_builder.file(moc_products.cpp);
697 }
698 }
699
700 fn generate_cpp_files_from_cxxqt_bridges(
701 &mut self,
702 header_dir: impl AsRef<Path>,
703 include_prefix: &str,
704 ) {
705 for files in generate_cxxqt_cpp_files(&self.rust_sources, &header_dir, include_prefix) {
706 self.cc_builder.file(files.plain_cpp);
707 if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) {
708 self.cc_builder.file(&qobject);
709 self.qobject_headers.push(qobject_header.into());
710 }
711 }
712 }
713
714 fn build_object_file(builder: &cc::Build, file_path: impl AsRef<Path>, object_path: PathBuf) {
715 let mut obj_builder = builder.clone();
716 obj_builder.file(file_path);
717 let obj_files = obj_builder.compile_intermediates();
718
719 if let [obj_file] = &obj_files[..] {
723 if dir::is_exporting() {
724 if let Some(directory) = object_path.parent() {
725 std::fs::create_dir_all(directory).unwrap_or_else(|_| {
726 panic!(
727 "Could not create directory for object file: {}",
728 object_path.to_string_lossy()
729 )
730 });
731 }
732 std::fs::copy(obj_file, &object_path).unwrap_or_else(|_| {
733 panic!(
734 "Failed to move object file to {}!",
735 object_path.to_string_lossy()
736 )
737 });
738 } else {
739 println!("cargo::rustc-link-arg={}", obj_file.to_string_lossy());
740 }
741 } else {
742 panic!(
743 "CXX-Qt internal error: Expected only one object file out of cc::Build! Got {}",
744 obj_files.len()
745 );
746 }
747 }
748
749 fn build_qml_modules(
750 &mut self,
751 init_builder: &cc::Build,
752 qtbuild: &mut qt_build_utils::QtBuild,
753 generated_header_dir: impl AsRef<Path>,
754 header_prefix: &str,
755 ) {
756 for qml_module in &self.qml_modules {
757 dir::clean(dir::module_target(&qml_module.uri))
758 .expect("Failed to clean qml module export directory!");
759
760 let mut qml_metatypes_json = Vec::new();
761
762 let dirs = qml_module
772 .rust_files
773 .iter()
774 .map(|file| {
775 if let Some(parent) = file.parent() {
776 parent.to_string_lossy().to_string()
777 } else {
778 String::new()
780 }
781 })
782 .collect::<HashSet<String>>();
783 if dirs.len() > 1 {
784 panic!(
785 "Only one directory is supported per QmlModule for rust_files.\n\
786 This is due to Qt bug https://bugreports.qt.io/browse/QTBUG-93443\n\
787 Found directories: {dirs:?}"
788 );
789 }
790
791 let cc_builder = &mut self.cc_builder;
796 qtbuild.cargo_link_libraries(cc_builder);
797
798 let mut moc_include_paths = HashSet::new();
799 for files in generate_cxxqt_cpp_files(
800 &qml_module.rust_files,
801 &generated_header_dir,
802 header_prefix,
803 ) {
804 cc_builder.file(files.plain_cpp);
805 if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header)
806 {
807 if let Some(dir) = qobject_header.parent() {
810 moc_include_paths.insert(dir.to_path_buf());
811 }
812
813 cc_builder.file(&qobject);
814 let moc_products = qtbuild.moc(
815 qobject_header,
816 MocArguments::default().uri(qml_module.uri.clone()),
817 );
818 if let Some(dir) = moc_products.cpp.parent() {
820 moc_include_paths.insert(dir.to_path_buf());
821 }
822 cc_builder.file(moc_products.cpp);
823 qml_metatypes_json.push(moc_products.metatypes_json);
824 }
825 }
826
827 let qml_module_registration_files = qtbuild.register_qml_module(
828 &qml_metatypes_json,
829 &qml_module.uri,
830 qml_module.version_major,
831 qml_module.version_minor,
832 &module_name_from_uri(&qml_module.uri),
836 &qml_module.qml_files,
837 &qml_module.qrc_files,
838 );
839 cc_builder
840 .file(qml_module_registration_files.qmltyperegistrar)
841 .file(qml_module_registration_files.plugin)
842 .file(qml_module_registration_files.rcc);
847
848 if let Some(include_path) = qml_module_registration_files.include_path {
851 moc_include_paths.insert(include_path);
852 }
853
854 for include_path in &moc_include_paths {
856 cc_builder.include(include_path);
857 }
858
859 for qmlcachegen_file in qml_module_registration_files.qmlcachegen {
860 cc_builder.file(qmlcachegen_file);
861 }
862 cc_builder.define("QT_STATICPLUGIN", None);
864
865 for path in qml_module.qml_files.iter().chain(
867 qml_module
868 .rust_files
869 .iter()
870 .chain(qml_module.qrc_files.iter()),
871 ) {
872 println!("cargo::rerun-if-changed={}", path.display());
873 }
874
875 Self::build_object_file(
884 init_builder,
885 &qml_module_registration_files.plugin_init,
886 dir::module_target(&qml_module.uri).join("plugin_init.o"),
887 );
888 }
889 }
890
891 fn setup_qt5_compatibility(&mut self, qtbuild: &qt_build_utils::QtBuild) {
892 if qtbuild.version().major == 5 {
908 self.initializers
909 .push(include_str!("std_types_qt5.cpp").to_owned());
910 }
911 }
912
913 fn generate_init_code(&self, initializers: &HashSet<PathBuf>) -> String {
914 initializers
915 .iter()
916 .map(|path| std::fs::read_to_string(path).expect("Could not read initializer file!"))
917 .chain(self.initializers.iter().cloned())
918 .collect::<Vec<_>>()
919 .join("\n")
920 }
921
922 fn build_initializers(&mut self, init_builder: &cc::Build, initializers: &HashSet<PathBuf>) {
923 let initializers_path = dir::out().join("cxx-qt-build").join("initializers");
924 std::fs::create_dir_all(&initializers_path).expect("Failed to create initializers path!");
925
926 let initializers_path = initializers_path.join(format!("{}.cpp", crate_name()));
927 std::fs::write(&initializers_path, self.generate_init_code(initializers))
928 .expect("Could not write initializers file");
929 Self::build_object_file(
930 init_builder,
931 initializers_path,
932 dir::crate_target().join("initializers.o"),
933 );
934 }
935
936 fn generate_cpp_from_qrc_files(
937 &mut self,
938 qtbuild: &mut qt_build_utils::QtBuild,
939 ) -> HashSet<PathBuf> {
940 self.qrc_files
941 .iter()
942 .map(|qrc_file| {
943 for qrc_inner_file in qtbuild.qrc_list(&qrc_file) {
945 println!("cargo::rerun-if-changed={}", qrc_inner_file.display());
946 }
947 qtbuild.qrc(&qrc_file)
950 })
951 .collect()
952 }
953
954 fn write_manifest(
955 &self,
956 dependencies: &[Dependency],
957 qt_modules: HashSet<String>,
958 initializers: HashSet<PathBuf>,
959 ) {
960 if let Some(interface) = &self.public_interface {
961 let dependencies = dependencies::reexported_dependencies(interface, dependencies);
966
967 let initializers = initializers.into_iter().collect();
968 let exported_include_prefixes =
969 dependencies::all_include_prefixes(interface, &dependencies);
970 let defines = dependencies::all_compile_definitions(Some(interface), &dependencies);
971
972 let manifest = Manifest {
973 name: crate_name(),
974 link_name: link_name()
975 .expect("The links key must be set when creating a library with CXX-Qt-build!"),
976 defines,
977 initializers,
978 qt_modules: qt_modules.into_iter().collect(),
979 exported_include_prefixes,
980 };
981
982 let manifest_path = dir::crate_target().join("manifest.json");
983 let manifest_json = serde_json::to_string_pretty(&manifest)
984 .expect("Failed to convert Manifest to JSON!");
985 std::fs::write(&manifest_path, manifest_json).expect("Failed to write manifest.json!");
986 println!(
987 "cargo::metadata=CXX_QT_MANIFEST_PATH={}",
988 manifest_path.to_string_lossy()
989 );
990 }
991 }
992
993 fn qt_modules(&self, dependencies: &[Dependency]) -> HashSet<String> {
994 let mut qt_modules = self.qt_modules.clone();
995 for dependency in dependencies {
996 qt_modules.extend(dependency.manifest.qt_modules.iter().cloned());
997 }
998 qt_modules
999 }
1000
1001 fn write_interface_include_dirs(&self) {
1002 let Some(interface) = &self.public_interface else {
1003 return;
1004 };
1005 let header_root = dir::header_root();
1006 for (header_dir, dest) in &interface.exported_include_directories {
1007 let dest_dir = header_root.join(dest);
1008 if let Err(e) = dir::symlink_or_copy_directory(header_dir, dest_dir) {
1009 panic!(
1010 "Failed to {INCLUDE_VERB} `{dest}` for export_include_directory `{dir_name}`: {e:?}",
1011 dir_name = header_dir.to_string_lossy()
1012 )
1013 };
1014 }
1015 }
1016
1017 pub fn build(mut self) {
1020 dir::clean(dir::crate_target()).expect("Failed to clean crate export directory!");
1021
1022 Self::write_common_headers();
1027 self.write_interface_include_dirs();
1028 let dependencies = Dependency::find_all();
1029 for dependency in &dependencies {
1030 self.include_dependency(dependency);
1031 }
1032 let qt_modules = self.qt_modules(&dependencies);
1033
1034 qt_build_utils::setup_linker();
1036
1037 let header_root = dir::header_root();
1038
1039 let mut qtbuild = qt_build_utils::QtBuild::new(qt_modules.iter().cloned().collect())
1040 .expect("Could not find Qt installation");
1041 qtbuild.cargo_link_libraries(&mut self.cc_builder);
1042 Self::define_qt_version_cfg_variables(qtbuild.version());
1043
1044 let mut init_builder = cc::Build::new();
1051 qtbuild.cargo_link_libraries(&mut init_builder);
1053 let mut include_paths = qtbuild.include_paths();
1054 include_paths.push(header_root.clone());
1055 include_paths.push(header_root.join(&self.include_prefix));
1061
1062 let compile_definitions =
1063 dependencies::all_compile_definitions(self.public_interface.as_ref(), &dependencies);
1064 Self::setup_cc_builder(&mut self.cc_builder, &include_paths, &compile_definitions);
1065
1066 Self::setup_cc_builder(&mut init_builder, &include_paths, &compile_definitions);
1067 let init_builder = init_builder;
1072
1073 self.generate_cpp_files_from_cxxqt_bridges(&header_root, &self.include_prefix.clone());
1075
1076 self.moc_qobject_headers(&mut qtbuild);
1077
1078 self.build_qml_modules(
1081 &init_builder,
1082 &mut qtbuild,
1083 &header_root,
1084 &self.include_prefix.clone(),
1085 );
1086
1087 let mut initializers = self.generate_cpp_from_qrc_files(&mut qtbuild);
1088 initializers.extend(dependencies::initializer_paths(
1089 self.public_interface.as_ref(),
1090 &dependencies,
1091 ));
1092
1093 self.setup_qt5_compatibility(&qtbuild);
1094
1095 self.build_initializers(&init_builder, &initializers);
1096
1097 if self.cc_builder.get_files().count() > 0 {
1100 if !dir::is_exporting() {
1105 println!("cargo::rustc-link-arg=-l{}", static_lib_name());
1106 }
1107
1108 self.cc_builder.compile(&static_lib_name());
1109 }
1110
1111 self.write_manifest(&dependencies, qt_modules, initializers);
1112 }
1113}