mod artifact;
mod checksum;
mod download;
mod extract;
use std::collections::HashMap;
use std::fs::DirEntry;
use std::path::{Path, PathBuf};
use crate::installation::qt_minimal::artifact::ParsedQtArtifact;
use crate::{QtBuildError, QtInstallation};
pub struct QtInstallationQtMinimal {
path_qt: PathBuf,
version: semver::Version,
}
impl TryFrom<PathBuf> for QtInstallationQtMinimal {
type Error = anyhow::Error;
fn try_from(path_qt: PathBuf) -> Result<Self, Self::Error> {
println!("cargo::rerun-if-changed={}", path_qt.display());
for folder in ["bin", "include", "lib"] {
if !path_qt.join(folder).exists() {
return Err(anyhow::anyhow!(
"Failed to find {folder} in Qt path: {}",
path_qt.display()
));
}
}
let Some(qtpaths) = ["bin", "libexec"]
.into_iter()
.map(|folder| {
path_qt
.join(folder)
.join(crate::QtTool::QtPaths.binary_name())
})
.find(|path| {
if path.exists() {
return true;
}
let path_exe = path.with_extension("exe");
if path_exe.exists() {
return true;
}
false
})
else {
return Err(anyhow::anyhow!(
"Failed to find qtpaths in Qt path: {}",
path_qt.display()
));
};
let version = semver::Version::parse(
&crate::QtToolQtPaths::from_path_buf(qtpaths)
.query("QT_VERSION")
.expect("Could not query qtpaths for QT_VERSION"),
)
.expect("Could not parse Qt version");
let link_location = Self::symlink_install_location()?;
let linked_path = link_location.join(path_qt.strip_prefix(Self::qt_minimal_root())?);
Ok(Self {
path_qt: linked_path.to_path_buf(),
version,
})
}
}
impl TryFrom<semver::Version> for QtInstallationQtMinimal {
type Error = anyhow::Error;
fn try_from(version: semver::Version) -> Result<Self, Self::Error> {
let local_artifacts = Self::local_artifacts()?;
let local_matches_grouped = Self::group_artifacts(Self::match_artifact_requirements(
local_artifacts,
core::slice::from_ref(&version),
));
if let Some(artifact) = local_matches_grouped.first() {
if let Ok(qt_installation) = Self::try_from(Path::new(&artifact.url).to_path_buf()) {
return Ok(qt_installation);
}
}
let manifest: artifact::ParsedQtManifest =
serde_json::from_str(qt_artifacts::QT_MANIFEST_JSON)?;
let artifacts =
Self::match_artifact_requirements(manifest.artifacts, core::slice::from_ref(&version));
let artifact_bin = artifacts
.iter()
.find(|artifact| artifact.content.contains(&"bin".to_string()))
.ok_or_else(|| QtBuildError::QtMissing)?;
let artifact_include = artifacts
.iter()
.find(|artifact| artifact.content.contains(&"include".to_string()))
.ok_or_else(|| QtBuildError::QtMissing)?;
let extract_target_dir = Self::qt_minimal_root()
.join(format!(
"{}.{}.{}",
version.major, version.minor, version.patch
))
.join(&artifact_bin.os)
.join(&artifact_bin.arch);
artifact_bin.download_and_extract(&extract_target_dir);
if artifact_bin != artifact_include {
artifact_include.download_and_extract(&extract_target_dir);
}
Self::try_from(extract_target_dir.join("qt"))
}
}
impl QtInstallation for QtInstallationQtMinimal {
fn framework_paths(&self, qt_modules: &[String]) -> Vec<PathBuf> {
let path_lib = self.path_qt.join("lib");
super::shared::framework_paths_for_qt_modules(qt_modules, path_lib)
}
fn include_paths(&self, qt_modules: &[String]) -> Vec<PathBuf> {
let path_include = self.path_qt.join("include");
let path_lib = self.path_qt.join("lib");
super::shared::include_paths_for_qt_modules(qt_modules, path_include, path_lib)
}
fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]) {
let path_frameworks = self.framework_paths(qt_modules);
let path_lib = self.path_qt.join("lib");
let path_prefix = self.path_qt.clone();
let path_plugins = self.path_qt.join("plugins");
let qt_version = self.version.clone();
super::shared::link_for_qt_modules(
builder,
qt_modules,
path_frameworks,
path_lib,
path_prefix,
path_plugins,
qt_version,
);
}
fn try_find_tool(&self, tool: crate::QtTool) -> anyhow::Result<PathBuf> {
for folder in ["bin", "libexec"] {
let path = self.path_qt.join(folder).join(tool.binary_name());
if path.exists() {
return Ok(path);
}
let path_exe = path.with_extension("exe");
if path_exe.exists() {
return Ok(path_exe);
}
}
Err(anyhow::anyhow!(
"Failed to find {} in bin/ or libexec/ under {}",
tool.binary_name(),
self.path_qt.display()
))
}
fn version(&self) -> semver::Version {
self.version.clone()
}
}
impl QtInstallationQtMinimal {
fn qt_minimal_root() -> PathBuf {
println!("cargo::rerun-if-env-changed=QT_MINIMAL_DOWNLOAD_ROOT");
let path = if let Ok(root) = std::env::var("QT_MINIMAL_DOWNLOAD_ROOT") {
PathBuf::from(root)
} else {
dirs::data_local_dir()
.expect("User local data directory to be found")
.join("qt_minimal_download")
};
if !path.exists() {
std::fs::create_dir_all(&path).expect("Could not create Qt minimal root path");
}
path
}
pub fn symlink_install_location() -> anyhow::Result<PathBuf> {
let out_dir = std::env::var("OUT_DIR")?;
let qt_location = QtInstallationQtMinimal::qt_minimal_root();
let new_location = Path::new(&out_dir).join("qt_minimal_root");
println!("cargo::rerun-if-changed={}", new_location.display());
if let Ok(existing_link) = new_location.read_link() {
if existing_link == qt_location {
return Ok(new_location);
}
#[cfg(unix)]
{
std::fs::remove_file(&new_location)?;
}
#[cfg(windows)]
{
std::fs::remove_dir(&new_location)?;
}
}
#[cfg(unix)]
{
std::os::unix::fs::symlink(qt_location, &new_location)?;
}
#[cfg(windows)]
{
std::os::windows::fs::symlink_dir(qt_location, &new_location)?;
}
#[cfg(not(any(unix, windows)))]
panic!(
"Unknown platform, cannot create symlink to OUT_DIR from {}",
qt_location.display()
);
Ok(new_location)
}
pub(crate) fn local_artifacts() -> anyhow::Result<Vec<ParsedQtArtifact>> {
let base_dir = Self::qt_minimal_root();
println!("cargo::rerun-if-changed={}", base_dir.display());
let mut artifacts = vec![];
for version in list_dirs(&base_dir) {
println!("cargo::rerun-if-changed={}", version.path().display());
let path = version;
let semver = semver::Version::parse(path.file_name().to_str().unwrap())
.expect("Could not parse semver from directory name");
for os in list_dirs(&path.path()) {
println!("cargo::rerun-if-changed={}", os.path().display());
let path = os;
let os = path.file_name().to_str().unwrap().to_string();
for arch in list_dirs(&path.path()) {
println!("cargo::rerun-if-changed={}", arch.path().display());
let path = arch;
let dir_entries = list_dirs(&path.path());
let qt_dir_path = dir_entries
.iter()
.rfind(|dir| dir.file_name() == "qt")
.expect("Expected to find a Qt directory");
println!("cargo::rerun-if-changed={}", qt_dir_path.path().display());
let qt_folders = list_dirs(&qt_dir_path.path());
for dir in qt_folders {
let filename = dir.file_name();
let mut artifact_type = None;
if filename == "bin" {
artifact_type = Some("bin");
} else if filename == "include" {
artifact_type = Some("include");
}
if let Some(artifact_type) = artifact_type {
artifacts.push(ParsedQtArtifact::new(
semver.clone(),
path.file_name().to_string_lossy().to_string(),
os.clone(),
qt_dir_path.path().to_string_lossy().to_string(),
artifact_type.to_string(),
))
}
}
}
}
}
Ok(artifacts)
}
pub(crate) fn match_artifact_requirements(
artifacts: Vec<ParsedQtArtifact>,
versions: &[semver::Version],
) -> Vec<ParsedQtArtifact> {
println!("cargo::rerun-if-env-changed=TARGET");
let (arch, os) = match std::env::var("TARGET").expect("TARGET to be set").as_str() {
"aarch64-unknown-linux-gnu" => ("arm64", "linux"),
"x86_64-unknown-linux-gnu" => ("x86_64", "linux"),
"aarch64-apple-darwin" => ("arm64", "macos"),
"x86_64-apple-darwin" => ("x86_64", "macos"),
"aarch64-pc-windows-msvc" => ("arm64", "windows"),
"x86_64-pc-windows-msvc" => ("x86_64", "windows"),
_others => panic!("Unknown TARGET to map to Qt artifact"),
};
artifacts
.into_iter()
.filter(|artifact| {
artifact.arch == arch && artifact.os == os && versions.contains(&artifact.version)
})
.collect()
}
pub(crate) fn group_artifacts(artifacts: Vec<ParsedQtArtifact>) -> Vec<ParsedQtArtifact> {
artifacts.into_iter().fold(
HashMap::<semver::Version, ParsedQtArtifact>::default(),
|mut acc, mut artifact| {
acc.entry(artifact.version.clone())
.and_modify(|value| {
if value.url == artifact.url {
value.content.append(&mut artifact.content)
} else {
println!("cargo::warning=Found multiple minimal installations of the same version but different urls: {} and {}", value.url, artifact.url);
}
})
.or_insert(artifact);
acc
},
)
.into_values()
.filter(|artifact| {
artifact.content.contains(&"bin".to_string())
&& artifact.content.contains(&"include".to_string())
})
.collect()
}
}
fn list_dirs(path: &Path) -> Vec<DirEntry> {
path.read_dir().unwrap().flatten().collect()
}