diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 67f61f869..e7d38d4ee 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -545,6 +545,9 @@ where self.cc_builder.file(qml_type_registration_files.plugin); cc_builder_whole_archive.file(qml_type_registration_files.plugin_init); cc_builder_whole_archive.file(qml_type_registration_files.qrc); + for qmlcachegen_file in qml_type_registration_files.qmlcachegen { + cc_builder_whole_archive.file(qmlcachegen_file); + } self.cc_builder.define("QT_STATICPLUGIN", None); cc_builder_whole_archive_files_added = true; } diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 52fc99209..0ce0c6bec 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -171,6 +171,8 @@ pub struct QmlModuleRegistrationFiles { pub plugin_init: PathBuf, /// File generated by rcc for the QML plugin. Must be linked with `+whole-archive`. pub qrc: PathBuf, + /// Files generated by qmlcachegen. Must be linked with `+whole-archive`. + pub qmlcachegen: Vec, } /// Helper for build.rs scripts using Qt @@ -186,6 +188,7 @@ pub struct QtBuild { qmake_executable: String, moc_executable: Option, qmltyperegistrar_executable: Option, + qmlcachegen_executable: Option, rcc_executable: Option, qt_modules: Vec, } @@ -286,6 +289,7 @@ impl QtBuild { qmake_executable: executable_name.to_string(), moc_executable: None, qmltyperegistrar_executable: None, + qmlcachegen_executable: None, rcc_executable: None, version, qt_modules, @@ -309,6 +313,7 @@ impl QtBuild { qmake_executable: executable_name.to_string(), moc_executable: None, qmltyperegistrar_executable: None, + qmlcachegen_executable: None, rcc_executable: None, version, qt_modules, @@ -618,6 +623,7 @@ impl QtBuild { } /// Generate C++ files to automatically register a QML module at build time using the JSON output from [moc](Self::moc). + /// When using Qt 6, this will run qmlcachegen to compile the specified .qml files ahead-of-time. pub fn register_qml_module( &mut self, metatypes_json: &[impl AsRef], @@ -634,6 +640,12 @@ impl QtBuild { .expect("Could not find qmltyperegistrar"), ); } + // qmlcachegen has a different CLI in Qt 5, so only support Qt >= 6 + if self.qmlcachegen_executable.is_none() && self.version.major >= 6 { + if let Ok(qmlcachegen_executable) = self.get_qt_tool("qmlcachegen") { + self.qmlcachegen_executable = Some(qmlcachegen_executable); + } + } let uri = &qml_module.uri; let qml_uri_dirs = uri.replace('.', "/"); @@ -646,8 +658,8 @@ impl QtBuild { let typeinfo_path = format!("{qml_uri_cpp_symbol_safe}.qmltypes"); let plugin_class_name = format!("{qml_uri_cpp_symbol_safe}_plugin"); - let mut qmldir = - File::create(format!("{qml_module_dir}/qmldir")).expect("Could not create qmldir file"); + let qmldir_file_path = format!("{qml_module_dir}/qmldir"); + let mut qmldir = File::create(&qmldir_file_path).expect("Could not create qmldir file"); write!( qmldir, "module {uri} @@ -770,11 +782,94 @@ Q_IMPORT_PLUGIN({plugin_class_name}); ) .unwrap(); + let mut qmlcachegen_file_paths = Vec::new(); + // qmlcachegen needs to be run once for each .qml file with --resource-path, + // then once for the module with --resource-name. + if let Some(qmlcachegen_executable) = &self.qmlcachegen_executable { + let qmlcachegen_dir = format!("{out_dir}/qmlcachegen/{qml_uri_dirs}"); + std::fs::create_dir_all(&qmlcachegen_dir) + .expect("Could not create qmlcachegen directory for QML module"); + + let common_args = vec![ + "-i".to_string(), + qmldir_file_path.to_string(), + "--resource".to_string(), + qrc_path.clone(), + ]; + + let mut qml_file_qrc_paths = Vec::new(); + for file in qml_module.qml_files { + let qrc_resource_path = + format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display()); + + let qml_compiled_file = format!( + "{qmlcachegen_dir}/{}.cpp", + file.as_ref().file_name().unwrap().to_string_lossy() + ); + qmlcachegen_file_paths.push(PathBuf::from(&qml_compiled_file)); + + let specific_args = vec![ + "--resource-path".to_string(), + qrc_resource_path.clone(), + "-o".to_string(), + qml_compiled_file, + std::fs::canonicalize(file) + .unwrap() + .to_string_lossy() + .to_string(), + ]; + + let cmd = Command::new(qmlcachegen_executable) + .args(common_args.iter().chain(&specific_args)) + .output() + .unwrap_or_else(|_| { + panic!( + "qmlcachegen failed for {} in QML module {uri}", + file.as_ref().display() + ) + }); + if !cmd.status.success() { + panic!( + "qmlcachegen failed for {} in QML module {uri}:\n{}", + file.as_ref().display(), + String::from_utf8_lossy(&cmd.stderr) + ); + } + qml_file_qrc_paths.push(qrc_resource_path); + } + + let qmlcachegen_loader = format!("{qmlcachegen_dir}/qmlcache_loader.cpp"); + let specific_args = vec![ + "--resource-name".to_string(), + format!("qmlcache_{qml_uri_cpp_symbol_safe}"), + "-o".to_string(), + qmlcachegen_loader.clone(), + ]; + + let cmd = Command::new(qmlcachegen_executable) + .args( + common_args + .iter() + .chain(&specific_args) + .chain(&qml_file_qrc_paths), + ) + .output() + .unwrap_or_else(|_| panic!("qmlcachegen failed for QML module {uri}")); + if !cmd.status.success() { + panic!( + "qmlcachegen failed for QML module {uri}:\n{}", + String::from_utf8_lossy(&cmd.stderr) + ); + } + qmlcachegen_file_paths.push(PathBuf::from(&qmlcachegen_loader)); + } + QmlModuleRegistrationFiles { qmltyperegistrar: output_path, plugin: qml_plugin_cpp_path, plugin_init: qml_plugin_init_path, qrc: qrc_output, + qmlcachegen: qmlcachegen_file_paths, } }