1#![deny(missing_docs)]
7
8#![allow(clippy::too_many_arguments)]
21mod cfg;
22pub use cfg::CfgGenerator;
23
24mod error;
25pub use error::QtBuildError;
26
27mod initializer;
28pub use initializer::Initializer;
29
30mod installation;
31pub use installation::QtInstallation;
32
33#[cfg(feature = "qmake")]
34pub use installation::qmake::QtInstallationQMake;
35
36#[cfg(feature = "qt_minimal")]
37pub use installation::qt_minimal::QtInstallationQtMinimal;
38
39#[cfg(feature = "qmake")]
40mod parse_cflags;
41
42mod platform;
43pub use platform::QtPlatformLinker;
44
45mod qml;
46pub use qml::{PluginType, QmlDirBuilder, QmlFile, QmlLsIniBuilder, QmlPluginCppBuilder, QmlUri};
47
48mod qrc;
49pub use qrc::{QResource, QResourceFile, QResources};
50
51mod tool;
52pub use tool::{
53 MocArguments, MocProducts, QmlCacheArguments, QmlCacheProducts, QtPathsQueryArguments, QtTool,
54 QtToolMoc, QtToolQmlCacheGen, QtToolQmlTypeRegistrar, QtToolQtPaths, QtToolRcc,
55};
56
57mod utils;
58
59use std::{
60 env,
61 ffi::{OsStr, OsString},
62 fs::File,
63 path::{Path, PathBuf},
64};
65
66use semver::Version;
67
68pub struct QmlModuleRegistrationFiles {
70 pub rcc: PathBuf,
74 pub qmlcachegen: Vec<PathBuf>,
76 pub qmltyperegistrar: Option<PathBuf>,
78 pub qmltypes: PathBuf,
81 pub qmldir: PathBuf,
84 pub plugin: PathBuf,
86 pub plugin_init: Initializer,
88 pub include_path: Option<PathBuf>,
90 pub qml_files: Vec<PathBuf>,
92}
93
94pub struct QtBuild {
103 qt_installation: Box<dyn QtInstallation>,
104 qt_modules: Vec<String>,
105 autorcc_options: Vec<OsString>,
106}
107
108impl QtBuild {
109 pub fn new(qt_modules: Vec<String>) -> anyhow::Result<Self> {
113 let find_qt_installation = || -> anyhow::Result<Box<dyn QtInstallation>> {
114 #[cfg(feature = "qmake")]
118 {
119 if let Some(result) = QtInstallationQMake::try_from_qmake_env() {
120 return result
121 .map(|installation| -> Box<dyn QtInstallation> { Box::new(installation) });
122 }
123 }
124
125 #[cfg(feature = "qt_version")]
126 let mut local_versions = std::collections::BTreeSet::new();
127
128 #[cfg(feature = "qt_version")]
130 {
131 let versions = qt_version::qt_versions();
132
133 #[cfg(feature = "qmake")]
135 {
136 if let Ok(qt_installation) = QtInstallationQMake::try_from_path() {
138 if versions.contains(&qt_installation.version()) {
139 return Ok(Box::new(qt_installation));
140 }
141
142 local_versions.insert(qt_installation.version());
143 }
144 }
145
146 #[cfg(feature = "qt_minimal")]
148 {
149 if let Ok(local_artifacts) = QtInstallationQtMinimal::local_artifacts() {
151 let artifacts = QtInstallationQtMinimal::match_artifact_requirements(
153 local_artifacts.clone(),
154 &versions,
155 );
156
157 let mut artifacts = QtInstallationQtMinimal::group_artifacts(artifacts);
159
160 artifacts.sort_by_key(|artifact| artifact.version.clone());
162
163 for artifact in artifacts.into_iter().rev() {
166 if let Ok(qt_installation) =
168 QtInstallationQtMinimal::try_from(PathBuf::from(artifact.url))
169 {
170 return Ok(Box::new(qt_installation));
171 }
172 }
173
174 local_versions
175 .extend(local_artifacts.into_iter().map(|artifact| artifact.version));
176 }
177
178 for version in versions.into_iter().rev() {
184 if let Ok(qt_installation) = QtInstallationQtMinimal::try_from(version) {
185 return Ok(Box::new(qt_installation));
186 }
187 }
188 }
189 }
190
191 #[cfg(not(feature = "qt_version"))]
192 {
193 #[cfg(feature = "qmake")]
195 {
196 if let Ok(qt_installation) = QtInstallationQMake::try_from_path() {
198 return Ok(Box::new(qt_installation));
199 }
200 }
201
202 }
205
206 #[cfg(feature = "qt_version")]
207 {
208 Err(QtBuildError::QtMissingVersion {
209 available_versions: local_versions.into_iter().collect(),
210 requested_versions: qt_version::qt_versions(),
211 }
212 .into())
213 }
214
215 #[cfg(not(feature = "qt_version"))]
216 Err(QtBuildError::QtMissing.into())
217 };
218
219 Ok(Self::with_installation(find_qt_installation()?, qt_modules))
220 }
221
222 pub fn with_installation(
225 qt_installation: Box<dyn QtInstallation>,
226 mut qt_modules: Vec<String>,
227 ) -> Self {
228 if qt_modules.is_empty() {
229 qt_modules.push("Core".to_owned());
230 }
231
232 Self {
233 qt_installation,
234 qt_modules,
235 autorcc_options: Vec::new(),
236 }
237 }
238
239 pub fn autorcc_options(mut self, options: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Self {
241 self.autorcc_options = options
242 .into_iter()
243 .map(|s| s.as_ref().to_os_string())
244 .collect();
245 self
246 }
247
248 pub fn cargo_link_libraries(&self, builder: &mut cc::Build) {
250 self.qt_installation.link_modules(builder, &self.qt_modules);
251 }
252
253 pub fn framework_paths(&self) -> Vec<PathBuf> {
256 self.qt_installation.framework_paths(&self.qt_modules)
257 }
258
259 pub fn include_paths(&self) -> Vec<PathBuf> {
262 self.qt_installation.include_paths(&self.qt_modules)
263 }
264
265 pub fn installation(&self) -> &dyn QtInstallation {
267 self.qt_installation.as_ref()
268 }
269
270 pub fn version(&self) -> Version {
272 self.qt_installation.version()
273 }
274
275 pub fn moc(&mut self) -> QtToolMoc {
279 QtToolMoc::new(self.qt_installation.as_ref(), &self.qt_modules)
280 }
281
282 pub fn register_qml_module(
290 &mut self,
291 metatypes_json: &[impl AsRef<Path>],
292 uri: &QmlUri,
293 version_major: usize,
294 version_minor: usize,
295 plugin_name: &str,
296 qml_files: &[QmlFile],
297 depends: impl IntoIterator<Item = impl Into<QmlUri>>,
298 plugin_type: PluginType,
299 ) -> QmlModuleRegistrationFiles {
300 let qml_uri_dirs = uri.as_dirs();
301 let qml_uri_underscores = uri.as_underscores();
302 let plugin_type_info = "plugin.qmltypes";
303 let plugin_class_name = format!("{}_plugin", qml_uri_underscores);
304
305 let out_dir = env::var("OUT_DIR").unwrap();
306 let qt_build_utils_dir = PathBuf::from(format!("{out_dir}/qt-build-utils"));
307 std::fs::create_dir_all(&qt_build_utils_dir).expect("Could not create qt_build_utils dir");
308
309 let qml_module_dir = qt_build_utils_dir.join("qml_modules").join(&qml_uri_dirs);
310 std::fs::create_dir_all(&qml_module_dir).expect("Could not create QML module directory");
311
312 let qmltypes_path = qml_module_dir.join(plugin_type_info);
313
314 let qmldir_file_path = qml_module_dir.join("qmldir");
316 {
317 let qml_type_files = qml_files
318 .iter()
319 .filter(|file| {
320 file.get_path()
323 .file_name()
324 .and_then(OsStr::to_str)
325 .and_then(|file_name| file_name.chars().next())
326 .map(char::is_uppercase)
327 .unwrap_or_default()
328 })
329 .cloned();
330 let mut file = File::create(&qmldir_file_path).expect("Could not create qmldir file");
331 QmlDirBuilder::new(uri.clone())
332 .depends(depends)
333 .plugin(plugin_name, true)
334 .class_name(&plugin_class_name)
335 .type_info(plugin_type_info)
336 .qml_files(qml_type_files)
337 .write(&mut file)
338 .expect("Could not write qmldir file");
339 }
340
341 let qrc_path =
344 qml_module_dir.join(format!("qml_module_resources_{qml_uri_underscores}.qrc"));
345 {
346 let qml_module_dir_str = qml_module_dir.to_str().unwrap();
347 let qml_uri_dirs_prefix = format!("/qt/qml/{qml_uri_dirs}");
348 let mut qrc = File::create(&qrc_path).expect("Could not create qrc file");
349 QResources::new()
350 .resource({
351 let mut resource = QResource::new().prefix(qml_uri_dirs_prefix.clone()).file(
352 QResourceFile::new(format!("{qml_module_dir_str}/qmldir"))
353 .alias("qmldir".to_string()),
354 );
355
356 fn resource_add_path(resource: QResource, path: &Path) -> QResource {
357 let resolved = std::fs::canonicalize(path)
358 .unwrap_or_else(|_| {
359 panic!("Could not canonicalize path {}", path.display())
360 })
361 .display()
362 .to_string();
363 resource
364 .file(QResourceFile::new(resolved).alias(path.display().to_string()))
365 }
366
367 for file in qml_files {
368 resource = resource_add_path(resource, file.get_path());
369 }
370 resource
371 })
372 .write(&mut qrc)
373 .expect("Could note write qrc file");
374 }
375
376 let mut qmlcachegen_file_paths = Vec::new();
380
381 if self.qt_installation.version().major >= 6 {
383 let qml_cache_args = QmlCacheArguments {
384 uri: uri.clone(),
385 qmldir_path: qmldir_file_path.clone(),
386 qmldir_qrc_path: qrc_path.clone(),
387 };
388 let mut qml_resource_paths = Vec::new();
389 for file in qml_files {
390 let result = QtToolQmlCacheGen::new(self.qt_installation.as_ref())
391 .compile(qml_cache_args.clone(), file.get_path());
392 qmlcachegen_file_paths.push(result.qml_cache_path);
393 qml_resource_paths.push(result.qml_resource_path);
394 }
395
396 if !qml_files.is_empty() {
398 qmlcachegen_file_paths.push(
399 QtToolQmlCacheGen::new(self.qt_installation.as_ref())
400 .compile_loader(qml_cache_args.clone(), &qml_resource_paths),
401 );
402 }
403 }
404
405 let qml_plugin_dir = PathBuf::from(format!("{out_dir}/qt-build-utils/qml_plugin"));
406 std::fs::create_dir_all(&qml_plugin_dir).expect("Could not create qml_plugin dir");
407
408 let qmltyperegistrar_path = self.qmltyperegistrar().compile(
410 metatypes_json,
411 &qmltypes_path,
412 uri,
413 Version::new(version_major as u64, version_minor as u64, 0),
414 );
415
416 let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp"));
418 let include_path;
419 {
420 let mut file = File::create(&qml_plugin_cpp_path)
421 .expect("Could not create plugin definition file");
422 let plugin_init = QmlPluginCppBuilder::new(uri.clone(), plugin_class_name.clone())
423 .qml_cache(!qml_files.is_empty() && !qmlcachegen_file_paths.is_empty())
424 .plugin_type(plugin_type)
425 .write(&mut file)
426 .expect("Failed to write plugin definition");
427
428 let moc_product = self.moc().compile(
429 &qml_plugin_cpp_path,
430 MocArguments::default().uri(uri.to_owned()),
431 );
432 include_path = moc_product.cpp.parent().map(Path::to_path_buf);
434
435 let qml_files = qml_files
436 .iter()
437 .map(|qml_file| qml_file.get_path().to_path_buf())
438 .collect();
439
440 let rcc = self.rcc().compile(&qrc_path);
441 QmlModuleRegistrationFiles {
442 rcc: rcc.file.unwrap(),
445 qmlcachegen: qmlcachegen_file_paths,
446 qmltyperegistrar: qmltyperegistrar_path,
447 qmltypes: qmltypes_path,
448 qmldir: qmldir_file_path,
449 plugin: qml_plugin_cpp_path,
450 plugin_init,
451 include_path,
452 qml_files,
453 }
454 }
455 }
456
457 pub fn rcc(&self) -> QtToolRcc {
461 QtToolRcc::new(self.qt_installation.as_ref()).custom_args(&self.autorcc_options)
462 }
463
464 pub fn qmltyperegistrar(&self) -> QtToolQmlTypeRegistrar {
466 QtToolQmlTypeRegistrar::new(self.qt_installation.as_ref())
467 }
468
469 pub fn qtpaths(&self) -> QtToolQtPaths {
471 QtToolQtPaths::new(self.qt_installation.as_ref())
472 }
473}