1#![deny(missing_docs)]
7
8#![allow(clippy::too_many_arguments)]
17
18mod parse_cflags;
19
20use std::{
21 env,
22 fs::File,
23 io::Write,
24 path::{Path, PathBuf},
25 process::Command,
26};
27
28pub use versions::SemVer;
29
30use thiserror::Error;
31
32#[derive(Error, Debug)]
33pub enum QtBuildError {
35 #[error("QMAKE environment variable specified as {qmake_env_var} but could not detect Qt: {error:?}")]
37 QMakeSetQtMissing {
38 qmake_env_var: String,
40 error: Box<QtBuildError>,
42 },
43 #[error("Could not find Qt")]
45 QtMissing,
46 #[error("Executing `qmake -query` failed: {0:?}")]
48 QmakeFailed(#[from] std::io::Error),
49 #[error("QT_VERSION_MAJOR environment variable specified as {qt_version_major_env_var} but could not parse as integer: {source:?}")]
51 QtVersionMajorInvalid {
52 qt_version_major_env_var: String,
54 source: std::num::ParseIntError,
56 },
57 #[error("qmake version ({qmake_version}) does not match version specified by QT_VERSION_MAJOR ({qt_version_major})")]
59 QtVersionMajorDoesNotMatch {
60 qmake_version: u32,
62 qt_version_major: u32,
64 },
65}
66
67fn command_help_output(command: &str) -> std::io::Result<std::process::Output> {
68 Command::new(command).args(["--help"]).output()
69}
70
71fn is_apple_target() -> bool {
73 env::var("TARGET")
74 .map(|target| target.contains("apple"))
75 .unwrap_or_else(|_| false)
76}
77
78pub fn setup_linker() {
88 if env::var("CARGO_CFG_UNIX").is_err() {
89 return;
90 }
91
92 if let Ok(vendor) = env::var("CARGO_CFG_TARGET_VENDOR") {
93 if vendor == "apple" {
94 println!("cargo::rustc-link-arg=-fapple-link-rtlib");
97 }
98 }
99
100 let flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();
101 if !flags.contains("-fuse-ld") {
103 let ld_help = String::from_utf8(
106 command_help_output("ld")
107 .expect("Could not run ld command")
108 .stdout,
109 )
110 .unwrap();
111 let ld_is_bfd = ld_help.contains("symbolsrec")
113 || ld_help.contains("verilog")
114 || ld_help.contains("tekhex");
115
116 if !ld_is_bfd {
118 return;
119 }
120
121 if command_help_output("lld").is_ok() {
127 println!("cargo::rustc-link-arg=-fuse-ld=lld");
128 } else if command_help_output("ld.gold").is_ok() {
129 println!("cargo::rustc-link-arg=-fuse-ld=gold");
130 } else if command_help_output("mold").is_ok() {
131 println!("cargo::rustc-link-arg=-fuse-ld=mold");
132 } else {
133 println!("cargo::warning=Neither mold, lld, nor gold linkers were found. Linking with GNU ld.bfd will likely fail.");
134 }
135 }
136}
137
138pub struct MocProducts {
140 pub cpp: PathBuf,
142 pub metatypes_json: PathBuf,
144}
145
146#[derive(Default, Clone)]
149pub struct MocArguments {
150 uri: Option<String>,
151 include_paths: Vec<PathBuf>,
152}
153
154impl MocArguments {
155 pub fn uri(mut self, uri: String) -> Self {
157 self.uri = Some(uri);
158 self
159 }
160
161 pub fn include_path(mut self, include_path: PathBuf) -> Self {
163 self.include_paths.push(include_path);
164 self
165 }
166
167 pub fn include_paths(mut self, mut include_paths: Vec<PathBuf>) -> Self {
169 self.include_paths.append(&mut include_paths);
170 self
171 }
172}
173
174pub struct QmlModuleRegistrationFiles {
176 pub rcc: PathBuf,
180 pub qmlcachegen: Vec<PathBuf>,
182 pub qmltyperegistrar: PathBuf,
184 pub plugin: PathBuf,
186 pub plugin_init: PathBuf,
188 pub include_path: Option<PathBuf>,
190}
191
192pub struct QtBuild {
201 version: SemVer,
202 qmake_executable: String,
203 moc_executable: Option<String>,
204 qmltyperegistrar_executable: Option<String>,
205 qmlcachegen_executable: Option<String>,
206 rcc_executable: Option<String>,
207 qt_modules: Vec<String>,
208}
209
210impl QtBuild {
211 pub fn new(mut qt_modules: Vec<String>) -> Result<Self, QtBuildError> {
250 if qt_modules.is_empty() {
251 qt_modules.push("Core".to_string());
252 }
253 println!("cargo::rerun-if-env-changed=QMAKE");
254 println!("cargo::rerun-if-env-changed=QT_VERSION_MAJOR");
255 fn verify_candidate(candidate: &str) -> Result<(&str, versions::SemVer), QtBuildError> {
256 match Command::new(candidate)
257 .args(["-query", "QT_VERSION"])
258 .output()
259 {
260 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(QtBuildError::QtMissing),
261 Err(e) => Err(QtBuildError::QmakeFailed(e)),
262 Ok(output) => {
263 if output.status.success() {
264 let version_string = std::str::from_utf8(&output.stdout)
265 .unwrap()
266 .trim()
267 .to_string();
268 let qmake_version = versions::SemVer::new(version_string).unwrap();
269 if let Ok(env_version) = env::var("QT_VERSION_MAJOR") {
270 let env_version = match env_version.trim().parse::<u32>() {
271 Err(e) if *e.kind() == std::num::IntErrorKind::Empty => {
272 println!(
273 "cargo::warning=QT_VERSION_MAJOR environment variable defined but empty"
274 );
275 return Ok((candidate, qmake_version));
276 }
277 Err(e) => {
278 return Err(QtBuildError::QtVersionMajorInvalid {
279 qt_version_major_env_var: env_version,
280 source: e,
281 })
282 }
283 Ok(int) => int,
284 };
285 if env_version == qmake_version.major {
286 return Ok((candidate, qmake_version));
287 } else {
288 return Err(QtBuildError::QtVersionMajorDoesNotMatch {
289 qmake_version: qmake_version.major,
290 qt_version_major: env_version,
291 });
292 }
293 }
294 Ok((candidate, qmake_version))
295 } else {
296 Err(QtBuildError::QtMissing)
297 }
298 }
299 }
300 }
301
302 if let Ok(qmake_env_var) = env::var("QMAKE") {
303 match verify_candidate(qmake_env_var.trim()) {
304 Ok((executable_name, version)) => {
305 return Ok(Self {
306 qmake_executable: executable_name.to_string(),
307 moc_executable: None,
308 qmltyperegistrar_executable: None,
309 qmlcachegen_executable: None,
310 rcc_executable: None,
311 version,
312 qt_modules,
313 });
314 }
315 Err(e) => {
316 return Err(QtBuildError::QMakeSetQtMissing {
317 qmake_env_var,
318 error: Box::new(e),
319 })
320 }
321 }
322 }
323
324 let candidate_executable_names = ["qmake6", "qmake-qt5", "qmake"];
326 for (index, executable_name) in candidate_executable_names.iter().enumerate() {
327 match verify_candidate(executable_name) {
328 Ok((executable_name, version)) => {
329 return Ok(Self {
330 qmake_executable: executable_name.to_string(),
331 moc_executable: None,
332 qmltyperegistrar_executable: None,
333 qmlcachegen_executable: None,
334 rcc_executable: None,
335 version,
336 qt_modules,
337 });
338 }
339 Err(QtBuildError::QtVersionMajorDoesNotMatch {
344 qmake_version,
345 qt_version_major,
346 }) => {
347 if index == candidate_executable_names.len() - 1 {
348 return Err(QtBuildError::QtVersionMajorDoesNotMatch {
349 qmake_version,
350 qt_version_major,
351 });
352 }
353 eprintln!("Candidate qmake executable `{executable_name}` is for Qt{qmake_version} but QT_VERSION_MAJOR environment variable specified as {qt_version_major}. Trying next candidate executable name `{}`...", candidate_executable_names[index + 1]);
354 continue;
355 }
356 Err(QtBuildError::QtMissing) => continue,
357 Err(e) => return Err(e),
358 }
359 }
360
361 Err(QtBuildError::QtMissing)
362 }
363
364 pub fn qmake_query(&self, var_name: &str) -> String {
366 std::str::from_utf8(
367 &Command::new(&self.qmake_executable)
368 .args(["-query", var_name])
369 .output()
370 .unwrap()
371 .stdout,
372 )
373 .unwrap()
374 .trim()
375 .to_string()
376 }
377
378 fn cargo_link_qt_library(
379 &self,
380 name: &str,
381 prefix_path: &str,
382 lib_path: &str,
383 link_lib: &str,
384 prl_path: &str,
385 builder: &mut cc::Build,
386 ) {
387 println!("cargo::rustc-link-lib={link_lib}");
388
389 match std::fs::read_to_string(prl_path) {
390 Ok(prl) => {
391 for line in prl.lines() {
392 if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") {
393 parse_cflags::parse_libs_cflags(
394 name,
395 line.replace(r"$$[QT_INSTALL_LIBS]", lib_path)
396 .replace(r"$$[QT_INSTALL_PREFIX]", prefix_path)
397 .as_bytes(),
398 builder,
399 );
400 }
401 }
402 }
403 Err(e) => {
404 println!(
405 "cargo::warning=Could not open {} file to read libraries to link: {}",
406 &prl_path, e
407 );
408 }
409 }
410 }
411
412 fn find_qt_module_prl(
415 &self,
416 lib_path: &str,
417 prefix: &str,
418 version_major: u32,
419 qt_module: &str,
420 ) -> String {
421 for arch in ["", "_arm64-v8a", "_armeabi-v7a", "_x86", "_x86_64"] {
422 let prl_path = format!(
423 "{}/{}Qt{}{}{}.prl",
424 lib_path, prefix, version_major, qt_module, arch
425 );
426 match Path::new(&prl_path).try_exists() {
427 Ok(exists) => {
428 if exists {
429 return prl_path;
430 }
431 }
432 Err(e) => {
433 println!(
434 "cargo::warning=failed checking for existence of {}: {}",
435 prl_path, e
436 );
437 }
438 }
439 }
440
441 format!(
442 "{}/{}Qt{}{}.prl",
443 lib_path, prefix, version_major, qt_module
444 )
445 }
446
447 pub fn cargo_link_libraries(&self, builder: &mut cc::Build) {
449 let prefix_path = self.qmake_query("QT_INSTALL_PREFIX");
450 let lib_path = self.qmake_query("QT_INSTALL_LIBS");
451 println!("cargo::rustc-link-search={lib_path}");
452
453 let target = env::var("TARGET");
454
455 if is_apple_target() {
467 println!("cargo::rustc-link-search=framework={lib_path}");
468
469 for framework_path in self.framework_paths() {
471 builder.flag_if_supported(format!("-F{}", framework_path.display()));
472 println!(
474 "cargo::rustc-link-arg=-Wl,-rpath,{}",
475 framework_path.display()
476 );
477 }
478 }
479
480 let prefix = match &target {
481 Ok(target) => {
482 if target.contains("windows") {
483 ""
484 } else {
485 "lib"
486 }
487 }
488 Err(_) => "lib",
489 };
490
491 for qt_module in &self.qt_modules {
492 let framework = if is_apple_target() {
493 Path::new(&format!("{lib_path}/Qt{qt_module}.framework")).exists()
494 } else {
495 false
496 };
497
498 let (link_lib, prl_path) = if framework {
499 (
500 format!("framework=Qt{qt_module}"),
501 format!("{lib_path}/Qt{qt_module}.framework/Resources/Qt{qt_module}.prl"),
502 )
503 } else {
504 (
505 format!("Qt{}{qt_module}", self.version.major),
506 self.find_qt_module_prl(&lib_path, prefix, self.version.major, qt_module),
507 )
508 };
509
510 self.cargo_link_qt_library(
511 &format!("Qt{}{qt_module}", self.version.major),
512 &prefix_path,
513 &lib_path,
514 &link_lib,
515 &prl_path,
516 builder,
517 );
518 }
519
520 let emscripten_targeted = match env::var("CARGO_CFG_TARGET_OS") {
521 Ok(val) => val == "emscripten",
522 Err(_) => false,
523 };
524 if emscripten_targeted {
525 let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS"));
526 println!("cargo::rustc-link-search={platforms_path}");
527 self.cargo_link_qt_library(
528 "qwasm",
529 &prefix_path,
530 &lib_path,
531 "qwasm",
532 &format!("{platforms_path}/libqwasm.prl"),
533 builder,
534 );
535 }
536 }
537
538 pub fn framework_paths(&self) -> Vec<PathBuf> {
541 let mut framework_paths = vec![];
542
543 if is_apple_target() {
544 let framework_path = self.qmake_query("QT_INSTALL_LIBS");
547 framework_paths.push(framework_path);
548 }
549
550 framework_paths
551 .iter()
552 .map(PathBuf::from)
553 .filter(|path| path.exists())
555 .collect()
556 }
557
558 pub fn include_paths(&self) -> Vec<PathBuf> {
561 let root_path = self.qmake_query("QT_INSTALL_HEADERS");
562 let lib_path = self.qmake_query("QT_INSTALL_LIBS");
563 let mut paths = Vec::new();
564 for qt_module in &self.qt_modules {
565 paths.push(format!("{root_path}/Qt{qt_module}"));
567
568 let header_path = format!("{lib_path}/Qt{qt_module}.framework/Headers");
570 if is_apple_target() && Path::new(&header_path).exists() {
571 paths.push(header_path);
572 }
573 }
574
575 paths.push(root_path);
577
578 paths
579 .iter()
580 .map(PathBuf::from)
581 .filter(|path| path.exists())
583 .collect()
584 }
585
586 pub fn version(&self) -> &SemVer {
588 &self.version
589 }
590
591 fn get_qt_tool(&self, tool_name: &str) -> Result<String, ()> {
594 for qmake_query_var in [
628 "QT_HOST_LIBEXECS/get",
629 "QT_HOST_LIBEXECS",
630 "QT_HOST_BINS/get",
631 "QT_HOST_BINS",
632 "QT_INSTALL_LIBEXECS/get",
633 "QT_INSTALL_LIBEXECS",
634 "QT_INSTALL_BINS/get",
635 "QT_INSTALL_BINS",
636 ] {
637 let executable_path = format!("{}/{tool_name}", self.qmake_query(qmake_query_var));
638 match Command::new(&executable_path).args(["-help"]).output() {
639 Ok(_) => return Ok(executable_path),
640 Err(_) => continue,
641 }
642 }
643 Err(())
644 }
645
646 pub fn moc(&mut self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts {
651 if self.moc_executable.is_none() {
652 self.moc_executable = Some(self.get_qt_tool("moc").expect("Could not find moc"));
653 }
654
655 let input_path = input_file.as_ref();
656
657 let moc_dir = PathBuf::from(format!(
659 "{}/qt-build-utils/moc",
660 env::var("OUT_DIR").unwrap()
661 ));
662 std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir");
663 let output_path = moc_dir.join(format!(
664 "moc_{}.cpp",
665 input_path.file_name().unwrap().to_str().unwrap()
666 ));
667
668 let metatypes_json_path = PathBuf::from(&format!("{}.json", output_path.display()));
669
670 let mut include_args = vec![];
671 for include_path in self
673 .include_paths()
674 .iter()
675 .chain(arguments.include_paths.iter())
676 {
677 include_args.push(format!("-I{}", include_path.display()));
678 }
679
680 let mut cmd = Command::new(self.moc_executable.as_ref().unwrap());
681
682 if let Some(uri) = arguments.uri {
683 cmd.arg(format!("-Muri={uri}"));
684 }
685
686 cmd.args(include_args);
687 cmd.arg(input_path.to_str().unwrap())
688 .arg("-o")
689 .arg(output_path.to_str().unwrap())
690 .arg("--output-json");
691 let cmd = cmd
692 .output()
693 .unwrap_or_else(|_| panic!("moc failed for {}", input_path.display()));
694
695 if !cmd.status.success() {
696 panic!(
697 "moc failed for {}:\n{}",
698 input_path.display(),
699 String::from_utf8_lossy(&cmd.stderr)
700 );
701 }
702
703 MocProducts {
704 cpp: output_path,
705 metatypes_json: metatypes_json_path,
706 }
707 }
708
709 pub fn register_qml_module(
717 &mut self,
718 metatypes_json: &[impl AsRef<Path>],
719 uri: &str,
720 version_major: usize,
721 version_minor: usize,
722 plugin_name: &str,
723 qml_files: &[impl AsRef<Path>],
724 qrc_files: &[impl AsRef<Path>],
725 ) -> QmlModuleRegistrationFiles {
726 if self.qmltyperegistrar_executable.is_none() {
727 self.qmltyperegistrar_executable = Some(
728 self.get_qt_tool("qmltyperegistrar")
729 .expect("Could not find qmltyperegistrar"),
730 );
731 }
732 if self.qmlcachegen_executable.is_none() && self.version.major >= 6 {
734 if let Ok(qmlcachegen_executable) = self.get_qt_tool("qmlcachegen") {
735 self.qmlcachegen_executable = Some(qmlcachegen_executable);
736 }
737 }
738
739 let qml_uri_dirs = uri.replace('.', "/");
740
741 let out_dir = env::var("OUT_DIR").unwrap();
742 let qt_build_utils_dir = PathBuf::from(format!("{out_dir}/qt-build-utils"));
743 std::fs::create_dir_all(&qt_build_utils_dir).expect("Could not create qt_build_utils dir");
744
745 let qml_module_dir = qt_build_utils_dir.join("qml_modules").join(&qml_uri_dirs);
746 std::fs::create_dir_all(&qml_module_dir).expect("Could not create QML module directory");
747
748 let qml_uri_underscores = uri.replace('.', "_");
749 let qmltypes_path = qml_module_dir.join("plugin.qmltypes");
750 let plugin_class_name = format!("{qml_uri_underscores}_plugin");
751
752 let qmldir_file_path = qml_module_dir.join("qmldir");
754 {
755 let mut qmldir = File::create(&qmldir_file_path).expect("Could not create qmldir file");
756 write!(
757 qmldir,
758 "module {uri}
759optional plugin {plugin_name}
760classname {plugin_class_name}
761typeinfo plugin.qmltypes
762prefer :/qt/qml/{qml_uri_dirs}/
763"
764 )
765 .expect("Could not write qmldir file");
766 }
767
768 let qrc_path =
770 qml_module_dir.join(format!("qml_module_resources_{qml_uri_underscores}.qrc"));
771 {
772 fn qrc_file_line(file_path: &impl AsRef<Path>) -> String {
773 let path_display = file_path.as_ref().display();
774 format!(
775 " <file alias=\"{}\">{}</file>\n",
776 path_display,
777 std::fs::canonicalize(file_path)
778 .unwrap_or_else(|_| panic!("Could not canonicalize path {}", path_display))
779 .display()
780 )
781 }
782
783 let mut qml_files_qrc = String::new();
784 for file_path in qml_files {
785 qml_files_qrc.push_str(&qrc_file_line(file_path));
786 }
787 for file_path in qrc_files {
788 qml_files_qrc.push_str(&qrc_file_line(file_path));
789 }
790
791 let mut qrc = File::create(&qrc_path).expect("Could not create qrc file");
792 let qml_module_dir_str = qml_module_dir.to_str().unwrap();
793 write!(
794 qrc,
795 r#"<RCC>
796<qresource prefix="/">
797 <file alias="/qt/qml/{qml_uri_dirs}">{qml_module_dir_str}</file>
798</qresource>
799<qresource prefix="/qt/qml/{qml_uri_dirs}">
800{qml_files_qrc}
801 <file alias="qmldir">{qml_module_dir_str}/qmldir</file>
802</qresource>
803</RCC>
804"#
805 )
806 .expect("Could note write qrc file");
807 }
808
809 let mut qmlcachegen_file_paths = Vec::new();
813 if let Some(qmlcachegen_executable) = &self.qmlcachegen_executable {
814 let qmlcachegen_dir = qt_build_utils_dir.join("qmlcachegen").join(&qml_uri_dirs);
815 std::fs::create_dir_all(&qmlcachegen_dir)
816 .expect("Could not create qmlcachegen directory for QML module");
817
818 let common_args = [
819 "-i".to_string(),
820 qmldir_file_path.to_string_lossy().to_string(),
821 "--resource".to_string(),
822 qrc_path.to_string_lossy().to_string(),
823 ];
824
825 let mut qml_file_qrc_paths = Vec::new();
826 for file in qml_files {
827 let qrc_resource_path =
828 format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display());
829
830 let qml_compiled_file = qmlcachegen_dir.join(format!(
831 "{}.cpp",
832 file.as_ref().file_name().unwrap().to_string_lossy()
833 ));
834 qmlcachegen_file_paths.push(PathBuf::from(&qml_compiled_file));
835
836 let specific_args = vec![
837 "--resource-path".to_string(),
838 qrc_resource_path.clone(),
839 "-o".to_string(),
840 qml_compiled_file.to_string_lossy().to_string(),
841 std::fs::canonicalize(file)
842 .unwrap()
843 .to_string_lossy()
844 .to_string(),
845 ];
846
847 let cmd = Command::new(qmlcachegen_executable)
848 .args(common_args.iter().chain(&specific_args))
849 .output()
850 .unwrap_or_else(|_| {
851 panic!(
852 "qmlcachegen failed for {} in QML module {uri}",
853 file.as_ref().display()
854 )
855 });
856 if !cmd.status.success() {
857 panic!(
858 "qmlcachegen failed for {} in QML module {uri}:\n{}",
859 file.as_ref().display(),
860 String::from_utf8_lossy(&cmd.stderr)
861 );
862 }
863 qml_file_qrc_paths.push(qrc_resource_path);
864 }
865
866 let qmlcachegen_loader = qmlcachegen_dir.join("qmlcache_loader.cpp");
867 let specific_args = vec![
868 "--resource-name".to_string(),
869 format!("qmlcache_{qml_uri_underscores}"),
870 "-o".to_string(),
871 qmlcachegen_loader.to_string_lossy().to_string(),
872 ];
873
874 if !qml_files.is_empty() {
876 let cmd = Command::new(qmlcachegen_executable)
877 .args(
878 common_args
879 .iter()
880 .chain(&specific_args)
881 .chain(&qml_file_qrc_paths),
882 )
883 .output()
884 .unwrap_or_else(|_| panic!("qmlcachegen failed for QML module {uri}"));
885 if !cmd.status.success() {
886 panic!(
887 "qmlcachegen failed for QML module {uri}:\n{}",
888 String::from_utf8_lossy(&cmd.stderr)
889 );
890 }
891 qmlcachegen_file_paths.push(PathBuf::from(&qmlcachegen_loader));
892 }
893 }
894
895 let qml_plugin_dir = PathBuf::from(format!("{out_dir}/qt-build-utils/qml_plugin"));
896 std::fs::create_dir_all(&qml_plugin_dir).expect("Could not create qml_plugin dir");
897
898 let qmltyperegistrar_output_path =
900 qml_plugin_dir.join(format!("{qml_uri_underscores}_qmltyperegistration.cpp"));
901
902 let metatypes_json: Vec<_> = metatypes_json
904 .iter()
905 .filter(|f| {
906 std::fs::metadata(f)
907 .unwrap_or_else(|_| {
908 panic!("couldn't open json file {}", f.as_ref().to_string_lossy())
909 })
910 .len()
911 > 0
912 })
913 .map(|f| f.as_ref().to_string_lossy().to_string())
914 .collect();
915
916 if !metatypes_json.is_empty() {
918 let mut args = vec![
919 "--generate-qmltypes".to_string(),
920 qmltypes_path.to_string_lossy().to_string(),
921 "--major-version".to_string(),
922 version_major.to_string(),
923 "--minor-version".to_string(),
924 version_minor.to_string(),
925 "--import-name".to_string(),
926 uri.to_string(),
927 "-o".to_string(),
928 qmltyperegistrar_output_path.to_string_lossy().to_string(),
929 ];
930 args.extend(metatypes_json);
931 let cmd = Command::new(self.qmltyperegistrar_executable.as_ref().unwrap())
932 .args(args)
933 .output()
934 .unwrap_or_else(|_| panic!("qmltyperegistrar failed for {uri}"));
935 if !cmd.status.success() {
936 panic!(
937 "qmltyperegistrar failed for {uri}:\n{}",
938 String::from_utf8_lossy(&cmd.stderr)
939 );
940 }
941 }
942
943 let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp"));
945 let qml_plugin_init_path = qml_plugin_dir.join(format!("{plugin_class_name}_init.cpp"));
946 let include_path;
947 {
948 let mut declarations = Vec::default();
949 let mut usages = Vec::default();
950
951 let mut generate_usage = |return_type: &str, function_name: &str| {
952 declarations.push(format!("extern {return_type} {function_name}();"));
953 usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);"));
954 };
955
956 generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}"));
958 generate_usage(
959 "int",
960 &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"),
961 );
962
963 if !qml_files.is_empty() && self.qmlcachegen_executable.is_some() {
964 generate_usage(
965 "int",
966 &format!("qInitResources_qmlcache_{qml_uri_underscores}"),
967 );
968 }
969 let declarations = declarations.join("\n");
970 let usages = usages.join("\n");
971
972 std::fs::write(
973 &qml_plugin_cpp_path,
974 format!(
975 r#"
976#include <QtQml/qqmlextensionplugin.h>
977
978// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in
979// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h
980{declarations}
981
982class {plugin_class_name} : public QQmlEngineExtensionPlugin
983{{
984 Q_OBJECT
985 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlEngineExtensionInterface")
986
987public:
988 {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent)
989 {{
990 {usages}
991 }}
992}};
993
994// The moc-generated cpp file doesn't compile on its own; it needs to be #included here.
995#include "moc_{plugin_class_name}.cpp.cpp"
996"#,
997 ),
998 )
999 .expect("Failed to write plugin definition");
1000
1001 let moc_product = self.moc(
1002 &qml_plugin_cpp_path,
1003 MocArguments::default().uri(uri.to_owned()),
1004 );
1005 include_path = moc_product.cpp.parent().map(|path| path.to_path_buf());
1007
1008 std::fs::write(
1010 &qml_plugin_init_path,
1011 format!(
1012 r#"
1013#include <QtPlugin>
1014Q_IMPORT_PLUGIN({plugin_class_name});
1015"#
1016 ),
1017 )
1018 .expect("Failed to write plugin initializer file");
1019 }
1020
1021 QmlModuleRegistrationFiles {
1022 rcc: self.qrc(&qrc_path),
1023 qmlcachegen: qmlcachegen_file_paths,
1024 qmltyperegistrar: qmltyperegistrar_output_path,
1025 plugin: qml_plugin_cpp_path,
1026 plugin_init: qml_plugin_init_path,
1027 include_path,
1028 }
1029 }
1030
1031 pub fn qrc(&mut self, input_file: &impl AsRef<Path>) -> PathBuf {
1036 if self.rcc_executable.is_none() {
1037 self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc"));
1038 }
1039
1040 let input_path = input_file.as_ref();
1041 let output_folder = PathBuf::from(&format!(
1042 "{}/qt-build-utils/qrc",
1043 env::var("OUT_DIR").unwrap()
1044 ));
1045 std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir");
1046 let output_path = output_folder.join(format!(
1047 "{}.cpp",
1048 input_path.file_name().unwrap().to_string_lossy(),
1049 ));
1050
1051 let cmd = Command::new(self.rcc_executable.as_ref().unwrap())
1052 .args([
1053 input_path.to_str().unwrap(),
1054 "-o",
1055 output_path.to_str().unwrap(),
1056 "--name",
1057 input_path.file_name().unwrap().to_str().unwrap(),
1058 ])
1059 .output()
1060 .unwrap_or_else(|_| panic!("rcc failed for {}", input_path.display()));
1061
1062 if !cmd.status.success() {
1063 panic!(
1064 "rcc failed for {}:\n{}",
1065 input_path.display(),
1066 String::from_utf8_lossy(&cmd.stderr)
1067 );
1068 }
1069
1070 output_path
1071 }
1072
1073 pub fn qrc_list(&mut self, input_file: &impl AsRef<Path>) -> Vec<PathBuf> {
1075 if self.rcc_executable.is_none() {
1076 self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc"));
1077 }
1078
1079 let input_path = input_file.as_ref();
1081 let cmd_list = Command::new(self.rcc_executable.as_ref().unwrap())
1082 .args(["--list", input_path.to_str().unwrap()])
1083 .output()
1084 .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display()));
1085
1086 if !cmd_list.status.success() {
1087 panic!(
1088 "rcc --list failed for {}:\n{}",
1089 input_path.display(),
1090 String::from_utf8_lossy(&cmd_list.stderr)
1091 );
1092 }
1093
1094 String::from_utf8_lossy(&cmd_list.stdout)
1095 .split('\n')
1096 .map(PathBuf::from)
1097 .collect()
1098 }
1099}