Skip to content

Commit af0cede

Browse files
authored
qt-build-utils: auto pick qmake or qt minimal (#1424)
* qt_minimal: have a common method to filter by version, arch, os * qt_minimal: add grouping of artifacts and pick latest local install * qt-build-utils: enable qt_version features when qt_minimal is enabled * qt-build-utils: add getter to access the inner QtInstallation * qt_minimal: add various cargo rerun-if lines * qt-build-utils: bump qt-version to 0.1.3 * qt-build-utils: use which to find full qmake path * qt-build-utils: ensure that Command has clean environment Otherwise when executing Qt binaries if LD_LIBRARY_PATH is set collisions can occur with differing Qt binary and libraries.
1 parent 05de6cd commit af0cede

File tree

10 files changed

+189
-49
lines changed

10 files changed

+189
-49
lines changed

Cargo.lock

Lines changed: 30 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/qt-build-utils/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ tempfile = { version = "3.25.0", optional = true }
2626
tar = { version = "0.4.44", optional = true }
2727
flate2 = { version = "1.1.9", optional = true }
2828
qt-artifacts = { version = "0.1.0", optional = true }
29-
qt-version = { version = "0.1.2", optional = true }
29+
qt-version = { version = "0.1.3", default-features = false, optional = true }
30+
which = { version = "8.0.0", optional = true }
3031

3132
[features]
3233
# TODO: should we default to qmake or let downstream crates specify, such as cxx-qt-build
@@ -42,8 +43,8 @@ default = ["qmake"]
4243
#
4344
# When linking Qt dynamically, this makes no difference.
4445
link_qt_object_files = []
45-
qmake = []
46-
qt_minimal = ["dep:serde", "serde/derive", "semver/serde", "dep:reqwest", "dep:sha2", "dep:tempfile", "dep:serde_json", "dep:flate2", "dep:tar", "dep:qt-artifacts", "dep:dirs"]
46+
qmake = ["dep:which"]
47+
qt_minimal = ["qt_version", "dep:serde", "serde/derive", "semver/serde", "dep:reqwest", "dep:sha2", "dep:tempfile", "dep:serde_json", "dep:flate2", "dep:tar", "dep:qt-artifacts", "dep:dirs"]
4748
qt_version = ["dep:qt-version"]
4849
serde = ["dep:serde"]
4950

crates/qt-build-utils/src/installation/qmake.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,20 @@ impl QtInstallationQMake {
7474
// Use the first non-errored installation
7575
// If there are no valid installations we display the last error
7676
.fold(None, |acc, qmake_path| {
77-
Some(acc.map_or_else(
78-
// Value is None so try to create installation
79-
|| QtInstallationQMake::try_from(PathBuf::from(qmake_path)),
80-
// Value is Some so pass through or create if Err
81-
|prev: anyhow::Result<Self>| {
82-
prev.or_else(|_|
77+
if let Ok(qmake_path) = which::which(qmake_path) {
78+
Some(acc.map_or_else(
79+
// Value is None so try to create installation
80+
|| QtInstallationQMake::try_from(qmake_path.clone()),
81+
// Value is Some so pass through or create if Err
82+
|prev: anyhow::Result<Self>| {
83+
prev.or_else(|_|
8384
// Value is Err so try to create installation
84-
QtInstallationQMake::try_from(PathBuf::from(qmake_path)))
85-
},
86-
))
85+
QtInstallationQMake::try_from(qmake_path.clone()))
86+
},
87+
))
88+
} else {
89+
None
90+
}
8791
})
8892
.unwrap_or_else(|| Err(QtBuildError::QtMissing.into()))
8993
}
@@ -114,6 +118,9 @@ impl TryFrom<PathBuf> for QtInstallationQMake {
114118
// Attempt to read the QT_VERSION from qmake
115119
let qmake_version = match Command::new(&qmake_path)
116120
.args(["-query", "QT_VERSION"])
121+
// Binaries should work without environment and this prevents
122+
// LD_LIBRARY_PATH from causing different Qt version clashes
123+
.env_clear()
117124
.output()
118125
{
119126
Err(e) if e.kind() == ErrorKind::NotFound => Err(QtBuildError::QtMissing),
@@ -207,6 +214,9 @@ impl QtInstallationQMake {
207214
String::from_utf8_lossy(
208215
&Command::new(&self.qmake_path)
209216
.args(["-query", var_name])
217+
// Binaries should work without environment and this prevents
218+
// LD_LIBRARY_PATH from causing different Qt version clashes
219+
.env_clear()
210220
.output()
211221
.unwrap()
212222
.stdout,
@@ -263,7 +273,12 @@ impl QtInstallationQMake {
263273
// Find the first valid executable path
264274
.find_map(|qmake_query_var| {
265275
let executable_path = PathBuf::from(self.qmake_query(qmake_query_var)).join(tool_name);
266-
let test_output = Command::new(&executable_path).args(["-help"]).output();
276+
let test_output = Command::new(&executable_path)
277+
.args(["-help"])
278+
// Binaries should work without environment and this prevents
279+
// LD_LIBRARY_PATH from causing different Qt version clashes
280+
.env_clear()
281+
.output();
267282
match test_output {
268283
Err(_err) => {
269284
failed_paths.push(executable_path);

crates/qt-build-utils/src/installation/qt_minimal/mod.rs

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod checksum;
88
mod download;
99
mod extract;
1010

11+
use std::collections::HashMap;
1112
use std::fs::DirEntry;
1213
use std::path::{Path, PathBuf};
1314

@@ -24,6 +25,8 @@ impl TryFrom<PathBuf> for QtInstallationQtMinimal {
2425
type Error = anyhow::Error;
2526

2627
fn try_from(path_qt: PathBuf) -> Result<Self, Self::Error> {
28+
println!("cargo::rerun-if-changed={}", path_qt.display());
29+
2730
// Verify that the expected folders exist
2831
for folder in ["bin", "include", "lib", "libexec"] {
2932
if !path_qt.join(folder).exists() {
@@ -70,29 +73,8 @@ impl TryFrom<semver::Version> for QtInstallationQtMinimal {
7073
let manifest: artifact::ParsedQtManifest =
7174
serde_json::from_str(qt_artifacts::QT_MANIFEST_JSON)?;
7275

73-
// Find artifacts matching Qt version
74-
//
75-
// Arch could be x86_64
76-
// OS could be linux
77-
// https://doc.rust-lang.org/cargo/appendix/glossary.html#target
78-
//
79-
// TODO: is there a better way to find the arch and os ?
80-
// and should this be configurable via env var overrides?
81-
let target = std::env::var("TARGET").expect("TARGET to be set");
82-
let target_parts: Vec<_> = target.split("-").collect();
83-
let arch = target_parts
84-
.first()
85-
.expect("TARGET to have a <arch><sub> component");
86-
let os = target_parts
87-
.get(2)
88-
.expect("TARGET to have a <sys> component");
89-
let artifacts: Vec<ParsedQtArtifact> = manifest
90-
.artifacts
91-
.into_iter()
92-
.filter(|artifact| {
93-
artifact.arch == *arch && artifact.os == *os && artifact.version == version
94-
})
95-
.collect();
76+
// Find artifacts for the Qt version
77+
let artifacts = Self::match_artifact_requirements(manifest.artifacts, &[version.clone()]);
9678

9779
// Find the first bin / include
9880
let artifact_bin = artifacts
@@ -110,8 +92,8 @@ impl TryFrom<semver::Version> for QtInstallationQtMinimal {
11092
"{}.{}.{}",
11193
version.major, version.minor, version.patch
11294
))
113-
.join(os)
114-
.join(arch);
95+
.join(&artifact_bin.os)
96+
.join(&artifact_bin.arch);
11597
artifact_bin.download_and_extract(&extract_target_dir);
11698
if artifact_bin != artifact_include {
11799
artifact_include.download_and_extract(&extract_target_dir);
@@ -174,6 +156,7 @@ impl QtInstallation for QtInstallationQtMinimal {
174156
impl QtInstallationQtMinimal {
175157
fn qt_minimal_root() -> PathBuf {
176158
// Check if a custom root has been set
159+
println!("cargo::rerun-if-env-changed=QT_MINIMAL_DOWNLOAD_ROOT");
177160
let path = if let Ok(root) = std::env::var("QT_MINIMAL_DOWNLOAD_ROOT") {
178161
PathBuf::from(root)
179162
} else {
@@ -183,14 +166,18 @@ impl QtInstallationQtMinimal {
183166
.join("qt_minimal_download")
184167
};
185168

186-
std::fs::create_dir_all(&path).expect("Could not create Qt minimal root path");
169+
// Only create when it doesn't exist to avoid file modifications
170+
if !path.exists() {
171+
std::fs::create_dir_all(&path).expect("Could not create Qt minimal root path");
172+
}
187173

188174
path
189175
}
190176

191177
/// Get a collection of the locally installed Qt artifacts
192178
pub(crate) fn local_artifacts() -> anyhow::Result<Vec<ParsedQtArtifact>> {
193179
let base_dir = Self::qt_minimal_root();
180+
println!("cargo::rerun-if-changed={}", base_dir.display());
194181

195182
// Expects folder structure like:
196183
// version/os/arch/qt/{bin, include}
@@ -199,17 +186,23 @@ impl QtInstallationQtMinimal {
199186

200187
// Iterate versions
201188
for version in list_dirs(&base_dir) {
189+
println!("cargo::rerun-if-changed={}", version.path().display());
190+
202191
let path = version;
203192
// TODO: Later skip unknown folders,
204193
// this will error if a directory exists which isn't a version number
205194
let semver = semver::Version::parse(path.file_name().to_str().unwrap())
206195
.expect("Could not parse semver from directory name");
207196

208197
for os in list_dirs(&path.path()) {
198+
println!("cargo::rerun-if-changed={}", os.path().display());
199+
209200
let path = os;
210201
let os = path.file_name().to_str().unwrap().to_string();
211202

212203
for arch in list_dirs(&path.path()) {
204+
println!("cargo::rerun-if-changed={}", arch.path().display());
205+
213206
let path = arch;
214207
let dir_entries = list_dirs(&path.path());
215208

@@ -219,6 +212,7 @@ impl QtInstallationQtMinimal {
219212
.filter(|dir| dir.file_name() == "qt")
220213
.last()
221214
.expect("Expected to find a Qt dir in this folder");
215+
println!("cargo::rerun-if-changed={}", qt_dir_path.path().display());
222216

223217
let qt_folders = list_dirs(&qt_dir_path.path());
224218
for dir in qt_folders {
@@ -248,6 +242,64 @@ impl QtInstallationQtMinimal {
248242

249243
Ok(artifacts)
250244
}
245+
246+
/// Find artifacts matching Qt version
247+
pub(crate) fn match_artifact_requirements(
248+
artifacts: Vec<ParsedQtArtifact>,
249+
versions: &[semver::Version],
250+
) -> Vec<ParsedQtArtifact> {
251+
// Arch could be x86_64
252+
// OS could be linux
253+
// https://doc.rust-lang.org/cargo/appendix/glossary.html#target
254+
//
255+
// TODO: is there a better way to find the arch and os ?
256+
// and should this be configurable via env var overrides?
257+
println!("cargo::rerun-if-env-changed=TARGET");
258+
let target = std::env::var("TARGET").expect("TARGET to be set");
259+
let target_parts: Vec<_> = target.split("-").collect();
260+
let arch = target_parts
261+
.first()
262+
.expect("TARGET to have a <arch><sub> component");
263+
let os = target_parts
264+
.get(2)
265+
.expect("TARGET to have a <sys> component");
266+
267+
artifacts
268+
.into_iter()
269+
.filter(|artifact| {
270+
artifact.arch == *arch && artifact.os == *os && versions.contains(&artifact.version)
271+
})
272+
.collect()
273+
}
274+
275+
/// Merge together artifacts with the same version
276+
/// so that we do not have bin/ and include/ split
277+
//
278+
// NOTE: later we may support bin and include folders being in different places
279+
pub(crate) fn group_artifacts(artifacts: Vec<ParsedQtArtifact>) -> Vec<ParsedQtArtifact> {
280+
artifacts.into_iter().fold(
281+
HashMap::<semver::Version, ParsedQtArtifact>::default(),
282+
|mut acc, mut artifact| {
283+
acc.entry(artifact.version.clone())
284+
.and_modify(|value| {
285+
if value.url == artifact.url {
286+
value.content.append(&mut artifact.content)
287+
} else {
288+
println!("cargo::warning=Found multiple minimal installations of the same version but different urls: {} and {}", value.url, artifact.url);
289+
}
290+
})
291+
.or_insert(artifact);
292+
acc
293+
},
294+
)
295+
.into_values()
296+
// Ensure that artifacts contain bin/ and include/
297+
.filter(|artifact| {
298+
artifact.content.contains(&"bin".to_string())
299+
&& artifact.content.contains(&"include".to_string())
300+
})
301+
.collect()
302+
}
251303
}
252304

253305
/// Get all valid directories in path bufs, ignoring errors

0 commit comments

Comments
 (0)