#![deny(missing_docs)]
#![allow(clippy::too_many_arguments)]
mod cfg;
pub use cfg::CfgGenerator;
mod error;
pub use error::QtBuildError;
mod initializer;
pub use initializer::Initializer;
mod installation;
pub use installation::QtInstallation;
#[cfg(feature = "qmake")]
pub use installation::qmake::QtInstallationQMake;
#[cfg(feature = "qt_minimal")]
pub use installation::qt_minimal::QtInstallationQtMinimal;
#[cfg(feature = "qmake")]
mod parse_cflags;
mod platform;
pub use platform::QtPlatformLinker;
mod qml;
pub use qml::{PluginType, QmlDirBuilder, QmlFile, QmlLsIniBuilder, QmlPluginCppBuilder, QmlUri};
mod qrc;
pub use qrc::{QResource, QResourceFile, QResources};
mod tool;
pub use tool::{
MocArguments, MocProducts, QmlCacheArguments, QmlCacheProducts, QtPathsQueryArguments, QtTool,
QtToolMoc, QtToolQmlCacheGen, QtToolQmlTypeRegistrar, QtToolQtPaths, QtToolRcc,
};
mod utils;
use std::{
env,
ffi::{OsStr, OsString},
fs::File,
path::{Path, PathBuf},
};
use semver::Version;
pub struct QmlModuleRegistrationFiles {
pub rcc: PathBuf,
pub qmlcachegen: Vec<PathBuf>,
pub qmltyperegistrar: Option<PathBuf>,
pub qmltypes: PathBuf,
pub qmldir: PathBuf,
pub plugin: PathBuf,
pub plugin_init: Initializer,
pub include_path: Option<PathBuf>,
pub qml_files: Vec<PathBuf>,
}
pub struct QtBuild {
qt_installation: Box<dyn QtInstallation>,
qt_modules: Vec<String>,
autorcc_options: Vec<OsString>,
}
impl QtBuild {
pub fn new(qt_modules: Vec<String>) -> anyhow::Result<Self> {
let find_qt_installation = || -> anyhow::Result<Box<dyn QtInstallation>> {
#[cfg(feature = "qmake")]
{
if let Some(result) = QtInstallationQMake::try_from_qmake_env() {
return result
.map(|installation| -> Box<dyn QtInstallation> { Box::new(installation) });
}
}
#[cfg(feature = "qt_version")]
let mut local_versions = std::collections::BTreeSet::new();
#[cfg(feature = "qt_version")]
{
let versions = qt_version::qt_versions();
#[cfg(feature = "qmake")]
{
if let Ok(qt_installation) = QtInstallationQMake::try_from_path() {
if versions.contains(&qt_installation.version()) {
return Ok(Box::new(qt_installation));
}
local_versions.insert(qt_installation.version());
}
}
#[cfg(feature = "qt_minimal")]
{
if let Ok(local_artifacts) = QtInstallationQtMinimal::local_artifacts() {
let artifacts = QtInstallationQtMinimal::match_artifact_requirements(
local_artifacts.clone(),
&versions,
);
let mut artifacts = QtInstallationQtMinimal::group_artifacts(artifacts);
artifacts.sort_by_key(|artifact| artifact.version.clone());
for artifact in artifacts.into_iter().rev() {
if let Ok(qt_installation) =
QtInstallationQtMinimal::try_from(PathBuf::from(artifact.url))
{
return Ok(Box::new(qt_installation));
}
}
local_versions
.extend(local_artifacts.into_iter().map(|artifact| artifact.version));
}
for version in versions.into_iter().rev() {
if let Ok(qt_installation) = QtInstallationQtMinimal::try_from(version) {
return Ok(Box::new(qt_installation));
}
}
}
}
#[cfg(not(feature = "qt_version"))]
{
#[cfg(feature = "qmake")]
{
if let Ok(qt_installation) = QtInstallationQMake::try_from_path() {
return Ok(Box::new(qt_installation));
}
}
}
#[cfg(feature = "qt_version")]
{
Err(QtBuildError::QtMissingVersion {
available_versions: local_versions.into_iter().collect(),
requested_versions: qt_version::qt_versions(),
}
.into())
}
#[cfg(not(feature = "qt_version"))]
Err(QtBuildError::QtMissing.into())
};
Ok(Self::with_installation(find_qt_installation()?, qt_modules))
}
pub fn with_installation(
qt_installation: Box<dyn QtInstallation>,
mut qt_modules: Vec<String>,
) -> Self {
if qt_modules.is_empty() {
qt_modules.push("Core".to_owned());
}
Self {
qt_installation,
qt_modules,
autorcc_options: Vec::new(),
}
}
pub fn autorcc_options(mut self, options: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Self {
self.autorcc_options = options
.into_iter()
.map(|s| s.as_ref().to_os_string())
.collect();
self
}
pub fn cargo_link_libraries(&self, builder: &mut cc::Build) {
self.qt_installation.link_modules(builder, &self.qt_modules);
}
pub fn framework_paths(&self) -> Vec<PathBuf> {
self.qt_installation.framework_paths(&self.qt_modules)
}
pub fn include_paths(&self) -> Vec<PathBuf> {
self.qt_installation.include_paths(&self.qt_modules)
}
pub fn installation(&self) -> &dyn QtInstallation {
self.qt_installation.as_ref()
}
pub fn version(&self) -> Version {
self.qt_installation.version()
}
pub fn moc(&mut self) -> QtToolMoc {
QtToolMoc::new(self.qt_installation.as_ref(), &self.qt_modules)
}
pub fn register_qml_module(
&mut self,
metatypes_json: &[impl AsRef<Path>],
uri: &QmlUri,
version_major: usize,
version_minor: usize,
plugin_name: &str,
qml_files: &[QmlFile],
depends: impl IntoIterator<Item = impl Into<QmlUri>>,
plugin_type: PluginType,
) -> QmlModuleRegistrationFiles {
let qml_uri_dirs = uri.as_dirs();
let qml_uri_underscores = uri.as_underscores();
let plugin_type_info = "plugin.qmltypes";
let plugin_class_name = format!("{}_plugin", qml_uri_underscores);
let out_dir = env::var("OUT_DIR").unwrap();
let qt_build_utils_dir = PathBuf::from(format!("{out_dir}/qt-build-utils"));
std::fs::create_dir_all(&qt_build_utils_dir).expect("Could not create qt_build_utils dir");
let qml_module_dir = qt_build_utils_dir.join("qml_modules").join(&qml_uri_dirs);
std::fs::create_dir_all(&qml_module_dir).expect("Could not create QML module directory");
let qmltypes_path = qml_module_dir.join(plugin_type_info);
let qmldir_file_path = qml_module_dir.join("qmldir");
{
let qml_type_files = qml_files
.iter()
.filter(|file| {
file.get_path()
.file_name()
.and_then(OsStr::to_str)
.and_then(|file_name| file_name.chars().next())
.map(char::is_uppercase)
.unwrap_or_default()
})
.cloned();
let mut file = File::create(&qmldir_file_path).expect("Could not create qmldir file");
QmlDirBuilder::new(uri.clone())
.depends(depends)
.plugin(plugin_name, true)
.class_name(&plugin_class_name)
.type_info(plugin_type_info)
.qml_files(qml_type_files)
.write(&mut file)
.expect("Could not write qmldir file");
}
let qrc_path =
qml_module_dir.join(format!("qml_module_resources_{qml_uri_underscores}.qrc"));
{
let qml_module_dir_str = qml_module_dir.to_str().unwrap();
let qml_uri_dirs_prefix = format!("/qt/qml/{qml_uri_dirs}");
let mut qrc = File::create(&qrc_path).expect("Could not create qrc file");
QResources::new()
.resource({
let mut resource = QResource::new().prefix(qml_uri_dirs_prefix.clone()).file(
QResourceFile::new(format!("{qml_module_dir_str}/qmldir"))
.alias("qmldir".to_string()),
);
fn resource_add_path(resource: QResource, path: &Path) -> QResource {
let resolved = std::fs::canonicalize(path)
.unwrap_or_else(|_| {
panic!("Could not canonicalize path {}", path.display())
})
.display()
.to_string();
resource
.file(QResourceFile::new(resolved).alias(path.display().to_string()))
}
for file in qml_files {
resource = resource_add_path(resource, file.get_path());
}
resource
})
.write(&mut qrc)
.expect("Could note write qrc file");
}
let mut qmlcachegen_file_paths = Vec::new();
if self.qt_installation.version().major >= 6 {
let qml_cache_args = QmlCacheArguments {
uri: uri.clone(),
qmldir_path: qmldir_file_path.clone(),
qmldir_qrc_path: qrc_path.clone(),
};
let mut qml_resource_paths = Vec::new();
for file in qml_files {
let result = QtToolQmlCacheGen::new(self.qt_installation.as_ref())
.compile(qml_cache_args.clone(), file.get_path());
qmlcachegen_file_paths.push(result.qml_cache_path);
qml_resource_paths.push(result.qml_resource_path);
}
if !qml_files.is_empty() {
qmlcachegen_file_paths.push(
QtToolQmlCacheGen::new(self.qt_installation.as_ref())
.compile_loader(qml_cache_args.clone(), &qml_resource_paths),
);
}
}
let qml_plugin_dir = PathBuf::from(format!("{out_dir}/qt-build-utils/qml_plugin"));
std::fs::create_dir_all(&qml_plugin_dir).expect("Could not create qml_plugin dir");
let qmltyperegistrar_path = self.qmltyperegistrar().compile(
metatypes_json,
&qmltypes_path,
uri,
Version::new(version_major as u64, version_minor as u64, 0),
);
let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp"));
let include_path;
{
let mut file = File::create(&qml_plugin_cpp_path)
.expect("Could not create plugin definition file");
let plugin_init = QmlPluginCppBuilder::new(uri.clone(), plugin_class_name.clone())
.qml_cache(!qml_files.is_empty() && !qmlcachegen_file_paths.is_empty())
.plugin_type(plugin_type)
.write(&mut file)
.expect("Failed to write plugin definition");
let moc_product = self.moc().compile(
&qml_plugin_cpp_path,
MocArguments::default().uri(uri.to_owned()),
);
include_path = moc_product.cpp.parent().map(Path::to_path_buf);
let qml_files = qml_files
.iter()
.map(|qml_file| qml_file.get_path().to_path_buf())
.collect();
let rcc = self.rcc().compile(&qrc_path);
QmlModuleRegistrationFiles {
rcc: rcc.file.unwrap(),
qmlcachegen: qmlcachegen_file_paths,
qmltyperegistrar: qmltyperegistrar_path,
qmltypes: qmltypes_path,
qmldir: qmldir_file_path,
plugin: qml_plugin_cpp_path,
plugin_init,
include_path,
qml_files,
}
}
}
pub fn rcc(&self) -> QtToolRcc {
QtToolRcc::new(self.qt_installation.as_ref()).custom_args(&self.autorcc_options)
}
pub fn qmltyperegistrar(&self) -> QtToolQmlTypeRegistrar {
QtToolQmlTypeRegistrar::new(self.qt_installation.as_ref())
}
pub fn qtpaths(&self) -> QtToolQtPaths {
QtToolQtPaths::new(self.qt_installation.as_ref())
}
}