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!("{lib_path}/{prefix}Qt{version_major}{qt_module}{arch}.prl");
423 match Path::new(&prl_path).try_exists() {
424 Ok(exists) => {
425 if exists {
426 return prl_path;
427 }
428 }
429 Err(e) => {
430 println!("cargo::warning=failed checking for existence of {prl_path}: {e}");
431 }
432 }
433 }
434
435 format!("{lib_path}/{prefix}Qt{version_major}{qt_module}.prl")
436 }
437
438 pub fn cargo_link_libraries(&self, builder: &mut cc::Build) {
440 let prefix_path = self.qmake_query("QT_INSTALL_PREFIX");
441 let lib_path = self.qmake_query("QT_INSTALL_LIBS");
442 println!("cargo::rustc-link-search={lib_path}");
443
444 let target = env::var("TARGET");
445
446 if is_apple_target() {
458 println!("cargo::rustc-link-search=framework={lib_path}");
459
460 for framework_path in self.framework_paths() {
462 builder.flag_if_supported(format!("-F{}", framework_path.display()));
463 println!(
465 "cargo::rustc-link-arg=-Wl,-rpath,{}",
466 framework_path.display()
467 );
468 }
469 }
470
471 let prefix = match &target {
472 Ok(target) => {
473 if target.contains("windows") {
474 ""
475 } else {
476 "lib"
477 }
478 }
479 Err(_) => "lib",
480 };
481
482 for qt_module in &self.qt_modules {
483 let framework = if is_apple_target() {
484 Path::new(&format!("{lib_path}/Qt{qt_module}.framework")).exists()
485 } else {
486 false
487 };
488
489 let (link_lib, prl_path) = if framework {
490 (
491 format!("framework=Qt{qt_module}"),
492 format!("{lib_path}/Qt{qt_module}.framework/Resources/Qt{qt_module}.prl"),
493 )
494 } else {
495 (
496 format!("Qt{}{qt_module}", self.version.major),
497 self.find_qt_module_prl(&lib_path, prefix, self.version.major, qt_module),
498 )
499 };
500
501 self.cargo_link_qt_library(
502 &format!("Qt{}{qt_module}", self.version.major),
503 &prefix_path,
504 &lib_path,
505 &link_lib,
506 &prl_path,
507 builder,
508 );
509 }
510
511 let emscripten_targeted = match env::var("CARGO_CFG_TARGET_OS") {
512 Ok(val) => val == "emscripten",
513 Err(_) => false,
514 };
515 if emscripten_targeted {
516 let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS"));
517 println!("cargo::rustc-link-search={platforms_path}");
518 self.cargo_link_qt_library(
519 "qwasm",
520 &prefix_path,
521 &lib_path,
522 "qwasm",
523 &format!("{platforms_path}/libqwasm.prl"),
524 builder,
525 );
526 }
527 }
528
529 pub fn framework_paths(&self) -> Vec<PathBuf> {
532 let mut framework_paths = vec![];
533
534 if is_apple_target() {
535 let framework_path = self.qmake_query("QT_INSTALL_LIBS");
538 framework_paths.push(framework_path);
539 }
540
541 framework_paths
542 .iter()
543 .map(PathBuf::from)
544 .filter(|path| path.exists())
546 .collect()
547 }
548
549 pub fn include_paths(&self) -> Vec<PathBuf> {
552 let root_path = self.qmake_query("QT_INSTALL_HEADERS");
553 let lib_path = self.qmake_query("QT_INSTALL_LIBS");
554 let mut paths = Vec::new();
555 for qt_module in &self.qt_modules {
556 paths.push(format!("{root_path}/Qt{qt_module}"));
558
559 let header_path = format!("{lib_path}/Qt{qt_module}.framework/Headers");
561 if is_apple_target() && Path::new(&header_path).exists() {
562 paths.push(header_path);
563 }
564 }
565
566 paths.push(root_path);
568
569 paths
570 .iter()
571 .map(PathBuf::from)
572 .filter(|path| path.exists())
574 .collect()
575 }
576
577 pub fn version(&self) -> &SemVer {
579 &self.version
580 }
581
582 fn get_qt_tool(&self, tool_name: &str) -> Result<String, ()> {
585 for qmake_query_var in [
619 "QT_HOST_LIBEXECS/get",
620 "QT_HOST_LIBEXECS",
621 "QT_HOST_BINS/get",
622 "QT_HOST_BINS",
623 "QT_INSTALL_LIBEXECS/get",
624 "QT_INSTALL_LIBEXECS",
625 "QT_INSTALL_BINS/get",
626 "QT_INSTALL_BINS",
627 ] {
628 let executable_path = format!("{}/{tool_name}", self.qmake_query(qmake_query_var));
629 match Command::new(&executable_path).args(["-help"]).output() {
630 Ok(_) => return Ok(executable_path),
631 Err(_) => continue,
632 }
633 }
634 Err(())
635 }
636
637 pub fn moc(&mut self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts {
642 if self.moc_executable.is_none() {
643 self.moc_executable = Some(self.get_qt_tool("moc").expect("Could not find moc"));
644 }
645
646 let input_path = input_file.as_ref();
647
648 let moc_dir = PathBuf::from(format!(
650 "{}/qt-build-utils/moc",
651 env::var("OUT_DIR").unwrap()
652 ));
653 std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir");
654 let output_path = moc_dir.join(format!(
655 "moc_{}.cpp",
656 input_path.file_name().unwrap().to_str().unwrap()
657 ));
658
659 let metatypes_json_path = PathBuf::from(&format!("{}.json", output_path.display()));
660
661 let mut include_args = vec![];
662 for include_path in self
664 .include_paths()
665 .iter()
666 .chain(arguments.include_paths.iter())
667 {
668 include_args.push(format!("-I{}", include_path.display()));
669 }
670
671 let mut cmd = Command::new(self.moc_executable.as_ref().unwrap());
672
673 if let Some(uri) = arguments.uri {
674 cmd.arg(format!("-Muri={uri}"));
675 }
676
677 cmd.args(include_args);
678 cmd.arg(input_path.to_str().unwrap())
679 .arg("-o")
680 .arg(output_path.to_str().unwrap())
681 .arg("--output-json");
682 let cmd = cmd
683 .output()
684 .unwrap_or_else(|_| panic!("moc failed for {}", input_path.display()));
685
686 if !cmd.status.success() {
687 panic!(
688 "moc failed for {}:\n{}",
689 input_path.display(),
690 String::from_utf8_lossy(&cmd.stderr)
691 );
692 }
693
694 MocProducts {
695 cpp: output_path,
696 metatypes_json: metatypes_json_path,
697 }
698 }
699
700 pub fn register_qml_module(
708 &mut self,
709 metatypes_json: &[impl AsRef<Path>],
710 uri: &str,
711 version_major: usize,
712 version_minor: usize,
713 plugin_name: &str,
714 qml_files: &[impl AsRef<Path>],
715 qrc_files: &[impl AsRef<Path>],
716 ) -> QmlModuleRegistrationFiles {
717 if self.qmltyperegistrar_executable.is_none() {
718 self.qmltyperegistrar_executable = Some(
719 self.get_qt_tool("qmltyperegistrar")
720 .expect("Could not find qmltyperegistrar"),
721 );
722 }
723 if self.qmlcachegen_executable.is_none() && self.version.major >= 6 {
725 if let Ok(qmlcachegen_executable) = self.get_qt_tool("qmlcachegen") {
726 self.qmlcachegen_executable = Some(qmlcachegen_executable);
727 }
728 }
729
730 let qml_uri_dirs = uri.replace('.', "/");
731
732 let out_dir = env::var("OUT_DIR").unwrap();
733 let qt_build_utils_dir = PathBuf::from(format!("{out_dir}/qt-build-utils"));
734 std::fs::create_dir_all(&qt_build_utils_dir).expect("Could not create qt_build_utils dir");
735
736 let qml_module_dir = qt_build_utils_dir.join("qml_modules").join(&qml_uri_dirs);
737 std::fs::create_dir_all(&qml_module_dir).expect("Could not create QML module directory");
738
739 let qml_uri_underscores = uri.replace('.', "_");
740 let qmltypes_path = qml_module_dir.join("plugin.qmltypes");
741 let plugin_class_name = format!("{qml_uri_underscores}_plugin");
742
743 let qmldir_file_path = qml_module_dir.join("qmldir");
745 {
746 let mut qmldir = File::create(&qmldir_file_path).expect("Could not create qmldir file");
747 write!(
748 qmldir,
749 "module {uri}
750optional plugin {plugin_name}
751classname {plugin_class_name}
752typeinfo plugin.qmltypes
753prefer :/qt/qml/{qml_uri_dirs}/
754"
755 )
756 .expect("Could not write qmldir file");
757 }
758
759 let qrc_path =
761 qml_module_dir.join(format!("qml_module_resources_{qml_uri_underscores}.qrc"));
762 {
763 fn qrc_file_line(file_path: &impl AsRef<Path>) -> String {
764 let path_display = file_path.as_ref().display();
765 format!(
766 " <file alias=\"{}\">{}</file>\n",
767 path_display,
768 std::fs::canonicalize(file_path)
769 .unwrap_or_else(|_| panic!("Could not canonicalize path {path_display}"))
770 .display()
771 )
772 }
773
774 let mut qml_files_qrc = String::new();
775 for file_path in qml_files {
776 qml_files_qrc.push_str(&qrc_file_line(file_path));
777 }
778 for file_path in qrc_files {
779 qml_files_qrc.push_str(&qrc_file_line(file_path));
780 }
781
782 let mut qrc = File::create(&qrc_path).expect("Could not create qrc file");
783 let qml_module_dir_str = qml_module_dir.to_str().unwrap();
784 write!(
785 qrc,
786 r#"<RCC>
787<qresource prefix="/">
788 <file alias="/qt/qml/{qml_uri_dirs}">{qml_module_dir_str}</file>
789</qresource>
790<qresource prefix="/qt/qml/{qml_uri_dirs}">
791{qml_files_qrc}
792 <file alias="qmldir">{qml_module_dir_str}/qmldir</file>
793</qresource>
794</RCC>
795"#
796 )
797 .expect("Could note write qrc file");
798 }
799
800 let mut qmlcachegen_file_paths = Vec::new();
804 if let Some(qmlcachegen_executable) = &self.qmlcachegen_executable {
805 let qmlcachegen_dir = qt_build_utils_dir.join("qmlcachegen").join(&qml_uri_dirs);
806 std::fs::create_dir_all(&qmlcachegen_dir)
807 .expect("Could not create qmlcachegen directory for QML module");
808
809 let common_args = [
810 "-i".to_string(),
811 qmldir_file_path.to_string_lossy().to_string(),
812 "--resource".to_string(),
813 qrc_path.to_string_lossy().to_string(),
814 ];
815
816 let mut qml_file_qrc_paths = Vec::new();
817 for file in qml_files {
818 let qrc_resource_path =
819 format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display());
820
821 let qml_compiled_file = qmlcachegen_dir.join(format!(
822 "{}.cpp",
823 file.as_ref().file_name().unwrap().to_string_lossy()
824 ));
825 qmlcachegen_file_paths.push(PathBuf::from(&qml_compiled_file));
826
827 let specific_args = vec![
828 "--resource-path".to_string(),
829 qrc_resource_path.clone(),
830 "-o".to_string(),
831 qml_compiled_file.to_string_lossy().to_string(),
832 std::fs::canonicalize(file)
833 .unwrap()
834 .to_string_lossy()
835 .to_string(),
836 ];
837
838 let cmd = Command::new(qmlcachegen_executable)
839 .args(common_args.iter().chain(&specific_args))
840 .output()
841 .unwrap_or_else(|_| {
842 panic!(
843 "qmlcachegen failed for {} in QML module {uri}",
844 file.as_ref().display()
845 )
846 });
847 if !cmd.status.success() {
848 panic!(
849 "qmlcachegen failed for {} in QML module {uri}:\n{}",
850 file.as_ref().display(),
851 String::from_utf8_lossy(&cmd.stderr)
852 );
853 }
854 qml_file_qrc_paths.push(qrc_resource_path);
855 }
856
857 let qmlcachegen_loader = qmlcachegen_dir.join("qmlcache_loader.cpp");
858 let specific_args = vec![
859 "--resource-name".to_string(),
860 format!("qmlcache_{qml_uri_underscores}"),
861 "-o".to_string(),
862 qmlcachegen_loader.to_string_lossy().to_string(),
863 ];
864
865 if !qml_files.is_empty() {
867 let cmd = Command::new(qmlcachegen_executable)
868 .args(
869 common_args
870 .iter()
871 .chain(&specific_args)
872 .chain(&qml_file_qrc_paths),
873 )
874 .output()
875 .unwrap_or_else(|_| panic!("qmlcachegen failed for QML module {uri}"));
876 if !cmd.status.success() {
877 panic!(
878 "qmlcachegen failed for QML module {uri}:\n{}",
879 String::from_utf8_lossy(&cmd.stderr)
880 );
881 }
882 qmlcachegen_file_paths.push(PathBuf::from(&qmlcachegen_loader));
883 }
884 }
885
886 let qml_plugin_dir = PathBuf::from(format!("{out_dir}/qt-build-utils/qml_plugin"));
887 std::fs::create_dir_all(&qml_plugin_dir).expect("Could not create qml_plugin dir");
888
889 let qmltyperegistrar_output_path =
891 qml_plugin_dir.join(format!("{qml_uri_underscores}_qmltyperegistration.cpp"));
892
893 let metatypes_json: Vec<_> = metatypes_json
895 .iter()
896 .filter(|f| {
897 std::fs::metadata(f)
898 .unwrap_or_else(|_| {
899 panic!("couldn't open json file {}", f.as_ref().to_string_lossy())
900 })
901 .len()
902 > 0
903 })
904 .map(|f| f.as_ref().to_string_lossy().to_string())
905 .collect();
906
907 if !metatypes_json.is_empty() {
909 let mut args = vec![
910 "--generate-qmltypes".to_string(),
911 qmltypes_path.to_string_lossy().to_string(),
912 "--major-version".to_string(),
913 version_major.to_string(),
914 "--minor-version".to_string(),
915 version_minor.to_string(),
916 "--import-name".to_string(),
917 uri.to_string(),
918 "-o".to_string(),
919 qmltyperegistrar_output_path.to_string_lossy().to_string(),
920 ];
921 args.extend(metatypes_json);
922 let cmd = Command::new(self.qmltyperegistrar_executable.as_ref().unwrap())
923 .args(args)
924 .output()
925 .unwrap_or_else(|_| panic!("qmltyperegistrar failed for {uri}"));
926 if !cmd.status.success() {
927 panic!(
928 "qmltyperegistrar failed for {uri}:\n{}",
929 String::from_utf8_lossy(&cmd.stderr)
930 );
931 }
932 }
933
934 let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp"));
936 let qml_plugin_init_path = qml_plugin_dir.join(format!("{plugin_class_name}_init.cpp"));
937 let include_path;
938 {
939 let mut declarations = Vec::default();
940 let mut usages = Vec::default();
941
942 let mut generate_usage = |return_type: &str, function_name: &str| {
943 declarations.push(format!("extern {return_type} {function_name}();"));
944 usages.push(format!("volatile auto {function_name}_usage = &{function_name};\nQ_UNUSED({function_name}_usage);"));
945 };
946
947 generate_usage("void", &format!("qml_register_types_{qml_uri_underscores}"));
949 generate_usage(
950 "int",
951 &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"),
952 );
953
954 if !qml_files.is_empty() && self.qmlcachegen_executable.is_some() {
955 generate_usage(
956 "int",
957 &format!("qInitResources_qmlcache_{qml_uri_underscores}"),
958 );
959 }
960 let declarations = declarations.join("\n");
961 let usages = usages.join("\n");
962
963 std::fs::write(
964 &qml_plugin_cpp_path,
965 format!(
966 r#"
967#include <QtQml/qqmlextensionplugin.h>
968
969// TODO: Add missing handling for GHS (Green Hills Software compiler) that is in
970// https://code.qt.io/cgit/qt/qtbase.git/plain/src/corelib/global/qtsymbolmacros.h
971{declarations}
972
973class {plugin_class_name} : public QQmlEngineExtensionPlugin
974{{
975 Q_OBJECT
976 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlEngineExtensionInterface")
977
978public:
979 {plugin_class_name}(QObject *parent = nullptr) : QQmlEngineExtensionPlugin(parent)
980 {{
981 {usages}
982 }}
983}};
984
985// The moc-generated cpp file doesn't compile on its own; it needs to be #included here.
986#include "moc_{plugin_class_name}.cpp.cpp"
987"#,
988 ),
989 )
990 .expect("Failed to write plugin definition");
991
992 let moc_product = self.moc(
993 &qml_plugin_cpp_path,
994 MocArguments::default().uri(uri.to_owned()),
995 );
996 include_path = moc_product.cpp.parent().map(|path| path.to_path_buf());
998
999 std::fs::write(
1001 &qml_plugin_init_path,
1002 format!(
1003 r#"
1004#include <QtPlugin>
1005Q_IMPORT_PLUGIN({plugin_class_name});
1006"#
1007 ),
1008 )
1009 .expect("Failed to write plugin initializer file");
1010 }
1011
1012 QmlModuleRegistrationFiles {
1013 rcc: self.qrc(&qrc_path),
1014 qmlcachegen: qmlcachegen_file_paths,
1015 qmltyperegistrar: qmltyperegistrar_output_path,
1016 plugin: qml_plugin_cpp_path,
1017 plugin_init: qml_plugin_init_path,
1018 include_path,
1019 }
1020 }
1021
1022 pub fn qrc(&mut self, input_file: &impl AsRef<Path>) -> PathBuf {
1027 if self.rcc_executable.is_none() {
1028 self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc"));
1029 }
1030
1031 let input_path = input_file.as_ref();
1032 let output_folder = PathBuf::from(&format!(
1033 "{}/qt-build-utils/qrc",
1034 env::var("OUT_DIR").unwrap()
1035 ));
1036 std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir");
1037 let output_path = output_folder.join(format!(
1038 "{}.cpp",
1039 input_path.file_name().unwrap().to_string_lossy(),
1040 ));
1041
1042 let cmd = Command::new(self.rcc_executable.as_ref().unwrap())
1043 .args([
1044 input_path.to_str().unwrap(),
1045 "-o",
1046 output_path.to_str().unwrap(),
1047 "--name",
1048 input_path.file_name().unwrap().to_str().unwrap(),
1049 ])
1050 .output()
1051 .unwrap_or_else(|_| panic!("rcc failed for {}", input_path.display()));
1052
1053 if !cmd.status.success() {
1054 panic!(
1055 "rcc failed for {}:\n{}",
1056 input_path.display(),
1057 String::from_utf8_lossy(&cmd.stderr)
1058 );
1059 }
1060
1061 output_path
1062 }
1063
1064 pub fn qrc_list(&mut self, input_file: &impl AsRef<Path>) -> Vec<PathBuf> {
1066 if self.rcc_executable.is_none() {
1067 self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc"));
1068 }
1069
1070 let input_path = input_file.as_ref();
1072 let cmd_list = Command::new(self.rcc_executable.as_ref().unwrap())
1073 .args(["--list", input_path.to_str().unwrap()])
1074 .output()
1075 .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display()));
1076
1077 if !cmd_list.status.success() {
1078 panic!(
1079 "rcc --list failed for {}:\n{}",
1080 input_path.display(),
1081 String::from_utf8_lossy(&cmd_list.stderr)
1082 );
1083 }
1084
1085 String::from_utf8_lossy(&cmd_list.stdout)
1086 .split('\n')
1087 .map(PathBuf::from)
1088 .collect()
1089 }
1090}