From 57e0b7898b6fa0beb9ac8e928e717ee70db957d7 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 14 Mar 2025 15:06:51 +0000 Subject: [PATCH 01/27] qt-build-utils: use u64 as major type this allows us to move to SemVer --- crates/qt-build-utils/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index bca04b71f..66324c690 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -58,9 +58,9 @@ pub enum QtBuildError { #[error("qmake version ({qmake_version}) does not match version specified by QT_VERSION_MAJOR ({qt_version_major})")] QtVersionMajorDoesNotMatch { /// The qmake version - qmake_version: u32, + qmake_version: u64, /// The Qt major version from `QT_VERSION_MAJOR` - qt_version_major: u32, + qt_version_major: u64, }, } @@ -296,7 +296,7 @@ impl QtBuild { .to_string(); let qmake_version = versions::SemVer::new(version_string).unwrap(); if let Ok(env_version) = env::var("QT_VERSION_MAJOR") { - let env_version = match env_version.trim().parse::<u32>() { + let env_version = match env_version.trim().parse::<u64>() { Err(e) if *e.kind() == std::num::IntErrorKind::Empty => { println!( "cargo::warning=QT_VERSION_MAJOR environment variable defined but empty" @@ -311,11 +311,11 @@ impl QtBuild { } Ok(int) => int, }; - if env_version == qmake_version.major { + if env_version == qmake_version.major as u64 { return Ok((candidate, qmake_version)); } else { return Err(QtBuildError::QtVersionMajorDoesNotMatch { - qmake_version: qmake_version.major, + qmake_version: qmake_version.major as u64, qt_version_major: env_version, }); } From 72bba130c10b50aa6162ecaef22995b95cf0d643 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 14 Mar 2025 15:07:43 +0000 Subject: [PATCH 02/27] qt-build-utils: add anyhow and semver crates for later --- Cargo.lock | 14 ++++++++++++++ crates/qt-build-utils/Cargo.toml | 2 ++ crates/qt-build-utils/src/lib.rs | 6 +++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44e39dc81..b17c246df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + [[package]] name = "async-channel" version = "1.9.0" @@ -1174,7 +1180,9 @@ dependencies = [ name = "qt-build-utils" version = "0.7.2" dependencies = [ + "anyhow", "cc", + "semver", "serde", "thiserror", "versions", @@ -1234,6 +1242,12 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.217" diff --git a/crates/qt-build-utils/Cargo.toml b/crates/qt-build-utils/Cargo.toml index acfc569e2..96e6521b2 100644 --- a/crates/qt-build-utils/Cargo.toml +++ b/crates/qt-build-utils/Cargo.toml @@ -14,7 +14,9 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow = "1.0" cc.workspace = true +semver = "1.0" serde = { workspace = true, optional = true } versions = "6.3" thiserror.workspace = true diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 66324c690..16e25e5e0 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -37,8 +37,8 @@ pub enum QtBuildError { QMakeSetQtMissing { /// The value of the qmake environment variable when the error occurred qmake_env_var: String, - /// The inner [QtBuildError] that occurred - error: Box<QtBuildError>, + /// The inner error that occurred + error: Box<anyhow::Error>, }, /// Qt was not found #[error("Could not find Qt")] @@ -344,7 +344,7 @@ impl QtBuild { Err(e) => { return Err(QtBuildError::QMakeSetQtMissing { qmake_env_var, - error: Box::new(e), + error: Box::new(e.into()), }) } } From 095156eb00bbae3140478335716b256d1a3a2f8c Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 14:55:23 +0100 Subject: [PATCH 03/27] qt-build-utils: add a QtTool enum for later --- crates/qt-build-utils/src/lib.rs | 3 +++ crates/qt-build-utils/src/tool.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 crates/qt-build-utils/src/tool.rs diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 16e25e5e0..7a0545d40 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -17,6 +17,9 @@ mod parse_cflags; +mod tool; +pub use tool::QtTool; + use std::{ env, fs::File, diff --git a/crates/qt-build-utils/src/tool.rs b/crates/qt-build-utils/src/tool.rs new file mode 100644 index 000000000..ce6adf0ac --- /dev/null +++ b/crates/qt-build-utils/src/tool.rs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +/// An enum representing known Qt tools +#[non_exhaustive] +pub enum QtTool { + /// Moc + Moc, + /// Rcc (Qt resources) + Rcc, + /// Qml cachegen + QmlCacheGen, + /// Qml Type Registrar + QmlTypeRegistrar, + // TODO: could add a Custom(&str) thing here +} + +impl QtTool { + pub(crate) fn binary_name(&self) -> &str { + match self { + Self::Moc => "moc", + Self::Rcc => "rcc", + Self::QmlCacheGen => "qmlcachegen", + Self::QmlTypeRegistrar => "qmltyperegistrar", + } + } +} From dffb2420ce2b8f986b3e3f8ebd24017758f929a6 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 14 Mar 2025 15:08:46 +0000 Subject: [PATCH 04/27] qt-build-utils: add QtInstallation trait --- crates/qt-build-utils/src/installation/mod.rs | 39 +++++++++++++++++++ crates/qt-build-utils/src/lib.rs | 3 ++ 2 files changed, 42 insertions(+) create mode 100644 crates/qt-build-utils/src/installation/mod.rs diff --git a/crates/qt-build-utils/src/installation/mod.rs b/crates/qt-build-utils/src/installation/mod.rs new file mode 100644 index 000000000..b786c8ade --- /dev/null +++ b/crates/qt-build-utils/src/installation/mod.rs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use semver::Version; +use std::path::PathBuf; + +use crate::QtTool; + +/// A Qt Installation that can be used by cxx-qt-build to run Qt related tasks +/// +/// Note that it is the responsbility of the QtInstallation implementation +/// to print any cargo::rerun-if-changed lines +pub trait QtInstallation { + /// Return the include paths for Qt, including Qt module subdirectories. + /// + /// This is intended to be passed to whichever tool you are using to invoke the C++ compiler. + fn include_paths(&self, qt_modules: &[String]) -> Vec<PathBuf>; + /// Configure the given cc::Build and cargo to link to the given Qt modules + /// + // TODO: should we hand in a cc::Build or should we instead return a struct + // with details of the rustc-link-lib / search paths ? and then have the + // calling function apply those and any flags to the cc::Build? + // eg return the following? + // + // pub struct LinkArgs { + // builder_flag_if_supported: Vec<String>, + // builder_object: Vec<String>, + // rustc_link_arg: Vec<String>, + // rustc_link_lib: Vec<String>, + // rustc_link_search: Vec<String>, + // } + fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]); + /// Find the path to a given Qt tool for the Qt installation + fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf>; + /// Version of the detected Qt installation + fn version(&self) -> Version; +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 7a0545d40..27b6ed0b8 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -15,6 +15,9 @@ #![allow(clippy::too_many_arguments)] +mod installation; +pub use installation::QtInstallation; + mod parse_cflags; mod tool; From 9cc3405e87340d8e3f860e106868c0db86096cef Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 14 Mar 2025 15:09:05 +0000 Subject: [PATCH 05/27] qt-build-utils: add qmake implementation of QtInstallation --- crates/qt-build-utils/Cargo.toml | 3 + crates/qt-build-utils/src/installation/mod.rs | 3 + .../qt-build-utils/src/installation/qmake.rs | 183 ++++++++++++++++++ crates/qt-build-utils/src/lib.rs | 3 + 4 files changed, 192 insertions(+) create mode 100644 crates/qt-build-utils/src/installation/qmake.rs diff --git a/crates/qt-build-utils/Cargo.toml b/crates/qt-build-utils/Cargo.toml index 96e6521b2..70c6ddd2d 100644 --- a/crates/qt-build-utils/Cargo.toml +++ b/crates/qt-build-utils/Cargo.toml @@ -22,6 +22,8 @@ versions = "6.3" thiserror.workspace = true [features] +# TODO: should we default to qmake or let downstream crates specify, such as cxx-qt-build +default = ["qmake"] # When Cargo links an executable, whether a bin crate or test executable, # and Qt 6 is linked statically, this feature must be enabled to link # unarchived .o files with static symbols that Qt ships (for example @@ -33,6 +35,7 @@ thiserror.workspace = true # # When linking Qt dynamically, this makes no difference. link_qt_object_files = [] +qmake = [] serde = ["dep:serde"] [lints] diff --git a/crates/qt-build-utils/src/installation/mod.rs b/crates/qt-build-utils/src/installation/mod.rs index b786c8ade..3847024f8 100644 --- a/crates/qt-build-utils/src/installation/mod.rs +++ b/crates/qt-build-utils/src/installation/mod.rs @@ -3,6 +3,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +#[cfg(feature = "qmake")] +pub(crate) mod qmake; + use semver::Version; use std::path::PathBuf; diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs new file mode 100644 index 000000000..f39770f6c --- /dev/null +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use semver::Version; +use std::{env, io::ErrorKind, path::PathBuf, process::Command}; + +use crate::{QtBuildError, QtInstallation, QtTool}; + +/// TODO +pub struct QtInstallationQMake { + qmake_path: PathBuf, + qmake_version: Version, +} + +impl QtInstallationQMake { + /// TODO + pub fn new() -> anyhow::Result<Self> { + // Try the QMAKE variable first + println!("cargo::rerun-if-env-changed=QMAKE"); + if let Ok(qmake_env_var) = env::var("QMAKE") { + return QtInstallationQMake::try_from(PathBuf::from(&qmake_env_var)).map_err(|err| { + QtBuildError::QMakeSetQtMissing { + qmake_env_var, + error: err.into(), + } + .into() + }); + } + + // Try variable candidates within the patch + ["qmake6", "qmake-qt5", "qmake"] + .iter() + // Use the first non-errored installation + // If there are no valid installations we display the last error + .fold(None, |acc, qmake_path| { + Some(acc.map_or_else( + // Value is None so try to create installation + || QtInstallationQMake::try_from(PathBuf::from(qmake_path)), + // Value is Some so pass through or create if Err + |prev: anyhow::Result<Self>| { + prev.or_else(|_| + // Value is Err so try to create installation + QtInstallationQMake::try_from(PathBuf::from(qmake_path))) + }, + )) + }) + .unwrap_or_else(|| Err(QtBuildError::QtMissing.into())) + } +} + +impl TryFrom<PathBuf> for QtInstallationQMake { + type Error = anyhow::Error; + + fn try_from(qmake_path: PathBuf) -> anyhow::Result<Self> { + // Attempt to read the QT_VERSION from qmake + let qmake_version = match Command::new(&qmake_path) + .args(["-query", "QT_VERSION"]) + .output() + { + Err(e) if e.kind() == ErrorKind::NotFound => Err(QtBuildError::QtMissing), + Err(e) => Err(QtBuildError::QmakeFailed(e)), + Ok(output) if !output.status.success() => Err(QtBuildError::QtMissing), + Ok(output) => Ok(Version::parse( + String::from_utf8_lossy(&output.stdout).trim(), + )?), + }?; + + // Check QT_VERSION_MAJOR is the same as the qmake version + println!("cargo::rerun-if-env-changed=QT_VERSION_MAJOR"); + if let Ok(env_qt_version_major) = env::var("QT_VERSION_MAJOR") { + // Parse to an integer + let env_qt_version_major = env_qt_version_major.trim().parse::<u64>().map_err(|e| { + QtBuildError::QtVersionMajorInvalid { + qt_version_major_env_var: env_qt_version_major, + source: e, + } + })?; + + // Ensure the version major is the same + if qmake_version.major != env_qt_version_major { + return Err(QtBuildError::QtVersionMajorDoesNotMatch { + qmake_version: qmake_version.major, + qt_version_major: env_qt_version_major, + } + .into()); + } + } + + Ok(Self { + qmake_path, + qmake_version, + }) + } +} + +impl QtInstallation for QtInstallationQMake { + fn include_paths(&self, _qt_modules: &[String]) -> Vec<PathBuf> { + todo!() + } + + fn link_modules(&self, _builder: &mut cc::Build, _qt_modules: &[String]) { + todo!() + } + + fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf> { + self.try_qmake_find_tool(tool.binary_name()) + } + + fn version(&self) -> semver::Version { + self.qmake_version.clone() + } +} + +impl QtInstallationQMake { + fn qmake_query(&self, var_name: &str) -> String { + String::from_utf8_lossy( + &Command::new(&self.qmake_path) + .args(["-query", var_name]) + .output() + .unwrap() + .stdout, + ) + .trim() + .to_string() + } + + fn try_qmake_find_tool(&self, tool_name: &str) -> Option<PathBuf> { + // "qmake -query" exposes a list of paths that describe where Qt executables and libraries + // are located, as well as where new executables & libraries should be installed to. + // We can use these variables to find any Qt tool. + // + // The order is important here. + // First, we check the _HOST_ variables. + // In cross-compilation contexts, these variables should point to the host toolchain used + // for building. The _INSTALL_ directories describe where to install new binaries to + // (i.e. the target directories). + // We still use the _INSTALL_ paths as fallback. + // + // The _LIBEXECS variables point to the executable Qt-internal tools (i.e. moc and + // friends), whilst _BINS point to the developer-facing executables (qdoc, qmake, etc.). + // As we mostly use the Qt-internal tools in this library, check _LIBEXECS first. + // + // Furthermore, in some contexts these variables include a `/get` variant. + // This is important for contexts where qmake and the Qt build tools do not have a static + // location, but are moved around during building. + // This notably happens with yocto builds. + // For each package, yocto builds a `sysroot` folder for both the host machine, as well + // as the target. This is done to keep package builds reproducable & separate. + // As a result the qmake executable is copied into each host sysroot for building. + // + // In this case the variables compiled into qmake still point to the paths relative + // from the host sysroot (e.g. /usr/bin). + // The /get variant in comparison will "get" the right full path from the current environment. + // Therefore prefer to use the `/get` variant when available. + // See: https://github.com/KDAB/cxx-qt/pull/430 + // + // To check & debug all variables available on your system, simply run: + // + // qmake -query + [ + "QT_HOST_LIBEXECS/get", + "QT_HOST_LIBEXECS", + "QT_HOST_BINS/get", + "QT_HOST_BINS", + "QT_INSTALL_LIBEXECS/get", + "QT_INSTALL_LIBEXECS", + "QT_INSTALL_BINS/get", + "QT_INSTALL_BINS", + ] + .iter() + // Find the first valid executable path + .find_map(|qmake_query_var| { + let executable_path = PathBuf::from(self.qmake_query(qmake_query_var)).join(tool_name); + Command::new(&executable_path) + .args(["-help"]) + .output() + .map(|_| executable_path) + .ok() + }) + } +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 27b6ed0b8..3f17f3f43 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -18,6 +18,9 @@ mod installation; pub use installation::QtInstallation; +#[cfg(feature = "qmake")] +pub use installation::qmake::QtInstallationQMake; + mod parse_cflags; mod tool; From dd7f0d1e9baec3fcc417c8ad979262ea3f3b7ec3 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 14 Mar 2025 15:59:05 +0000 Subject: [PATCH 06/27] qt-build-utils: create a QtInstallation but don't use it This is just to test what we can do still with the old API --- crates/qt-build-utils/src/lib.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 3f17f3f43..d8cfaaee3 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -236,6 +236,7 @@ pub struct QmlModuleRegistrationFiles { /// let qtbuild = qt_build_utils::QtBuild::new(qt_modules).expect("Could not find Qt installation"); /// ``` pub struct QtBuild { + qt_installation: Box<dyn QtInstallation>, version: SemVer, qmake_executable: String, moc_executable: Option<String>, @@ -284,10 +285,16 @@ impl QtBuild { /// WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} /// ) /// ``` - pub fn new(mut qt_modules: Vec<String>) -> Result<Self, QtBuildError> { + pub fn new(mut qt_modules: Vec<String>) -> anyhow::Result<Self> { if qt_modules.is_empty() { qt_modules.push("Core".to_string()); } + + #[cfg(feature = "qmake")] + let qt_installation = Box::new(QtInstallationQMake::new()?); + #[cfg(not(feature = "qmake"))] + unsupported!("Only qmake feature is supported"); + println!("cargo::rerun-if-env-changed=QMAKE"); println!("cargo::rerun-if-env-changed=QT_VERSION_MAJOR"); fn verify_candidate(candidate: &str) -> Result<(&str, versions::SemVer), QtBuildError> { @@ -341,6 +348,7 @@ impl QtBuild { match verify_candidate(qmake_env_var.trim()) { Ok((executable_name, version)) => { return Ok(Self { + qt_installation, qmake_executable: executable_name.to_string(), moc_executable: None, qmltyperegistrar_executable: None, @@ -354,7 +362,8 @@ impl QtBuild { return Err(QtBuildError::QMakeSetQtMissing { qmake_env_var, error: Box::new(e.into()), - }) + } + .into()) } } } @@ -365,6 +374,7 @@ impl QtBuild { match verify_candidate(executable_name) { Ok((executable_name, version)) => { return Ok(Self { + qt_installation, qmake_executable: executable_name.to_string(), moc_executable: None, qmltyperegistrar_executable: None, @@ -386,17 +396,18 @@ impl QtBuild { return Err(QtBuildError::QtVersionMajorDoesNotMatch { qmake_version, qt_version_major, - }); + } + .into()); } eprintln!("Candidate qmake executable `{executable_name}` is for Qt{qmake_version} but QT_VERSION_MAJOR environment variable specified as {qt_version_major}. Trying next candidate executable name `{}`...", candidate_executable_names[index + 1]); continue; } Err(QtBuildError::QtMissing) => continue, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), } } - Err(QtBuildError::QtMissing) + Err(QtBuildError::QtMissing.into()) } /// Get the output of running `qmake -query var_name` @@ -623,6 +634,7 @@ impl QtBuild { /// Version of the detected Qt installation pub fn version(&self) -> &SemVer { + let _ = self.qt_installation.version(); &self.version } From 53a39b6d3f2f2cdfcd40821c66629e1c8c86b617 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 14:27:30 +0100 Subject: [PATCH 07/27] cxx-qt-build: use version from QtInstallation --- Cargo.lock | 50 +------------------------------- Cargo.toml | 1 + crates/cxx-qt-build/Cargo.toml | 2 +- crates/cxx-qt-build/src/lib.rs | 4 +-- crates/qt-build-utils/Cargo.toml | 3 +- crates/qt-build-utils/src/lib.rs | 48 ++++++++++++++---------------- 6 files changed, 28 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b17c246df..8b013fc8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,9 +394,9 @@ dependencies = [ "proc-macro2", "qt-build-utils", "quote", + "semver", "serde", "serde_json", - "version_check", ] [[package]] @@ -537,12 +537,6 @@ dependencies = [ "syn", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "errno" version = "0.3.10" @@ -935,15 +929,6 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.14" @@ -1020,22 +1005,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -1185,7 +1154,6 @@ dependencies = [ "semver", "serde", "thiserror", - "versions", ] [[package]] @@ -1491,22 +1459,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "versions" -version = "6.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25d498b63d1fdb376b4250f39ab3a5ee8d103957346abacd911e2d8b612c139" -dependencies = [ - "itertools", - "nom", -] - [[package]] name = "wasm-bindgen" version = "0.2.100" diff --git a/Cargo.toml b/Cargo.toml index 14d391692..90150e22f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ cxx-gen = "0.7.121" proc-macro2 = "1.0" syn = { version = "2.0", features = ["extra-traits", "full"] } quote = "1.0" +semver = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" diff --git a/crates/cxx-qt-build/Cargo.toml b/crates/cxx-qt-build/Cargo.toml index d58970138..5f4afbbb2 100644 --- a/crates/cxx-qt-build/Cargo.toml +++ b/crates/cxx-qt-build/Cargo.toml @@ -21,9 +21,9 @@ proc-macro2.workspace = true quote.workspace = true qt-build-utils = { workspace = true, features = ["serde"] } codespan-reporting = "0.11" -version_check = "0.9" serde.workspace = true serde_json = "1.0" +semver.workspace = true [features] link_qt_object_files = ["qt-build-utils/link_qt_object_files"] diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 6522a27c7..19220880e 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -32,8 +32,8 @@ use qml_modules::OwningQmlModule; pub use qml_modules::QmlModule; pub use qt_build_utils::MocArguments; -use qt_build_utils::SemVer; use quote::ToTokens; +use semver::Version; use std::{ collections::HashSet, env, @@ -600,7 +600,7 @@ impl CxxQtBuilder { } } - fn define_qt_version_cfg_variables(version: &SemVer) { + fn define_qt_version_cfg_variables(version: Version) { // Allow for Qt 5 or Qt 6 as valid values CxxQtBuilder::define_cfg_check_variable( "cxxqt_qt_version_major".to_owned(), diff --git a/crates/qt-build-utils/Cargo.toml b/crates/qt-build-utils/Cargo.toml index 70c6ddd2d..b5fb673ae 100644 --- a/crates/qt-build-utils/Cargo.toml +++ b/crates/qt-build-utils/Cargo.toml @@ -16,9 +16,8 @@ rust-version.workspace = true [dependencies] anyhow = "1.0" cc.workspace = true -semver = "1.0" +semver.workspace = true serde = { workspace = true, optional = true } -versions = "6.3" thiserror.workspace = true [features] diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index d8cfaaee3..3b753395b 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -34,8 +34,7 @@ use std::{ process::Command, }; -pub use versions::SemVer; - +use semver::Version; use thiserror::Error; #[derive(Error, Debug)] @@ -237,7 +236,6 @@ pub struct QmlModuleRegistrationFiles { /// ``` pub struct QtBuild { qt_installation: Box<dyn QtInstallation>, - version: SemVer, qmake_executable: String, moc_executable: Option<String>, qmltyperegistrar_executable: Option<String>, @@ -297,7 +295,7 @@ impl QtBuild { println!("cargo::rerun-if-env-changed=QMAKE"); println!("cargo::rerun-if-env-changed=QT_VERSION_MAJOR"); - fn verify_candidate(candidate: &str) -> Result<(&str, versions::SemVer), QtBuildError> { + fn verify_candidate(candidate: &str) -> Result<&str, QtBuildError> { match Command::new(candidate) .args(["-query", "QT_VERSION"]) .output() @@ -310,14 +308,14 @@ impl QtBuild { .unwrap() .trim() .to_string(); - let qmake_version = versions::SemVer::new(version_string).unwrap(); + let qmake_version = Version::parse(&version_string).unwrap(); if let Ok(env_version) = env::var("QT_VERSION_MAJOR") { let env_version = match env_version.trim().parse::<u64>() { Err(e) if *e.kind() == std::num::IntErrorKind::Empty => { println!( "cargo::warning=QT_VERSION_MAJOR environment variable defined but empty" ); - return Ok((candidate, qmake_version)); + return Ok(candidate); } Err(e) => { return Err(QtBuildError::QtVersionMajorInvalid { @@ -328,7 +326,7 @@ impl QtBuild { Ok(int) => int, }; if env_version == qmake_version.major as u64 { - return Ok((candidate, qmake_version)); + return Ok(candidate); } else { return Err(QtBuildError::QtVersionMajorDoesNotMatch { qmake_version: qmake_version.major as u64, @@ -336,7 +334,7 @@ impl QtBuild { }); } } - Ok((candidate, qmake_version)) + Ok(candidate) } else { Err(QtBuildError::QtMissing) } @@ -346,7 +344,7 @@ impl QtBuild { if let Ok(qmake_env_var) = env::var("QMAKE") { match verify_candidate(qmake_env_var.trim()) { - Ok((executable_name, version)) => { + Ok(executable_name) => { return Ok(Self { qt_installation, qmake_executable: executable_name.to_string(), @@ -354,7 +352,6 @@ impl QtBuild { qmltyperegistrar_executable: None, qmlcachegen_executable: None, rcc_executable: None, - version, qt_modules, }); } @@ -372,7 +369,7 @@ impl QtBuild { let candidate_executable_names = ["qmake6", "qmake-qt5", "qmake"]; for (index, executable_name) in candidate_executable_names.iter().enumerate() { match verify_candidate(executable_name) { - Ok((executable_name, version)) => { + Ok(executable_name) => { return Ok(Self { qt_installation, qmake_executable: executable_name.to_string(), @@ -380,7 +377,6 @@ impl QtBuild { qmltyperegistrar_executable: None, qmlcachegen_executable: None, rcc_executable: None, - version, qt_modules, }); } @@ -464,7 +460,7 @@ impl QtBuild { &self, lib_path: &str, prefix: &str, - version_major: u32, + version_major: u64, qt_module: &str, ) -> String { for arch in ["", "_arm64-v8a", "_armeabi-v7a", "_x86", "_x86_64"] { @@ -551,13 +547,18 @@ impl QtBuild { ) } else { ( - format!("Qt{}{qt_module}", self.version.major), - self.find_qt_module_prl(&lib_path, prefix, self.version.major, qt_module), + format!("Qt{}{qt_module}", self.qt_installation.version().major), + self.find_qt_module_prl( + &lib_path, + prefix, + self.qt_installation.version().major, + qt_module, + ), ) }; self.cargo_link_qt_library( - &format!("Qt{}{qt_module}", self.version.major), + &format!("Qt{}{qt_module}", self.qt_installation.version().major), &prefix_path, &lib_path, &link_lib, @@ -633,9 +634,8 @@ impl QtBuild { } /// Version of the detected Qt installation - pub fn version(&self) -> &SemVer { - let _ = self.qt_installation.version(); - &self.version + pub fn version(&self) -> Version { + self.qt_installation.version() } /// Lazy load the path of a Qt executable tool @@ -780,7 +780,7 @@ impl QtBuild { ); } // qmlcachegen has a different CLI in Qt 5, so only support Qt >= 6 - if self.qmlcachegen_executable.is_none() && self.version.major >= 6 { + if self.qmlcachegen_executable.is_none() && self.qt_installation.version().major >= 6 { if let Ok(qmlcachegen_executable) = self.get_qt_tool("qmlcachegen") { self.qmlcachegen_executable = Some(qmlcachegen_executable); } @@ -1127,12 +1127,8 @@ Q_IMPORT_PLUGIN({plugin_class_name}); ); } - let qt_6_5 = SemVer { - major: 6, - minor: 5, - ..SemVer::default() - }; - let init_header = if self.version >= qt_6_5 { + let qt_6_5 = Version::new(6, 5, 0); + let init_header = if self.qt_installation.version() >= qt_6_5 { // With Qt6.5 the Q_INIT_RESOURCE macro is in the QtResource header "QtCore/QtResource" } else { From e188bd51247205b97eeaba0c348e7e4b5e22fc64 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 15:12:02 +0100 Subject: [PATCH 08/27] qt-build-utils: ask QtInstallation for location of Qt tools --- crates/qt-build-utils/src/lib.rs | 126 ++++++++----------------------- 1 file changed, 31 insertions(+), 95 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 3b753395b..d9b5217a7 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -237,10 +237,6 @@ pub struct QmlModuleRegistrationFiles { pub struct QtBuild { qt_installation: Box<dyn QtInstallation>, qmake_executable: String, - moc_executable: Option<String>, - qmltyperegistrar_executable: Option<String>, - qmlcachegen_executable: Option<String>, - rcc_executable: Option<String>, qt_modules: Vec<String>, } @@ -348,10 +344,6 @@ impl QtBuild { return Ok(Self { qt_installation, qmake_executable: executable_name.to_string(), - moc_executable: None, - qmltyperegistrar_executable: None, - qmlcachegen_executable: None, - rcc_executable: None, qt_modules, }); } @@ -373,10 +365,6 @@ impl QtBuild { return Ok(Self { qt_installation, qmake_executable: executable_name.to_string(), - moc_executable: None, - qmltyperegistrar_executable: None, - qmlcachegen_executable: None, - rcc_executable: None, qt_modules, }); } @@ -638,70 +626,15 @@ impl QtBuild { self.qt_installation.version() } - /// Lazy load the path of a Qt executable tool - /// Skip doing this in the constructor because not every user of this crate will use each tool - fn get_qt_tool(&self, tool_name: &str) -> Result<String, ()> { - // "qmake -query" exposes a list of paths that describe where Qt executables and libraries - // are located, as well as where new executables & libraries should be installed to. - // We can use these variables to find any Qt tool. - // - // The order is important here. - // First, we check the _HOST_ variables. - // In cross-compilation contexts, these variables should point to the host toolchain used - // for building. The _INSTALL_ directories describe where to install new binaries to - // (i.e. the target directories). - // We still use the _INSTALL_ paths as fallback. - // - // The _LIBEXECS variables point to the executable Qt-internal tools (i.e. moc and - // friends), whilst _BINS point to the developer-facing executables (qdoc, qmake, etc.). - // As we mostly use the Qt-internal tools in this library, check _LIBEXECS first. - // - // Furthermore, in some contexts these variables include a `/get` variant. - // This is important for contexts where qmake and the Qt build tools do not have a static - // location, but are moved around during building. - // This notably happens with yocto builds. - // For each package, yocto builds a `sysroot` folder for both the host machine, as well - // as the target. This is done to keep package builds reproducable & separate. - // As a result the qmake executable is copied into each host sysroot for building. - // - // In this case the variables compiled into qmake still point to the paths relative - // from the host sysroot (e.g. /usr/bin). - // The /get variant in comparison will "get" the right full path from the current environment. - // Therefore prefer to use the `/get` variant when available. - // See: https://github.com/KDAB/cxx-qt/pull/430 - // - // To check & debug all variables available on your system, simply run: - // - // qmake -query - // - for qmake_query_var in [ - "QT_HOST_LIBEXECS/get", - "QT_HOST_LIBEXECS", - "QT_HOST_BINS/get", - "QT_HOST_BINS", - "QT_INSTALL_LIBEXECS/get", - "QT_INSTALL_LIBEXECS", - "QT_INSTALL_BINS/get", - "QT_INSTALL_BINS", - ] { - let executable_path = format!("{}/{tool_name}", self.qmake_query(qmake_query_var)); - match Command::new(&executable_path).args(["-help"]).output() { - Ok(_) => return Ok(executable_path), - Err(_) => continue, - } - } - Err(()) - } - /// Run moc on a C++ header file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). /// The return value contains the path to the generated C++ file, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file), /// as well as the path to the generated metatypes.json file, which can be passed to [register_qml_module](Self::register_qml_module). /// pub fn moc(&mut self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts { - if self.moc_executable.is_none() { - self.moc_executable = Some(self.get_qt_tool("moc").expect("Could not find moc")); - } - + let moc_executable = self + .qt_installation + .try_find_tool(QtTool::Moc) + .expect("Could not find moc"); let input_path = input_file.as_ref(); // Put all the moc files into one place, this can then be added to the include path @@ -727,7 +660,7 @@ impl QtBuild { include_args.push(format!("-I{}", include_path.display())); } - let mut cmd = Command::new(self.moc_executable.as_ref().unwrap()); + let mut cmd = Command::new(moc_executable); if let Some(uri) = arguments.uri { cmd.arg(format!("-Muri={uri}")); @@ -773,18 +706,20 @@ impl QtBuild { qml_files: &[impl AsRef<Path>], qrc_files: &[impl AsRef<Path>], ) -> QmlModuleRegistrationFiles { - if self.qmltyperegistrar_executable.is_none() { - self.qmltyperegistrar_executable = Some( - self.get_qt_tool("qmltyperegistrar") - .expect("Could not find qmltyperegistrar"), - ); - } + let qmltyperegistrar_executable = self + .qt_installation + .try_find_tool(QtTool::QmlTypeRegistrar) + .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.qt_installation.version().major >= 6 { - if let Ok(qmlcachegen_executable) = self.get_qt_tool("qmlcachegen") { - self.qmlcachegen_executable = Some(qmlcachegen_executable); - } - } + let qmlcachegen_executable = if self.qt_installation.version().major >= 6 { + Some( + self.qt_installation + .try_find_tool(QtTool::QmlCacheGen) + .expect("Could not find qmlcachegen"), + ) + } else { + None + }; let qml_uri_dirs = uri.replace('.', "/"); @@ -860,7 +795,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ // qmlcachegen needs to be run once for each .qml file with --resource-path, // then once for the module with --resource-name. let mut qmlcachegen_file_paths = Vec::new(); - if let Some(qmlcachegen_executable) = &self.qmlcachegen_executable { + if let Some(qmlcachegen_executable) = &qmlcachegen_executable { let qmlcachegen_dir = qt_build_utils_dir.join("qmlcachegen").join(&qml_uri_dirs); std::fs::create_dir_all(&qmlcachegen_dir) .expect("Could not create qmlcachegen directory for QML module"); @@ -978,7 +913,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ qmltyperegistrar_output_path.to_string_lossy().to_string(), ]; args.extend(metatypes_json); - let cmd = Command::new(self.qmltyperegistrar_executable.as_ref().unwrap()) + let cmd = Command::new(qmltyperegistrar_executable) .args(args) .output() .unwrap_or_else(|_| panic!("qmltyperegistrar failed for {uri}")); @@ -1009,7 +944,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), ); - if !qml_files.is_empty() && self.qmlcachegen_executable.is_some() { + if !qml_files.is_empty() && qmlcachegen_executable.is_some() { generate_usage( "int", &format!("qInitResources_qmlcache_{qml_uri_underscores}"), @@ -1088,10 +1023,10 @@ Q_IMPORT_PLUGIN({plugin_class_name}); /// the `+whole-archive` flag is used, or the initializer function is called by the /// application. pub fn qrc(&mut self, input_file: &impl AsRef<Path>) -> Initializer { - if self.rcc_executable.is_none() { - self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc")); - } - + let rcc_executable = self + .qt_installation + .try_find_tool(QtTool::Rcc) + .expect("Could not find rcc"); let input_path = input_file.as_ref(); let output_folder = PathBuf::from(&format!( "{}/qt-build-utils/qrc", @@ -1108,7 +1043,7 @@ Q_IMPORT_PLUGIN({plugin_class_name}); .to_string_lossy() .replace('.', "_"); - let cmd = Command::new(self.rcc_executable.as_ref().unwrap()) + let cmd = Command::new(rcc_executable) .args([ input_path.to_str().unwrap(), "-o", @@ -1143,13 +1078,14 @@ Q_IMPORT_PLUGIN({plugin_class_name}); /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and return the paths of the sources pub fn qrc_list(&mut self, input_file: &impl AsRef<Path>) -> Vec<PathBuf> { - if self.rcc_executable.is_none() { - self.rcc_executable = Some(self.get_qt_tool("rcc").expect("Could not find rcc")); - } + let rcc_executable = self + .qt_installation + .try_find_tool(QtTool::Rcc) + .expect("Could not find rcc"); // Add the qrc file contents to the cargo rerun list let input_path = input_file.as_ref(); - let cmd_list = Command::new(self.rcc_executable.as_ref().unwrap()) + let cmd_list = Command::new(rcc_executable) .args(["--list", input_path.to_str().unwrap()]) .output() .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display())); From a4effb304c4bca6aafca98353e2bd5d1c56bed9a Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 15:25:38 +0100 Subject: [PATCH 09/27] qt-build-utils: split out common is_*_target --- crates/qt-build-utils/src/lib.rs | 21 ++++++--------------- crates/qt-build-utils/src/utils.rs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 crates/qt-build-utils/src/utils.rs diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index d9b5217a7..4329f8d12 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -26,6 +26,8 @@ mod parse_cflags; mod tool; pub use tool::QtTool; +mod utils; + use std::{ env, fs::File, @@ -76,13 +78,6 @@ fn command_help_output(command: &str) -> std::io::Result<std::process::Output> { Command::new(command).args(["--help"]).output() } -/// Whether apple is the current target -fn is_apple_target() -> bool { - env::var("TARGET") - .map(|target| target.contains("apple")) - .unwrap_or_else(|_| false) -} - /// Linking executables (including tests) with Cargo that link to Qt fails to link with GNU ld.bfd, /// which is the default on most Linux distributions, so use GNU ld.gold, lld, or mold instead. /// If you are using a C++ build system such as CMake to do the final link of the executable, you do @@ -496,7 +491,7 @@ impl QtBuild { // // Note that this adds the framework path which allows for // includes such as <QtCore/QObject> to be resolved correctly - if is_apple_target() { + if utils::is_apple_target() { println!("cargo::rustc-link-search=framework={lib_path}"); // Ensure that any framework paths are set to -F @@ -522,7 +517,7 @@ impl QtBuild { }; for qt_module in &self.qt_modules { - let framework = if is_apple_target() { + let framework = if utils::is_apple_target() { Path::new(&format!("{lib_path}/Qt{qt_module}.framework")).exists() } else { false @@ -555,11 +550,7 @@ impl QtBuild { ); } - let emscripten_targeted = match env::var("CARGO_CFG_TARGET_OS") { - Ok(val) => val == "emscripten", - Err(_) => false, - }; - if emscripten_targeted { + if utils::is_emscripten_target() { let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS")); println!("cargo::rustc-link-search={platforms_path}"); self.cargo_link_qt_library( @@ -605,7 +596,7 @@ impl QtBuild { // Ensure that we add any framework's headers path let header_path = format!("{lib_path}/Qt{qt_module}.framework/Headers"); - if is_apple_target() && Path::new(&header_path).exists() { + if utils::is_apple_target() && Path::new(&header_path).exists() { paths.push(header_path); } } diff --git a/crates/qt-build-utils/src/utils.rs b/crates/qt-build-utils/src/utils.rs new file mode 100644 index 000000000..347846276 --- /dev/null +++ b/crates/qt-build-utils/src/utils.rs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +/// Whether apple is the current target +pub(crate) fn is_apple_target() -> bool { + std::env::var("TARGET") + .map(|target| target.contains("apple")) + .unwrap_or_else(|_| false) +} + +/// Whether emscripten is the current target +pub(crate) fn is_emscripten_target() -> bool { + std::env::var("CARGO_CFG_TARGET_OS") == Ok("emscripten".to_owned()) +} From 6f3770c9376e9d8ec84ce62128c4b176c2927e90 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 15:38:34 +0100 Subject: [PATCH 10/27] qt-build-utils: move linking of Qt modules to Qt Installation --- .../qt-build-utils/src/installation/qmake.rs | 184 +++++++++++++++++- crates/qt-build-utils/src/lib.rs | 178 +---------------- 2 files changed, 181 insertions(+), 181 deletions(-) diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs index f39770f6c..6e8cbc2da 100644 --- a/crates/qt-build-utils/src/installation/qmake.rs +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -4,9 +4,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use semver::Version; -use std::{env, io::ErrorKind, path::PathBuf, process::Command}; +use std::{ + env, + io::ErrorKind, + path::{Path, PathBuf}, + process::Command, +}; -use crate::{QtBuildError, QtInstallation, QtTool}; +use crate::{parse_cflags, utils, QtBuildError, QtInstallation, QtTool}; /// TODO pub struct QtInstallationQMake { @@ -100,8 +105,90 @@ impl QtInstallation for QtInstallationQMake { todo!() } - fn link_modules(&self, _builder: &mut cc::Build, _qt_modules: &[String]) { - todo!() + fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]) { + let prefix_path = self.qmake_query("QT_INSTALL_PREFIX"); + let lib_path = self.qmake_query("QT_INSTALL_LIBS"); + println!("cargo::rustc-link-search={lib_path}"); + + let target = env::var("TARGET"); + + // Add the QT_INSTALL_LIBS as a framework link search path as well + // + // Note that leaving the kind empty should default to all, + // but this doesn't appear to find frameworks in all situations + // https://github.com/KDAB/cxx-qt/issues/885 + // + // Note this doesn't have an adverse affect running all the time + // as it appears that all rustc-link-search are added + // + // Note that this adds the framework path which allows for + // includes such as <QtCore/QObject> to be resolved correctly + if utils::is_apple_target() { + println!("cargo::rustc-link-search=framework={lib_path}"); + + // Ensure that any framework paths are set to -F + for framework_path in self.qmake_framework_paths() { + builder.flag_if_supported(format!("-F{}", framework_path.display())); + // Also set the -rpath otherwise frameworks can not be found at runtime + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{}", + framework_path.display() + ); + } + } + + let prefix = match &target { + Ok(target) => { + if target.contains("windows") { + "" + } else { + "lib" + } + } + Err(_) => "lib", + }; + + for qt_module in qt_modules { + let framework = if utils::is_apple_target() { + Path::new(&format!("{lib_path}/Qt{qt_module}.framework")).exists() + } else { + false + }; + + let (link_lib, prl_path) = if framework { + ( + format!("framework=Qt{qt_module}"), + format!("{lib_path}/Qt{qt_module}.framework/Resources/Qt{qt_module}.prl"), + ) + } else { + ( + format!("Qt{}{qt_module}", self.qmake_version.major), + self.find_qt_module_prl(&lib_path, prefix, self.qmake_version.major, qt_module), + ) + }; + + self.link_qt_library( + &format!("Qt{}{qt_module}", self.qmake_version.major), + &prefix_path, + &lib_path, + &link_lib, + &prl_path, + builder, + ); + } + + if utils::is_emscripten_target() { + let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS")); + println!("cargo::rustc-link-search={platforms_path}"); + self.link_qt_library( + "qwasm", + &prefix_path, + &lib_path, + "qwasm", + &format!("{platforms_path}/libqwasm.prl"), + builder, + ); + } } fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf> { @@ -114,6 +201,95 @@ impl QtInstallation for QtInstallationQMake { } impl QtInstallationQMake { + /// Some prl files include their architecture in their naming scheme. + /// Just try all known architectures and fallback to non when they all failed. + fn find_qt_module_prl( + &self, + lib_path: &str, + prefix: &str, + version_major: u64, + qt_module: &str, + ) -> String { + for arch in ["", "_arm64-v8a", "_armeabi-v7a", "_x86", "_x86_64"] { + let prl_path = format!( + "{}/{}Qt{}{}{}.prl", + lib_path, prefix, version_major, qt_module, arch + ); + match Path::new(&prl_path).try_exists() { + Ok(exists) => { + if exists { + return prl_path; + } + } + Err(e) => { + println!( + "cargo::warning=failed checking for existence of {}: {}", + prl_path, e + ); + } + } + } + + format!( + "{}/{}Qt{}{}.prl", + lib_path, prefix, version_major, qt_module + ) + } + + fn link_qt_library( + &self, + name: &str, + prefix_path: &str, + lib_path: &str, + link_lib: &str, + prl_path: &str, + builder: &mut cc::Build, + ) { + println!("cargo::rustc-link-lib={link_lib}"); + + match std::fs::read_to_string(prl_path) { + Ok(prl) => { + for line in prl.lines() { + if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") { + parse_cflags::parse_libs_cflags( + name, + line.replace(r"$$[QT_INSTALL_LIBS]", lib_path) + .replace(r"$$[QT_INSTALL_PREFIX]", prefix_path) + .as_bytes(), + builder, + ); + } + } + } + Err(e) => { + println!( + "cargo::warning=Could not open {} file to read libraries to link: {}", + &prl_path, e + ); + } + } + } + + /// Get the framework paths for Qt. This is intended + /// to be passed to whichever tool you are using to invoke the C++ compiler. + fn qmake_framework_paths(&self) -> Vec<PathBuf> { + let mut framework_paths = vec![]; + + if utils::is_apple_target() { + // Note that this adds the framework path which allows for + // includes such as <QtCore/QObject> to be resolved correctly + let framework_path = self.qmake_query("QT_INSTALL_LIBS"); + framework_paths.push(framework_path); + } + + framework_paths + .iter() + .map(PathBuf::from) + // Only add paths if they exist + .filter(|path| path.exists()) + .collect() + } + fn qmake_query(&self, var_name: &str) -> String { String::from_utf8_lossy( &Command::new(&self.qmake_path) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 4329f8d12..47c406f55 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -403,185 +403,9 @@ impl QtBuild { .to_string() } - fn cargo_link_qt_library( - &self, - name: &str, - prefix_path: &str, - lib_path: &str, - link_lib: &str, - prl_path: &str, - builder: &mut cc::Build, - ) { - println!("cargo::rustc-link-lib={link_lib}"); - - match std::fs::read_to_string(prl_path) { - Ok(prl) => { - for line in prl.lines() { - if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") { - parse_cflags::parse_libs_cflags( - name, - line.replace(r"$$[QT_INSTALL_LIBS]", lib_path) - .replace(r"$$[QT_INSTALL_PREFIX]", prefix_path) - .as_bytes(), - builder, - ); - } - } - } - Err(e) => { - println!( - "cargo::warning=Could not open {} file to read libraries to link: {}", - &prl_path, e - ); - } - } - } - - /// Some prl files include their architecture in their naming scheme. - /// Just try all known architectures and fallback to non when they all failed. - fn find_qt_module_prl( - &self, - lib_path: &str, - prefix: &str, - version_major: u64, - qt_module: &str, - ) -> String { - for arch in ["", "_arm64-v8a", "_armeabi-v7a", "_x86", "_x86_64"] { - let prl_path = format!( - "{}/{}Qt{}{}{}.prl", - lib_path, prefix, version_major, qt_module, arch - ); - match Path::new(&prl_path).try_exists() { - Ok(exists) => { - if exists { - return prl_path; - } - } - Err(e) => { - println!( - "cargo::warning=failed checking for existence of {}: {}", - prl_path, e - ); - } - } - } - - format!( - "{}/{}Qt{}{}.prl", - lib_path, prefix, version_major, qt_module - ) - } - /// Tell Cargo to link each Qt module. pub fn cargo_link_libraries(&self, builder: &mut cc::Build) { - let prefix_path = self.qmake_query("QT_INSTALL_PREFIX"); - let lib_path = self.qmake_query("QT_INSTALL_LIBS"); - println!("cargo::rustc-link-search={lib_path}"); - - let target = env::var("TARGET"); - - // Add the QT_INSTALL_LIBS as a framework link search path as well - // - // Note that leaving the kind empty should default to all, - // but this doesn't appear to find frameworks in all situations - // https://github.com/KDAB/cxx-qt/issues/885 - // - // Note this doesn't have an adverse affect running all the time - // as it appears that all rustc-link-search are added - // - // Note that this adds the framework path which allows for - // includes such as <QtCore/QObject> to be resolved correctly - if utils::is_apple_target() { - println!("cargo::rustc-link-search=framework={lib_path}"); - - // Ensure that any framework paths are set to -F - for framework_path in self.framework_paths() { - builder.flag_if_supported(format!("-F{}", framework_path.display())); - // Also set the -rpath otherwise frameworks can not be found at runtime - println!( - "cargo::rustc-link-arg=-Wl,-rpath,{}", - framework_path.display() - ); - } - } - - let prefix = match &target { - Ok(target) => { - if target.contains("windows") { - "" - } else { - "lib" - } - } - Err(_) => "lib", - }; - - for qt_module in &self.qt_modules { - let framework = if utils::is_apple_target() { - Path::new(&format!("{lib_path}/Qt{qt_module}.framework")).exists() - } else { - false - }; - - let (link_lib, prl_path) = if framework { - ( - format!("framework=Qt{qt_module}"), - format!("{lib_path}/Qt{qt_module}.framework/Resources/Qt{qt_module}.prl"), - ) - } else { - ( - format!("Qt{}{qt_module}", self.qt_installation.version().major), - self.find_qt_module_prl( - &lib_path, - prefix, - self.qt_installation.version().major, - qt_module, - ), - ) - }; - - self.cargo_link_qt_library( - &format!("Qt{}{qt_module}", self.qt_installation.version().major), - &prefix_path, - &lib_path, - &link_lib, - &prl_path, - builder, - ); - } - - if utils::is_emscripten_target() { - let platforms_path = format!("{}/platforms", self.qmake_query("QT_INSTALL_PLUGINS")); - println!("cargo::rustc-link-search={platforms_path}"); - self.cargo_link_qt_library( - "qwasm", - &prefix_path, - &lib_path, - "qwasm", - &format!("{platforms_path}/libqwasm.prl"), - builder, - ); - } - } - - /// Get the framework paths for Qt. This is intended - /// to be passed to whichever tool you are using to invoke the C++ compiler. - pub fn framework_paths(&self) -> Vec<PathBuf> { - let mut framework_paths = vec![]; - - if is_apple_target() { - // Note that this adds the framework path which allows for - // includes such as <QtCore/QObject> to be resolved correctly - let framework_path = self.qmake_query("QT_INSTALL_LIBS"); - framework_paths.push(framework_path); - } - - framework_paths - .iter() - .map(PathBuf::from) - // Only add paths if they exist - .filter(|path| path.exists()) - .collect() + self.qt_installation.link_modules(builder, &self.qt_modules); } /// Get the include paths for Qt, including Qt module subdirectories. This is intended From 02c434171017a85f2b5b1fe9345ac373ff6cfd49 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 15:46:41 +0100 Subject: [PATCH 11/27] qt-build-utils: move include paths to qmake installation --- .../qt-build-utils/src/installation/qmake.rs | 26 +++++++++++++++++-- crates/qt-build-utils/src/lib.rs | 24 +---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs index 6e8cbc2da..bedc8248f 100644 --- a/crates/qt-build-utils/src/installation/qmake.rs +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -101,8 +101,30 @@ impl TryFrom<PathBuf> for QtInstallationQMake { } impl QtInstallation for QtInstallationQMake { - fn include_paths(&self, _qt_modules: &[String]) -> Vec<PathBuf> { - todo!() + fn include_paths(&self, qt_modules: &[String]) -> Vec<PathBuf> { + let root_path = self.qmake_query("QT_INSTALL_HEADERS"); + let lib_path = self.qmake_query("QT_INSTALL_LIBS"); + let mut paths = Vec::new(); + for qt_module in qt_modules { + // Add the usual location for the Qt module + paths.push(format!("{root_path}/Qt{qt_module}")); + + // Ensure that we add any framework's headers path + let header_path = format!("{lib_path}/Qt{qt_module}.framework/Headers"); + if utils::is_apple_target() && Path::new(&header_path).exists() { + paths.push(header_path); + } + } + + // Add the QT_INSTALL_HEADERS itself + paths.push(root_path); + + paths + .iter() + .map(PathBuf::from) + // Only add paths if they exist + .filter(|path| path.exists()) + .collect() } fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]) { diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 47c406f55..3a61f0f32 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -411,29 +411,7 @@ impl QtBuild { /// Get the include paths for Qt, including Qt module subdirectories. This is intended /// to be passed to whichever tool you are using to invoke the C++ compiler. pub fn include_paths(&self) -> Vec<PathBuf> { - let root_path = self.qmake_query("QT_INSTALL_HEADERS"); - let lib_path = self.qmake_query("QT_INSTALL_LIBS"); - let mut paths = Vec::new(); - for qt_module in &self.qt_modules { - // Add the usual location for the Qt module - paths.push(format!("{root_path}/Qt{qt_module}")); - - // Ensure that we add any framework's headers path - let header_path = format!("{lib_path}/Qt{qt_module}.framework/Headers"); - if utils::is_apple_target() && Path::new(&header_path).exists() { - paths.push(header_path); - } - } - - // Add the QT_INSTALL_HEADERS itself - paths.push(root_path); - - paths - .iter() - .map(PathBuf::from) - // Only add paths if they exist - .filter(|path| path.exists()) - .collect() + self.qt_installation.include_paths(&self.qt_modules) } /// Version of the detected Qt installation From 112871aec773320b5ca33b060c1bd422edc9af75 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 15:48:50 +0100 Subject: [PATCH 12/27] qt-build-utils: remove qmake executable path as it is in installation --- crates/qt-build-utils/src/lib.rs | 122 +------------------------------ 1 file changed, 4 insertions(+), 118 deletions(-) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 3a61f0f32..e3013852b 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -231,7 +231,6 @@ pub struct QmlModuleRegistrationFiles { /// ``` pub struct QtBuild { qt_installation: Box<dyn QtInstallation>, - qmake_executable: String, qt_modules: Vec<String>, } @@ -284,123 +283,10 @@ impl QtBuild { #[cfg(not(feature = "qmake"))] unsupported!("Only qmake feature is supported"); - println!("cargo::rerun-if-env-changed=QMAKE"); - println!("cargo::rerun-if-env-changed=QT_VERSION_MAJOR"); - fn verify_candidate(candidate: &str) -> Result<&str, QtBuildError> { - match Command::new(candidate) - .args(["-query", "QT_VERSION"]) - .output() - { - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(QtBuildError::QtMissing), - Err(e) => Err(QtBuildError::QmakeFailed(e)), - Ok(output) => { - if output.status.success() { - let version_string = std::str::from_utf8(&output.stdout) - .unwrap() - .trim() - .to_string(); - let qmake_version = Version::parse(&version_string).unwrap(); - if let Ok(env_version) = env::var("QT_VERSION_MAJOR") { - let env_version = match env_version.trim().parse::<u64>() { - Err(e) if *e.kind() == std::num::IntErrorKind::Empty => { - println!( - "cargo::warning=QT_VERSION_MAJOR environment variable defined but empty" - ); - return Ok(candidate); - } - Err(e) => { - return Err(QtBuildError::QtVersionMajorInvalid { - qt_version_major_env_var: env_version, - source: e, - }) - } - Ok(int) => int, - }; - if env_version == qmake_version.major as u64 { - return Ok(candidate); - } else { - return Err(QtBuildError::QtVersionMajorDoesNotMatch { - qmake_version: qmake_version.major as u64, - qt_version_major: env_version, - }); - } - } - Ok(candidate) - } else { - Err(QtBuildError::QtMissing) - } - } - } - } - - if let Ok(qmake_env_var) = env::var("QMAKE") { - match verify_candidate(qmake_env_var.trim()) { - Ok(executable_name) => { - return Ok(Self { - qt_installation, - qmake_executable: executable_name.to_string(), - qt_modules, - }); - } - Err(e) => { - return Err(QtBuildError::QMakeSetQtMissing { - qmake_env_var, - error: Box::new(e.into()), - } - .into()) - } - } - } - - // Fedora 36 renames Qt5's qmake to qmake-qt5 - let candidate_executable_names = ["qmake6", "qmake-qt5", "qmake"]; - for (index, executable_name) in candidate_executable_names.iter().enumerate() { - match verify_candidate(executable_name) { - Ok(executable_name) => { - return Ok(Self { - qt_installation, - qmake_executable: executable_name.to_string(), - qt_modules, - }); - } - // If QT_VERSION_MAJOR is specified, it is expected that one of the versioned - // executable names will not match, so the unversioned `qmake` needs to be - // attempted last and QtVersionMajorDoesNotMatch should only be returned if - // none of the candidate executable names match. - Err(QtBuildError::QtVersionMajorDoesNotMatch { - qmake_version, - qt_version_major, - }) => { - if index == candidate_executable_names.len() - 1 { - return Err(QtBuildError::QtVersionMajorDoesNotMatch { - qmake_version, - qt_version_major, - } - .into()); - } - eprintln!("Candidate qmake executable `{executable_name}` is for Qt{qmake_version} but QT_VERSION_MAJOR environment variable specified as {qt_version_major}. Trying next candidate executable name `{}`...", candidate_executable_names[index + 1]); - continue; - } - Err(QtBuildError::QtMissing) => continue, - Err(e) => return Err(e.into()), - } - } - - Err(QtBuildError::QtMissing.into()) - } - - /// Get the output of running `qmake -query var_name` - pub fn qmake_query(&self, var_name: &str) -> String { - std::str::from_utf8( - &Command::new(&self.qmake_executable) - .args(["-query", var_name]) - .output() - .unwrap() - .stdout, - ) - .unwrap() - .trim() - .to_string() + Ok(Self { + qt_installation, + qt_modules, + }) } /// Tell Cargo to link each Qt module. From 84e18cdc2a355b04b10fed3b38d7c6f0ddf765f0 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 16:09:19 +0100 Subject: [PATCH 13/27] qt-build-utils: parse_cflags is now only needed for qmake builds --- crates/qt-build-utils/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index e3013852b..5c174f315 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -21,6 +21,7 @@ pub use installation::QtInstallation; #[cfg(feature = "qmake")] pub use installation::qmake::QtInstallationQMake; +#[cfg(feature = "qmake")] mod parse_cflags; mod tool; From 8f48c03fdee5d11427fbad1df3ae4413ed382853 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 22 Apr 2025 17:49:01 +0100 Subject: [PATCH 14/27] qt-build-utils: fix docs and add new method with QtInstallation arg --- crates/cxx-qt-build/src/lib.rs | 6 +- crates/cxx-qt-lib/build.rs | 2 +- .../qt-build-utils/src/installation/qmake.rs | 36 +++++++++- crates/qt-build-utils/src/lib.rs | 65 ++++++------------- tests/qt_types_standalone/rust/build.rs | 2 +- 5 files changed, 59 insertions(+), 52 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 19220880e..75b65baeb 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -1122,8 +1122,10 @@ extern "C" bool {init_fun}() {{ let header_root = dir::header_root(); - let mut qtbuild = qt_build_utils::QtBuild::new(qt_modules.iter().cloned().collect()) - .expect("Could not find Qt installation"); + let mut qtbuild = qt_build_utils::QtBuild::new_with_default_installation( + qt_modules.iter().cloned().collect(), + ) + .expect("Could not find Qt installation"); qtbuild.cargo_link_libraries(&mut self.cc_builder); Self::define_qt_version_cfg_variables(qtbuild.version()); diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index e132c51fc..5a06120ed 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -99,7 +99,7 @@ fn write_headers() { } fn main() { - let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) + let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(vec!["Core".to_owned()]) .expect("Could not find Qt installation"); write_headers(); diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs index bedc8248f..825b7aa61 100644 --- a/crates/qt-build-utils/src/installation/qmake.rs +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -13,14 +13,46 @@ use std::{ use crate::{parse_cflags, utils, QtBuildError, QtInstallation, QtTool}; -/// TODO +/// A implementation of [QtInstallation] using qmake pub struct QtInstallationQMake { qmake_path: PathBuf, qmake_version: Version, } impl QtInstallationQMake { - /// TODO + /// The directories specified by the `PATH` environment variable are where qmake is + /// searched for. Alternatively, the `QMAKE` environment variable may be set to specify + /// an explicit path to qmake. + /// + /// If multiple major versions (for example, `5` and `6`) of Qt could be installed, set + /// the `QT_VERSION_MAJOR` environment variable to force which one to use. When using Cargo + /// as the build system for the whole build, prefer using `QT_VERSION_MAJOR` over the `QMAKE` + /// environment variable because it will account for different names for the qmake executable + /// that some Linux distributions use. + /// + /// However, when building a Rust staticlib that gets linked to C++ code by a C++ build + /// system, it is best to use the `QMAKE` environment variable to ensure that the Rust + /// staticlib is linked to the same installation of Qt that the C++ build system has + /// detected. + /// With CMake, this will automatically be set up for you when using cxxqt_import_crate. + /// + /// Alternatively, you can get this from the `Qt::qmake` target's `IMPORTED_LOCATION` + /// property, for example: + /// ```cmake + /// find_package(Qt6 COMPONENTS Core) + /// if(NOT Qt6_FOUND) + /// find_package(Qt5 5.15 COMPONENTS Core REQUIRED) + /// endif() + /// get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) + /// + /// execute_process( + /// COMMAND cmake -E env + /// "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}/cargo" + /// "QMAKE=${QMAKE}" + /// cargo build + /// WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + /// ) + /// ``` pub fn new() -> anyhow::Result<Self> { // Try the QMAKE variable first println!("cargo::rerun-if-env-changed=QMAKE"); diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 5c174f315..bc0fa54b0 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -228,7 +228,7 @@ pub struct QmlModuleRegistrationFiles { /// .iter() /// .map(|m| String::from(*m)) /// .collect(); -/// let qtbuild = qt_build_utils::QtBuild::new(qt_modules).expect("Could not find Qt installation"); +/// let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(qt_modules).expect("Could not find Qt installation"); /// ``` pub struct QtBuild { qt_installation: Box<dyn QtInstallation>, @@ -236,58 +236,31 @@ pub struct QtBuild { } impl QtBuild { - /// Search for where Qt is installed using qmake. Specify the Qt modules you are - /// linking with the `qt_modules` parameter, ommitting the `Qt` prefix (`"Core"` - /// rather than `"QtCore"`). After construction, use the [QtBuild::qmake_query] - /// method to get information about the Qt installation. - /// - /// The directories specified by the `PATH` environment variable are where qmake is - /// searched for. Alternatively, the `QMAKE` environment variable may be set to specify - /// an explicit path to qmake. - /// - /// If multiple major versions (for example, `5` and `6`) of Qt could be installed, set - /// the `QT_VERSION_MAJOR` environment variable to force which one to use. When using Cargo - /// as the build system for the whole build, prefer using `QT_VERSION_MAJOR` over the `QMAKE` - /// environment variable because it will account for different names for the qmake executable - /// that some Linux distributions use. - /// - /// However, when building a Rust staticlib that gets linked to C++ code by a C++ build - /// system, it is best to use the `QMAKE` environment variable to ensure that the Rust - /// staticlib is linked to the same installation of Qt that the C++ build system has - /// detected. - /// With CMake, this will automatically be set up for you when using cxxqt_import_crate. - /// - /// Alternatively, you can get this from the `Qt::qmake` target's `IMPORTED_LOCATION` - /// property, for example: - /// ```cmake - /// find_package(Qt6 COMPONENTS Core) - /// if(NOT Qt6_FOUND) - /// find_package(Qt5 5.15 COMPONENTS Core REQUIRED) - /// endif() - /// get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION) - /// - /// execute_process( - /// COMMAND cmake -E env - /// "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}/cargo" - /// "QMAKE=${QMAKE}" - /// cargo build - /// WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - /// ) - /// ``` - pub fn new(mut qt_modules: Vec<String>) -> anyhow::Result<Self> { - if qt_modules.is_empty() { - qt_modules.push("Core".to_string()); - } - + /// Create a [QtBuild] using the default [QtInstallation] (currently uses [QtInstallationQMake]) + /// and specify which Qt modules you are linking, ommitting the `Qt` prefix (`"Core"` + /// rather than `"QtCore"`). + // + // TODO: is there a better name for this method or a sane way to create a default QtInstallation? + pub fn new_with_default_installation(qt_modules: Vec<String>) -> anyhow::Result<Self> { #[cfg(feature = "qmake")] let qt_installation = Box::new(QtInstallationQMake::new()?); #[cfg(not(feature = "qmake"))] unsupported!("Only qmake feature is supported"); - Ok(Self { + Ok(Self::new(qt_installation, qt_modules)) + } + + /// Create a [QtBuild] using the given [QtInstallation] and specify which + /// Qt modules you are linking, ommitting the `Qt` prefix (`"Core"` rather than `"QtCore"`). + pub fn new(qt_installation: Box<dyn QtInstallation>, mut qt_modules: Vec<String>) -> Self { + if qt_modules.is_empty() { + qt_modules.push("Core".to_string()); + } + + Self { qt_installation, qt_modules, - }) + } } /// Tell Cargo to link each Qt module. diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 51f6df496..72993b6ed 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -6,7 +6,7 @@ use cxx_qt_build::CxxQtBuilder; fn main() { - let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) + let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(vec!["Core".to_owned()]) .expect("Could not find Qt installation"); let mut builder = CxxQtBuilder::new() From 3b1b3f3668449b83d6bcc89d74317c9a78f4c3b9 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 24 Apr 2025 10:22:59 +0100 Subject: [PATCH 15/27] qt-build-utils: add back a cache for finding the Qt tool --- .../qt-build-utils/src/installation/qmake.rs | 18 +++++++++++++++++- crates/qt-build-utils/src/tool.rs | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs index 825b7aa61..5b3820792 100644 --- a/crates/qt-build-utils/src/installation/qmake.rs +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -5,6 +5,8 @@ use semver::Version; use std::{ + cell::RefCell, + collections::HashMap, env, io::ErrorKind, path::{Path, PathBuf}, @@ -17,6 +19,8 @@ use crate::{parse_cflags, utils, QtBuildError, QtInstallation, QtTool}; pub struct QtInstallationQMake { qmake_path: PathBuf, qmake_version: Version, + // Internal cache of paths for tools + tool_cache: RefCell<HashMap<QtTool, Option<PathBuf>>>, } impl QtInstallationQMake { @@ -128,6 +132,7 @@ impl TryFrom<PathBuf> for QtInstallationQMake { Ok(Self { qmake_path, qmake_version, + tool_cache: HashMap::default().into(), }) } } @@ -246,7 +251,18 @@ impl QtInstallation for QtInstallationQMake { } fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf> { - self.try_qmake_find_tool(tool.binary_name()) + let find_tool_closure = |tool: &QtTool| self.try_qmake_find_tool(tool.binary_name()); + + // Attempt to use the cache + if let Ok(mut tool_cache) = self.tool_cache.try_borrow_mut() { + // Read the tool from the cache or insert + tool_cache + .entry(tool) + .or_insert_with_key(find_tool_closure) + .to_owned() + } else { + find_tool_closure(&tool) + } } fn version(&self) -> semver::Version { diff --git a/crates/qt-build-utils/src/tool.rs b/crates/qt-build-utils/src/tool.rs index ce6adf0ac..94e7116e7 100644 --- a/crates/qt-build-utils/src/tool.rs +++ b/crates/qt-build-utils/src/tool.rs @@ -5,6 +5,7 @@ /// An enum representing known Qt tools #[non_exhaustive] +#[derive(Eq, Hash, PartialEq)] pub enum QtTool { /// Moc Moc, From 025ae244caf0ba7989309bd99c3f452103040c4f Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 13:52:16 +0100 Subject: [PATCH 16/27] qt-build-utils: split error into separate module --- crates/qt-build-utils/src/error.rs | 41 ++++++++++++++++++++++++++++++ crates/qt-build-utils/src/lib.rs | 39 +++------------------------- 2 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 crates/qt-build-utils/src/error.rs diff --git a/crates/qt-build-utils/src/error.rs b/crates/qt-build-utils/src/error.rs new file mode 100644 index 000000000..fe48813d1 --- /dev/null +++ b/crates/qt-build-utils/src/error.rs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use thiserror::Error; + +#[derive(Error, Debug)] +/// Errors that can occur while using [crate::QtBuild] +pub enum QtBuildError { + /// `QMAKE` environment variable was set but Qt was not detected + #[error("QMAKE environment variable specified as {qmake_env_var} but could not detect Qt: {error:?}")] + QMakeSetQtMissing { + /// The value of the qmake environment variable when the error occurred + qmake_env_var: String, + /// The inner error that occurred + error: Box<anyhow::Error>, + }, + /// Qt was not found + #[error("Could not find Qt")] + QtMissing, + /// Executing `qmake -query` failed + #[error("Executing `qmake -query` failed: {0:?}")] + QmakeFailed(#[from] std::io::Error), + /// `QT_VERSION_MAJOR` environment variable was specified but could not be parsed as an integer + #[error("QT_VERSION_MAJOR environment variable specified as {qt_version_major_env_var} but could not parse as integer: {source:?}")] + QtVersionMajorInvalid { + /// The Qt major version from `QT_VERSION_MAJOR` + qt_version_major_env_var: String, + /// The [std::num::ParseIntError] when parsing the `QT_VERSION_MAJOR` + source: std::num::ParseIntError, + }, + /// `QT_VERSION_MAJOR` environment variable was specified but the Qt version specified by `qmake -query QT_VERSION` did not match + #[error("qmake version ({qmake_version}) does not match version specified by QT_VERSION_MAJOR ({qt_version_major})")] + QtVersionMajorDoesNotMatch { + /// The qmake version + qmake_version: u64, + /// The Qt major version from `QT_VERSION_MAJOR` + qt_version_major: u64, + }, +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index bc0fa54b0..1b7b0760f 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -15,6 +15,9 @@ #![allow(clippy::too_many_arguments)] +mod error; +pub use error::QtBuildError; + mod installation; pub use installation::QtInstallation; @@ -38,42 +41,6 @@ use std::{ }; use semver::Version; -use thiserror::Error; - -#[derive(Error, Debug)] -/// Errors that can occur while using [QtBuild] -pub enum QtBuildError { - /// `QMAKE` environment variable was set but Qt was not detected - #[error("QMAKE environment variable specified as {qmake_env_var} but could not detect Qt: {error:?}")] - QMakeSetQtMissing { - /// The value of the qmake environment variable when the error occurred - qmake_env_var: String, - /// The inner error that occurred - error: Box<anyhow::Error>, - }, - /// Qt was not found - #[error("Could not find Qt")] - QtMissing, - /// Executing `qmake -query` failed - #[error("Executing `qmake -query` failed: {0:?}")] - QmakeFailed(#[from] std::io::Error), - /// `QT_VERSION_MAJOR` environment variable was specified but could not be parsed as an integer - #[error("QT_VERSION_MAJOR environment variable specified as {qt_version_major_env_var} but could not parse as integer: {source:?}")] - QtVersionMajorInvalid { - /// The Qt major version from `QT_VERSION_MAJOR` - qt_version_major_env_var: String, - /// The [std::num::ParseIntError] when parsing the `QT_VERSION_MAJOR` - source: std::num::ParseIntError, - }, - /// `QT_VERSION_MAJOR` environment variable was specified but the Qt version specified by `qmake -query QT_VERSION` did not match - #[error("qmake version ({qmake_version}) does not match version specified by QT_VERSION_MAJOR ({qt_version_major})")] - QtVersionMajorDoesNotMatch { - /// The qmake version - qmake_version: u64, - /// The Qt major version from `QT_VERSION_MAJOR` - qt_version_major: u64, - }, -} fn command_help_output(command: &str) -> std::io::Result<std::process::Output> { Command::new(command).args(["--help"]).output() From 42599e24f7ba20fa8cbdeb666d64277301f812c1 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 14:14:54 +0100 Subject: [PATCH 17/27] qt-build-utils: split initialiser into module --- crates/qt-build-utils/src/initializer.rs | 35 ++++++++++++++++++++++++ crates/qt-build-utils/src/lib.rs | 32 ++-------------------- 2 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 crates/qt-build-utils/src/initializer.rs diff --git a/crates/qt-build-utils/src/initializer.rs b/crates/qt-build-utils/src/initializer.rs new file mode 100644 index 000000000..b336eb772 --- /dev/null +++ b/crates/qt-build-utils/src/initializer.rs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::path::PathBuf; + +#[doc(hidden)] +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Initializer { + pub file: Option<PathBuf>, + pub init_call: Option<String>, + pub init_declaration: Option<String>, +} + +impl Initializer { + #[doc(hidden)] + pub fn default_signature(name: &str) -> Self { + Self { + file: None, + init_call: Some(format!("{name}();")), + init_declaration: Some(format!("extern \"C\" bool {name}();")), + } + } + + #[doc(hidden)] + // Strip the init files from the public initializers + // For downstream dependencies, it's often enough to just declare the init function and + // call it. + pub fn strip_file(mut self) -> Self { + self.file = None; + self + } +} diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 1b7b0760f..e69cf890f 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -18,6 +18,9 @@ mod error; pub use error::QtBuildError; +mod initializer; +pub use initializer::Initializer; + mod installation; pub use installation::QtInstallation; @@ -106,35 +109,6 @@ pub fn setup_linker() { } } -#[doc(hidden)] -#[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Initializer { - pub file: Option<PathBuf>, - pub init_call: Option<String>, - pub init_declaration: Option<String>, -} - -impl Initializer { - #[doc(hidden)] - pub fn default_signature(name: &str) -> Self { - Self { - file: None, - init_call: Some(format!("{name}();")), - init_declaration: Some(format!("extern \"C\" bool {name}();")), - } - } - - #[doc(hidden)] - // Strip the init files from the public initializers - // For downstream dependencies, it's often enough to just declare the init function and - // call it. - pub fn strip_file(mut self) -> Self { - self.file = None; - self - } -} - /// Paths to files generated by [QtBuild::moc] pub struct MocProducts { /// Generated C++ file From a60136719d1ae9284ce327fbded9483384570b6a Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 14:16:05 +0100 Subject: [PATCH 18/27] qt-build-utils: move tool to a folder --- crates/qt-build-utils/src/{tool.rs => tool/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/qt-build-utils/src/{tool.rs => tool/mod.rs} (100%) diff --git a/crates/qt-build-utils/src/tool.rs b/crates/qt-build-utils/src/tool/mod.rs similarity index 100% rename from crates/qt-build-utils/src/tool.rs rename to crates/qt-build-utils/src/tool/mod.rs From 481c7f4926e922dea8ccebc24b6fb3406315381c Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 14:40:27 +0100 Subject: [PATCH 19/27] qt-build-utils: split out rcc into a tool --- crates/cxx-qt-build/src/lib.rs | 4 +- crates/qt-build-utils/src/lib.rs | 94 ++++----------------- crates/qt-build-utils/src/tool/mod.rs | 3 + crates/qt-build-utils/src/tool/rcc.rs | 114 ++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 80 deletions(-) create mode 100644 crates/qt-build-utils/src/tool/rcc.rs diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 75b65baeb..e39feaced 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -1030,12 +1030,12 @@ extern "C" bool {init_fun}() {{ .iter() .map(|qrc_file| { // Also ensure that each of the files in the qrc can cause a change - for qrc_inner_file in qtbuild.qrc_list(&qrc_file) { + for qrc_inner_file in qtbuild.qrc_list(qrc_file) { println!("cargo::rerun-if-changed={}", qrc_inner_file.display()); } // We need to link this using an object file or +whole-achive, the static initializer of // the qrc file isn't lost. - qtbuild.qrc(&qrc_file) + qtbuild.qrc(qrc_file) }) .collect() } diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index e69cf890f..00bff2a56 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -31,7 +31,7 @@ pub use installation::qmake::QtInstallationQMake; mod parse_cflags; mod tool; -pub use tool::QtTool; +pub use tool::{QtTool, QtToolRcc}; mod utils; @@ -609,6 +609,13 @@ Q_IMPORT_PLUGIN({plugin_class_name}); } } + /// Create a [QtToolRcc] for this [QtBuild] + /// + /// This allows for using [rcc](https://doc.qt.io/qt-6/resources.html) + pub fn rcc(&self) -> QtToolRcc { + QtToolRcc::new(self.qt_installation.as_ref()) + } + /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). /// The path to the generated C++ file is returned, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file). /// This function also returns a String that contains the name of the resource initializer @@ -616,85 +623,16 @@ Q_IMPORT_PLUGIN({plugin_class_name}); /// The build system must ensure that if the .cpp file is built into a static library, either /// the `+whole-archive` flag is used, or the initializer function is called by the /// application. - pub fn qrc(&mut self, input_file: &impl AsRef<Path>) -> Initializer { - let rcc_executable = self - .qt_installation - .try_find_tool(QtTool::Rcc) - .expect("Could not find rcc"); - let input_path = input_file.as_ref(); - let output_folder = PathBuf::from(&format!( - "{}/qt-build-utils/qrc", - env::var("OUT_DIR").unwrap() - )); - std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir"); - let output_path = output_folder.join(format!( - "{}.cpp", - input_path.file_name().unwrap().to_string_lossy(), - )); - let name = input_path - .file_name() - .unwrap() - .to_string_lossy() - .replace('.', "_"); - - let cmd = Command::new(rcc_executable) - .args([ - input_path.to_str().unwrap(), - "-o", - output_path.to_str().unwrap(), - "--name", - &name, - ]) - .output() - .unwrap_or_else(|_| panic!("rcc failed for {}", input_path.display())); - - if !cmd.status.success() { - panic!( - "rcc failed for {}:\n{}", - input_path.display(), - String::from_utf8_lossy(&cmd.stderr) - ); - } - - let qt_6_5 = Version::new(6, 5, 0); - let init_header = if self.qt_installation.version() >= qt_6_5 { - // With Qt6.5 the Q_INIT_RESOURCE macro is in the QtResource header - "QtCore/QtResource" - } else { - "QtCore/QDir" - }; - Initializer { - file: Some(output_path), - init_call: Some(format!("Q_INIT_RESOURCE({name});")), - init_declaration: Some(format!("#include <{init_header}>")), - } + pub fn qrc(&mut self, input_file: impl AsRef<Path>) -> Initializer { + // TODO: later change to just have a rcc() -> QtToolRcc + // but keep this for compat for now + QtToolRcc::new(self.qt_installation.as_ref()).compile(input_file) } /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and return the paths of the sources - pub fn qrc_list(&mut self, input_file: &impl AsRef<Path>) -> Vec<PathBuf> { - let rcc_executable = self - .qt_installation - .try_find_tool(QtTool::Rcc) - .expect("Could not find rcc"); - - // Add the qrc file contents to the cargo rerun list - let input_path = input_file.as_ref(); - let cmd_list = Command::new(rcc_executable) - .args(["--list", input_path.to_str().unwrap()]) - .output() - .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display())); - - if !cmd_list.status.success() { - panic!( - "rcc --list failed for {}:\n{}", - input_path.display(), - String::from_utf8_lossy(&cmd_list.stderr) - ); - } - - String::from_utf8_lossy(&cmd_list.stdout) - .split('\n') - .map(PathBuf::from) - .collect() + pub fn qrc_list(&mut self, input_file: impl AsRef<Path>) -> Vec<PathBuf> { + // TODO: later change to just have a rcc() -> QtToolRcc + // but keep this for compat for now + QtToolRcc::new(self.qt_installation.as_ref()).list(input_file) } } diff --git a/crates/qt-build-utils/src/tool/mod.rs b/crates/qt-build-utils/src/tool/mod.rs index 94e7116e7..7726a485c 100644 --- a/crates/qt-build-utils/src/tool/mod.rs +++ b/crates/qt-build-utils/src/tool/mod.rs @@ -3,6 +3,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod rcc; +pub use rcc::QtToolRcc; + /// An enum representing known Qt tools #[non_exhaustive] #[derive(Eq, Hash, PartialEq)] diff --git a/crates/qt-build-utils/src/tool/rcc.rs b/crates/qt-build-utils/src/tool/rcc.rs new file mode 100644 index 000000000..ae45c49bd --- /dev/null +++ b/crates/qt-build-utils/src/tool/rcc.rs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{Initializer, QtInstallation, QtTool}; + +use semver::Version; +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +/// A wrapper around the [rcc](https://doc.qt.io/qt-6/resources.html) tool +pub struct QtToolRcc { + executable: PathBuf, + qt_version: Version, +} + +impl QtToolRcc { + /// Construct a [QtToolRcc] from a given [QtInstallation] + pub fn new(qt_installation: &dyn QtInstallation) -> Self { + let executable = qt_installation + .try_find_tool(QtTool::Rcc) + .expect("Could not find rcc"); + let qt_version = qt_installation.version(); + + Self { + executable, + qt_version, + } + } + + /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). + /// The path to the generated C++ file is returned, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file). + /// This function also returns a String that contains the name of the resource initializer + /// function. + /// The build system must ensure that if the .cpp file is built into a static library, either + /// the `+whole-archive` flag is used, or the initializer function is called by the + /// application. + pub fn compile(&self, input_file: impl AsRef<Path>) -> Initializer { + let input_path = input_file.as_ref(); + let output_folder = PathBuf::from(&format!( + "{}/qt-build-utils/qrc", + env::var("OUT_DIR").unwrap() + )); + std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir"); + let output_path = output_folder.join(format!( + "{}.cpp", + input_path.file_name().unwrap().to_string_lossy(), + )); + let name = input_path + .file_name() + .unwrap() + .to_string_lossy() + .replace('.', "_"); + + let cmd = Command::new(&self.executable) + .args([ + input_path.to_str().unwrap(), + "-o", + output_path.to_str().unwrap(), + "--name", + &name, + ]) + .output() + .unwrap_or_else(|_| panic!("rcc failed for {}", input_path.display())); + + if !cmd.status.success() { + panic!( + "rcc failed for {}:\n{}", + input_path.display(), + String::from_utf8_lossy(&cmd.stderr) + ); + } + + let qt_6_5 = Version::new(6, 5, 0); + let init_header = if self.qt_version >= qt_6_5 { + // With Qt6.5 the Q_INIT_RESOURCE macro is in the QtResource header + "QtCore/QtResource" + } else { + "QtCore/QDir" + }; + Initializer { + file: Some(output_path), + init_call: Some(format!("Q_INIT_RESOURCE({name});")), + init_declaration: Some(format!("#include <{init_header}>")), + } + } + + /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and return the paths of the sources + pub fn list(&self, input_file: impl AsRef<Path>) -> Vec<PathBuf> { + // Add the qrc file contents to the cargo rerun list + let input_path = input_file.as_ref(); + let cmd_list = Command::new(&self.executable) + .args(["--list", input_path.to_str().unwrap()]) + .output() + .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display())); + + if !cmd_list.status.success() { + panic!( + "rcc --list failed for {}:\n{}", + input_path.display(), + String::from_utf8_lossy(&cmd_list.stderr) + ); + } + + String::from_utf8_lossy(&cmd_list.stdout) + .split('\n') + .map(PathBuf::from) + .collect() + } +} From bdb9e4c479b82b161d07df514543cd462e5a6300 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 15:01:50 +0100 Subject: [PATCH 20/27] qt-build-utils: split Moc into a separate tool --- crates/qt-build-utils/src/lib.rs | 98 +------------------- crates/qt-build-utils/src/tool/moc.rs | 126 ++++++++++++++++++++++++++ crates/qt-build-utils/src/tool/mod.rs | 3 + 3 files changed, 133 insertions(+), 94 deletions(-) create mode 100644 crates/qt-build-utils/src/tool/moc.rs diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 00bff2a56..7ce231040 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -31,7 +31,7 @@ pub use installation::qmake::QtInstallationQMake; mod parse_cflags; mod tool; -pub use tool::{QtTool, QtToolRcc}; +pub use tool::{MocArguments, MocProducts, QtTool, QtToolMoc, QtToolRcc}; mod utils; @@ -109,42 +109,6 @@ pub fn setup_linker() { } } -/// Paths to files generated by [QtBuild::moc] -pub struct MocProducts { - /// Generated C++ file - pub cpp: PathBuf, - /// Generated JSON file - pub metatypes_json: PathBuf, -} - -/// Arguments for a Qt moc invocation. -/// See: [QtBuild::moc] -#[derive(Default, Clone)] -pub struct MocArguments { - uri: Option<String>, - include_paths: Vec<PathBuf>, -} - -impl MocArguments { - /// Should be passed if the input_file is part of a QML module - pub fn uri(mut self, uri: String) -> Self { - self.uri = Some(uri); - self - } - - /// Additional include path to pass to moc - pub fn include_path(mut self, include_path: PathBuf) -> Self { - self.include_paths.push(include_path); - self - } - - /// Additional include paths to pass to moc. - pub fn include_paths(mut self, mut include_paths: Vec<PathBuf>) -> Self { - self.include_paths.append(&mut include_paths); - self - } -} - /// Paths to C++ files generated by [QtBuild::register_qml_module] pub struct QmlModuleRegistrationFiles { /// File generated by [rcc](https://doc.qt.io/qt-6/rcc.html) for the QML plugin. The compiled static library @@ -223,64 +187,10 @@ impl QtBuild { /// Run moc on a C++ header file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). /// The return value contains the path to the generated C++ file, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file), /// as well as the path to the generated metatypes.json file, which can be passed to [register_qml_module](Self::register_qml_module). - /// pub fn moc(&mut self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts { - let moc_executable = self - .qt_installation - .try_find_tool(QtTool::Moc) - .expect("Could not find moc"); - let input_path = input_file.as_ref(); - - // Put all the moc files into one place, this can then be added to the include path - let moc_dir = PathBuf::from(format!( - "{}/qt-build-utils/moc", - env::var("OUT_DIR").unwrap() - )); - std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir"); - let output_path = moc_dir.join(format!( - "moc_{}.cpp", - input_path.file_name().unwrap().to_str().unwrap() - )); - - let metatypes_json_path = PathBuf::from(&format!("{}.json", output_path.display())); - - let mut include_args = vec![]; - // Qt includes - for include_path in self - .include_paths() - .iter() - .chain(arguments.include_paths.iter()) - { - include_args.push(format!("-I{}", include_path.display())); - } - - let mut cmd = Command::new(moc_executable); - - if let Some(uri) = arguments.uri { - cmd.arg(format!("-Muri={uri}")); - } - - cmd.args(include_args); - cmd.arg(input_path.to_str().unwrap()) - .arg("-o") - .arg(output_path.to_str().unwrap()) - .arg("--output-json"); - let cmd = cmd - .output() - .unwrap_or_else(|_| panic!("moc failed for {}", input_path.display())); - - if !cmd.status.success() { - panic!( - "moc failed for {}:\n{}", - input_path.display(), - String::from_utf8_lossy(&cmd.stderr) - ); - } - - MocProducts { - cpp: output_path, - metatypes_json: metatypes_json_path, - } + // TODO: do we change this API to just be moc() -> QtToolMoc ? + QtToolMoc::new(self.qt_installation.as_ref(), &self.qt_modules) + .compile(input_file, arguments) } /// Generate C++ files to automatically register a QML module at build time using the JSON output from [moc](Self::moc). diff --git a/crates/qt-build-utils/src/tool/moc.rs b/crates/qt-build-utils/src/tool/moc.rs new file mode 100644 index 000000000..632580ad7 --- /dev/null +++ b/crates/qt-build-utils/src/tool/moc.rs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{QtInstallation, QtTool}; + +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +/// Paths to files generated by [QtToolMoc::compile] +pub struct MocProducts { + /// Generated C++ file + pub cpp: PathBuf, + /// Generated JSON file + pub metatypes_json: PathBuf, +} + +/// Arguments for a Qt moc invocation. +/// See: [QtToolMoc::compile] +#[derive(Default, Clone)] +pub struct MocArguments { + uri: Option<String>, + include_paths: Vec<PathBuf>, +} + +impl MocArguments { + /// Should be passed if the input_file is part of a QML module + pub fn uri(mut self, uri: String) -> Self { + self.uri = Some(uri); + self + } + + /// Additional include path to pass to moc + pub fn include_path(mut self, include_path: PathBuf) -> Self { + self.include_paths.push(include_path); + self + } + + /// Additional include paths to pass to moc. + pub fn include_paths(mut self, mut include_paths: Vec<PathBuf>) -> Self { + self.include_paths.append(&mut include_paths); + self + } +} + +/// A wrapper around the [moc](https://doc.qt.io/qt-6/moc.html) tool +pub struct QtToolMoc { + executable: PathBuf, + qt_include_paths: Vec<PathBuf>, +} + +impl QtToolMoc { + /// Construct a [QtToolMoc] from a given [QtInstallation] + pub fn new(qt_installation: &dyn QtInstallation, qt_modules: &[String]) -> Self { + let executable = qt_installation + .try_find_tool(QtTool::Moc) + .expect("Could not find moc"); + let qt_include_paths = qt_installation.include_paths(qt_modules); + + Self { + executable, + qt_include_paths, + } + } + + /// Run moc on a C++ header file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). + /// The return value contains the path to the generated C++ file, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file), + /// as well as the path to the generated metatypes.json file, which can be used for QML modules. + pub fn compile(&self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts { + let input_path = input_file.as_ref(); + // Put all the moc files into one place, this can then be added to the include path + let moc_dir = PathBuf::from(format!( + "{}/qt-build-utils/moc", + env::var("OUT_DIR").unwrap() + )); + std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir"); + let output_path = moc_dir.join(format!( + "moc_{}.cpp", + input_path.file_name().unwrap().to_str().unwrap() + )); + + let metatypes_json_path = PathBuf::from(&format!("{}.json", output_path.display())); + + let mut include_args = vec![]; + // Qt includes + for include_path in self + .qt_include_paths + .iter() + .chain(arguments.include_paths.iter()) + { + include_args.push(format!("-I{}", include_path.display())); + } + + let mut cmd = Command::new(&self.executable); + + if let Some(uri) = arguments.uri { + cmd.arg(format!("-Muri={uri}")); + } + + cmd.args(include_args); + cmd.arg(input_path.to_str().unwrap()) + .arg("-o") + .arg(output_path.to_str().unwrap()) + .arg("--output-json"); + let cmd = cmd + .output() + .unwrap_or_else(|_| panic!("moc failed for {}", input_path.display())); + + if !cmd.status.success() { + panic!( + "moc failed for {}:\n{}", + input_path.display(), + String::from_utf8_lossy(&cmd.stderr) + ); + } + + MocProducts { + cpp: output_path, + metatypes_json: metatypes_json_path, + } + } +} diff --git a/crates/qt-build-utils/src/tool/mod.rs b/crates/qt-build-utils/src/tool/mod.rs index 7726a485c..965b927f9 100644 --- a/crates/qt-build-utils/src/tool/mod.rs +++ b/crates/qt-build-utils/src/tool/mod.rs @@ -3,6 +3,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod moc; +pub use moc::{MocArguments, MocProducts, QtToolMoc}; + mod rcc; pub use rcc::QtToolRcc; From 4b1873fa157e58b2d7c7162c751f2ac25596d20b Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Thu, 1 May 2025 15:08:21 +0100 Subject: [PATCH 21/27] qt-build-utils: have a command writable path method for tools --- crates/qt-build-utils/src/tool/moc.rs | 6 +----- crates/qt-build-utils/src/tool/mod.rs | 9 +++++++++ crates/qt-build-utils/src/tool/rcc.rs | 6 +----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/qt-build-utils/src/tool/moc.rs b/crates/qt-build-utils/src/tool/moc.rs index 632580ad7..fadf6152b 100644 --- a/crates/qt-build-utils/src/tool/moc.rs +++ b/crates/qt-build-utils/src/tool/moc.rs @@ -6,7 +6,6 @@ use crate::{QtInstallation, QtTool}; use std::{ - env, path::{Path, PathBuf}, process::Command, }; @@ -73,10 +72,7 @@ impl QtToolMoc { pub fn compile(&self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts { let input_path = input_file.as_ref(); // Put all the moc files into one place, this can then be added to the include path - let moc_dir = PathBuf::from(format!( - "{}/qt-build-utils/moc", - env::var("OUT_DIR").unwrap() - )); + let moc_dir = QtTool::Moc.writable_path(); std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir"); let output_path = moc_dir.join(format!( "moc_{}.cpp", diff --git a/crates/qt-build-utils/src/tool/mod.rs b/crates/qt-build-utils/src/tool/mod.rs index 965b927f9..1495ce17d 100644 --- a/crates/qt-build-utils/src/tool/mod.rs +++ b/crates/qt-build-utils/src/tool/mod.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::{env, path::PathBuf}; + mod moc; pub use moc::{MocArguments, MocProducts, QtToolMoc}; @@ -33,4 +35,11 @@ impl QtTool { Self::QmlTypeRegistrar => "qmltyperegistrar", } } + + /// Return a directory where files can be written by this tool + /// + /// Note the location might not exist yet + pub(crate) fn writable_path(&self) -> PathBuf { + PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR was not set")).join(self.binary_name()) + } } diff --git a/crates/qt-build-utils/src/tool/rcc.rs b/crates/qt-build-utils/src/tool/rcc.rs index ae45c49bd..223e15ea1 100644 --- a/crates/qt-build-utils/src/tool/rcc.rs +++ b/crates/qt-build-utils/src/tool/rcc.rs @@ -7,7 +7,6 @@ use crate::{Initializer, QtInstallation, QtTool}; use semver::Version; use std::{ - env, path::{Path, PathBuf}, process::Command, }; @@ -41,10 +40,7 @@ impl QtToolRcc { /// application. pub fn compile(&self, input_file: impl AsRef<Path>) -> Initializer { let input_path = input_file.as_ref(); - let output_folder = PathBuf::from(&format!( - "{}/qt-build-utils/qrc", - env::var("OUT_DIR").unwrap() - )); + let output_folder = QtTool::Rcc.writable_path(); std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir"); let output_path = output_folder.join(format!( "{}.cpp", From ec39afe0c3cd04b50fecce432c599cdcbfb6479e Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Mon, 5 May 2025 11:41:16 +0100 Subject: [PATCH 22/27] qt-build-utils: split out qmltyperegistrar into a separate tool --- crates/cxx-qt-build/src/lib.rs | 4 +- crates/qt-build-utils/src/lib.rs | 66 ++++---------- crates/qt-build-utils/src/tool/mod.rs | 3 + .../src/tool/qmltyperegistrar.rs | 88 +++++++++++++++++++ 4 files changed, 109 insertions(+), 52 deletions(-) create mode 100644 crates/qt-build-utils/src/tool/qmltyperegistrar.rs diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index e39feaced..09db9e176 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -852,8 +852,10 @@ impl CxxQtBuilder { &qml_module.qml_files, &qml_module.qrc_files, ); + if let Some(qmltyperegistrar) = qml_module_registration_files.qmltyperegistrar { + cc_builder.file(qmltyperegistrar); + } cc_builder - .file(qml_module_registration_files.qmltyperegistrar) .file(qml_module_registration_files.plugin) // In comparison to the other RCC files, we don't need to link this with whole-archive or // anything like that. diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 7ce231040..51a9f1c78 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -31,7 +31,7 @@ pub use installation::qmake::QtInstallationQMake; mod parse_cflags; mod tool; -pub use tool::{MocArguments, MocProducts, QtTool, QtToolMoc, QtToolRcc}; +pub use tool::{MocArguments, MocProducts, QtTool, QtToolMoc, QtToolQmlTypeRegistrar, QtToolRcc}; mod utils; @@ -118,7 +118,7 @@ pub struct QmlModuleRegistrationFiles { /// Files generated by [qmlcachegen](https://doc.qt.io/qt-6/qtqml-qtquick-compiler-tech.html). Must be linked with `+whole-archive`. pub qmlcachegen: Vec<PathBuf>, /// File generated by [qmltyperegistrar](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) CLI tool. - pub qmltyperegistrar: PathBuf, + pub qmltyperegistrar: Option<PathBuf>, /// File with generated [QQmlEngineExtensionPlugin](https://doc.qt.io/qt-6/qqmlengineextensionplugin.html) that calls the function generated by qmltyperegistrar. pub plugin: PathBuf, /// Initializer that automatically registers the QQmlExtensionPlugin at startup. @@ -210,10 +210,6 @@ impl QtBuild { qml_files: &[impl AsRef<Path>], qrc_files: &[impl AsRef<Path>], ) -> QmlModuleRegistrationFiles { - let qmltyperegistrar_executable = self - .qt_installation - .try_find_tool(QtTool::QmlTypeRegistrar) - .expect("Could not find qmltyperegistrar"); // qmlcachegen has a different CLI in Qt 5, so only support Qt >= 6 let qmlcachegen_executable = if self.qt_installation.version().major >= 6 { Some( @@ -384,50 +380,13 @@ prefer :/qt/qml/{qml_uri_dirs}/ 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"); - // Run qmltyperegistrar - let qmltyperegistrar_output_path = - qml_plugin_dir.join(format!("{qml_uri_underscores}_qmltyperegistration.cpp")); - - // Filter out empty jsons - let metatypes_json: Vec<_> = metatypes_json - .iter() - .filter(|f| { - std::fs::metadata(f) - .unwrap_or_else(|_| { - panic!("couldn't open json file {}", f.as_ref().to_string_lossy()) - }) - .len() - > 0 - }) - .map(|f| f.as_ref().to_string_lossy().to_string()) - .collect(); - - // Only run qmltyperegistrar if we have valid json files left out - if !metatypes_json.is_empty() { - let mut args = vec![ - "--generate-qmltypes".to_string(), - qmltypes_path.to_string_lossy().to_string(), - "--major-version".to_string(), - version_major.to_string(), - "--minor-version".to_string(), - version_minor.to_string(), - "--import-name".to_string(), - uri.to_string(), - "-o".to_string(), - qmltyperegistrar_output_path.to_string_lossy().to_string(), - ]; - args.extend(metatypes_json); - let cmd = Command::new(qmltyperegistrar_executable) - .args(args) - .output() - .unwrap_or_else(|_| panic!("qmltyperegistrar failed for {uri}")); - if !cmd.status.success() { - panic!( - "qmltyperegistrar failed for {uri}:\n{}", - String::from_utf8_lossy(&cmd.stderr) - ); - } - } + // Run qmltyperegistrar over the meta types + let qmltyperegistrar_path = self.qmltyperegistrar().compile( + metatypes_json, + qmltypes_path, + uri, + Version::new(version_major as u64, version_minor as u64, 0), + ); // Generate QQmlEngineExtensionPlugin let qml_plugin_cpp_path = qml_plugin_dir.join(format!("{plugin_class_name}.cpp")); @@ -511,7 +470,7 @@ Q_IMPORT_PLUGIN({plugin_class_name}); // so we don't need to treat it like an initializer here. rcc: rcc.file.unwrap(), qmlcachegen: qmlcachegen_file_paths, - qmltyperegistrar: qmltyperegistrar_output_path, + qmltyperegistrar: qmltyperegistrar_path, plugin: qml_plugin_cpp_path, plugin_init, include_path, @@ -526,6 +485,11 @@ Q_IMPORT_PLUGIN({plugin_class_name}); QtToolRcc::new(self.qt_installation.as_ref()) } + /// Create a [QtToolQmlTypeRegistrar] for this [QtBuild] + pub fn qmltyperegistrar(&self) -> QtToolQmlTypeRegistrar { + QtToolQmlTypeRegistrar::new(self.qt_installation.as_ref()) + } + /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). /// The path to the generated C++ file is returned, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file). /// This function also returns a String that contains the name of the resource initializer diff --git a/crates/qt-build-utils/src/tool/mod.rs b/crates/qt-build-utils/src/tool/mod.rs index 1495ce17d..193eb8b6c 100644 --- a/crates/qt-build-utils/src/tool/mod.rs +++ b/crates/qt-build-utils/src/tool/mod.rs @@ -8,6 +8,9 @@ use std::{env, path::PathBuf}; mod moc; pub use moc::{MocArguments, MocProducts, QtToolMoc}; +mod qmltyperegistrar; +pub use qmltyperegistrar::QtToolQmlTypeRegistrar; + mod rcc; pub use rcc::QtToolRcc; diff --git a/crates/qt-build-utils/src/tool/qmltyperegistrar.rs b/crates/qt-build-utils/src/tool/qmltyperegistrar.rs new file mode 100644 index 000000000..71e108d5f --- /dev/null +++ b/crates/qt-build-utils/src/tool/qmltyperegistrar.rs @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{QtInstallation, QtTool}; +use semver::Version; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +/// A wrapper around the [qmltyperegistrar](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) tool +pub struct QtToolQmlTypeRegistrar { + executable: PathBuf, +} + +impl QtToolQmlTypeRegistrar { + /// Construct a [QtToolQmlTypeRegistrar] from a given [QtInstallation] + pub fn new(qt_installation: &dyn QtInstallation) -> Self { + let executable = qt_installation + .try_find_tool(QtTool::QmlTypeRegistrar) + .expect("Could not find qmltyperegistrar"); + + Self { executable } + } + + /// Run [qmltyperegistrar](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) + pub fn compile( + &self, + metatypes_json: &[impl AsRef<Path>], + qmltypes: impl AsRef<Path>, + uri: &str, + version: Version, + ) -> Option<PathBuf> { + // Filter out empty jsons + let metatypes_json: Vec<_> = metatypes_json + .iter() + .filter(|f| { + std::fs::metadata(f) + .unwrap_or_else(|_| { + panic!("couldn't open json file {}", f.as_ref().to_string_lossy()) + }) + .len() + > 0 + }) + .map(|f| f.as_ref().to_string_lossy().to_string()) + .collect(); + + // Only run qmltyperegistrar if we have valid json files left out + if metatypes_json.is_empty() { + return None; + } + + let qml_uri_underscores = uri.replace('.', "_"); + // TODO: note before this was the plugin folder + let output_folder = QtTool::QmlTypeRegistrar.writable_path(); + std::fs::create_dir_all(&output_folder).expect("Could not create qmltyperegistrar dir"); + let qmltyperegistrar_output_path = + output_folder.join(format!("{qml_uri_underscores}_qmltyperegistration.cpp")); + + let mut args = vec![ + "--generate-qmltypes".to_string(), + qmltypes.as_ref().to_string_lossy().to_string(), + "--major-version".to_string(), + version.major.to_string(), + "--minor-version".to_string(), + version.minor.to_string(), + "--import-name".to_string(), + uri.to_string(), + "-o".to_string(), + qmltyperegistrar_output_path.to_string_lossy().to_string(), + ]; + args.extend(metatypes_json); + let cmd = Command::new(&self.executable) + .args(args) + .output() + .unwrap_or_else(|_| panic!("qmltyperegistrar failed for {uri}")); + if !cmd.status.success() { + panic!( + "qmltyperegistrar failed for {uri}:\n{}", + String::from_utf8_lossy(&cmd.stderr) + ); + } + + Some(qmltyperegistrar_output_path) + } +} From 88dad8fece7f30153b68d2dd8b74ea64e6feeedf Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Fri, 9 May 2025 15:17:51 +0100 Subject: [PATCH 23/27] qt-build-utils: split out qmlcachegen into a separate tool --- crates/qt-build-utils/src/lib.rs | 109 +++---------- crates/qt-build-utils/src/tool/mod.rs | 3 + crates/qt-build-utils/src/tool/qmlcachegen.rs | 154 ++++++++++++++++++ 3 files changed, 179 insertions(+), 87 deletions(-) create mode 100644 crates/qt-build-utils/src/tool/qmlcachegen.rs diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 51a9f1c78..3cf6b0193 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -31,7 +31,10 @@ pub use installation::qmake::QtInstallationQMake; mod parse_cflags; mod tool; -pub use tool::{MocArguments, MocProducts, QtTool, QtToolMoc, QtToolQmlTypeRegistrar, QtToolRcc}; +pub use tool::{ + MocArguments, MocProducts, QmlCacheArguments, QmlCacheProducts, QtTool, QtToolMoc, + QtToolQmlCacheGen, QtToolQmlTypeRegistrar, QtToolRcc, +}; mod utils; @@ -210,17 +213,6 @@ impl QtBuild { qml_files: &[impl AsRef<Path>], qrc_files: &[impl AsRef<Path>], ) -> QmlModuleRegistrationFiles { - // qmlcachegen has a different CLI in Qt 5, so only support Qt >= 6 - let qmlcachegen_executable = if self.qt_installation.version().major >= 6 { - Some( - self.qt_installation - .try_find_tool(QtTool::QmlCacheGen) - .expect("Could not find qmlcachegen"), - ) - } else { - None - }; - let qml_uri_dirs = uri.replace('.', "/"); let out_dir = env::var("OUT_DIR").unwrap(); @@ -295,85 +287,28 @@ prefer :/qt/qml/{qml_uri_dirs}/ // qmlcachegen needs to be run once for each .qml file with --resource-path, // then once for the module with --resource-name. let mut qmlcachegen_file_paths = Vec::new(); - if let Some(qmlcachegen_executable) = &qmlcachegen_executable { - let qmlcachegen_dir = qt_build_utils_dir.join("qmlcachegen").join(&qml_uri_dirs); - std::fs::create_dir_all(&qmlcachegen_dir) - .expect("Could not create qmlcachegen directory for QML module"); - - let common_args = [ - "-i".to_string(), - qmldir_file_path.to_string_lossy().to_string(), - "--resource".to_string(), - qrc_path.to_string_lossy().to_string(), - ]; - - let mut qml_file_qrc_paths = Vec::new(); + + // qmlcachegen has a different CLI in Qt 5, so only support Qt >= 6 + if self.qt_installation.version().major >= 6 { + let qml_cache_args = QmlCacheArguments { + uri: uri.to_string(), + qmldir_path: qmldir_file_path, + qmldir_qrc_path: qrc_path.clone(), + }; + let mut qml_resource_paths = Vec::new(); for file in qml_files { - let qrc_resource_path = - format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display()); - - let qml_compiled_file = qmlcachegen_dir.join(format!( - "{}.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.to_string_lossy().to_string(), - 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 result = QtToolQmlCacheGen::new(self.qt_installation.as_ref()) + .compile(qml_cache_args.clone(), file); + qmlcachegen_file_paths.push(result.qml_cache_path); + qml_resource_paths.push(result.qml_resource_path); } - let qmlcachegen_loader = qmlcachegen_dir.join("qmlcache_loader.cpp"); - let specific_args = vec![ - "--resource-name".to_string(), - format!("qmlcache_{qml_uri_underscores}"), - "-o".to_string(), - qmlcachegen_loader.to_string_lossy().to_string(), - ]; - // If there are no QML files there is nothing for qmlcachegen to run with if !qml_files.is_empty() { - 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)); + qmlcachegen_file_paths.push( + QtToolQmlCacheGen::new(self.qt_installation.as_ref()) + .compile_loader(qml_cache_args.clone(), &qml_resource_paths), + ); } } @@ -407,7 +342,7 @@ prefer :/qt/qml/{qml_uri_dirs}/ &format!("qInitResources_qml_module_resources_{qml_uri_underscores}_qrc"), ); - if !qml_files.is_empty() && qmlcachegen_executable.is_some() { + if !qml_files.is_empty() && !qmlcachegen_file_paths.is_empty() { generate_usage( "int", &format!("qInitResources_qmlcache_{qml_uri_underscores}"), diff --git a/crates/qt-build-utils/src/tool/mod.rs b/crates/qt-build-utils/src/tool/mod.rs index 193eb8b6c..b03d6c970 100644 --- a/crates/qt-build-utils/src/tool/mod.rs +++ b/crates/qt-build-utils/src/tool/mod.rs @@ -8,6 +8,9 @@ use std::{env, path::PathBuf}; mod moc; pub use moc::{MocArguments, MocProducts, QtToolMoc}; +mod qmlcachegen; +pub use qmlcachegen::{QmlCacheArguments, QmlCacheProducts, QtToolQmlCacheGen}; + mod qmltyperegistrar; pub use qmltyperegistrar::QtToolQmlTypeRegistrar; diff --git a/crates/qt-build-utils/src/tool/qmlcachegen.rs b/crates/qt-build-utils/src/tool/qmlcachegen.rs new file mode 100644 index 000000000..a4bd3f248 --- /dev/null +++ b/crates/qt-build-utils/src/tool/qmlcachegen.rs @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{QtInstallation, QtTool}; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +/// Arguments for a [QtToolQmlCacheGen] +#[derive(Clone)] +pub struct QmlCacheArguments { + /// The URI for the QML module + pub uri: String, + /// The path to the qmldir + pub qmldir_path: PathBuf, + /// The path to the qrc file that contains a qmldir + pub qmldir_qrc_path: PathBuf, +} + +/// Paths to files generated by [QtToolQmlCacheGen] +pub struct QmlCacheProducts { + /// The path of the generated cache file + pub qml_cache_path: PathBuf, + /// The Qt resource path for qml file + pub qml_resource_path: String, +} + +/// A wrapper around the [qmlcachegen](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) tool +pub struct QtToolQmlCacheGen { + executable: PathBuf, +} + +impl QtToolQmlCacheGen { + /// Construct a [QtToolQmlCacheGen] from a given [QtInstallation] + pub fn new(qt_installation: &dyn QtInstallation) -> Self { + let executable = qt_installation + .try_find_tool(QtTool::QmlCacheGen) + .expect("Could not find qmlcachegen"); + + Self { executable } + } + + /// Run qmlcachegen for a given qml file + pub fn compile( + &self, + common_args: QmlCacheArguments, + file: impl AsRef<Path>, + ) -> QmlCacheProducts { + let uri = common_args.uri; + let qml_uri_dirs = uri.replace('.', "/"); + + let qmlcachegen_dir = QtTool::QmlCacheGen.writable_path().join(&qml_uri_dirs); + std::fs::create_dir_all(&qmlcachegen_dir) + .expect("Could not create qmlcachegen directory for QML module"); + + let common_args = [ + "-i".to_string(), + common_args.qmldir_path.to_string_lossy().to_string(), + "--resource".to_string(), + common_args.qmldir_qrc_path.to_string_lossy().to_string(), + ]; + + let qml_cache_path = qmlcachegen_dir.join(format!( + "{}.cpp", + file.as_ref().file_name().unwrap().to_string_lossy() + )); + + let qml_resource_path = format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display()); + + let specific_args = vec![ + "--resource-path".to_string(), + qml_resource_path.to_string(), + "-o".to_string(), + qml_cache_path.to_string_lossy().to_string(), + std::fs::canonicalize(&file) + .unwrap() + .to_string_lossy() + .to_string(), + ]; + + let cmd = Command::new(&self.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) + ); + } + + QmlCacheProducts { + qml_cache_path, + qml_resource_path, + } + } + + /// Compile a loader for given qml resource paths + pub fn compile_loader( + &self, + common_args: QmlCacheArguments, + qml_resource_paths: &[String], + ) -> PathBuf { + let uri = common_args.uri; + let qml_uri_dirs = uri.replace('.', "/"); + let qml_uri_underscores = uri.replace('.', "_"); + + let qmlcachegen_dir = QtTool::QmlCacheGen.writable_path().join(qml_uri_dirs); + std::fs::create_dir_all(&qmlcachegen_dir) + .expect("Could not create qmlcachegen directory for QML module"); + + let common_args = [ + "-i".to_string(), + common_args.qmldir_path.to_string_lossy().to_string(), + "--resource".to_string(), + common_args.qmldir_qrc_path.to_string_lossy().to_string(), + ]; + + let qmlcachegen_loader = qmlcachegen_dir.join("qmlcache_loader.cpp"); + let specific_args = vec![ + "--resource-name".to_string(), + format!("qmlcache_{qml_uri_underscores}"), + "-o".to_string(), + qmlcachegen_loader.to_string_lossy().to_string(), + ]; + + let cmd = Command::new(&self.executable) + .args( + common_args + .iter() + .chain(&specific_args) + .chain(qml_resource_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_loader + } +} From e0e407264c91baebe2dfb4938f17fe11fe574ec1 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 13 May 2025 11:04:38 +0100 Subject: [PATCH 24/27] cxx-qt-build: use new rcc method instead of qrc --- crates/cxx-qt-build/src/lib.rs | 4 ++-- crates/qt-build-utils/src/lib.rs | 22 +--------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 09db9e176..81913d34b 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -1032,12 +1032,12 @@ extern "C" bool {init_fun}() {{ .iter() .map(|qrc_file| { // Also ensure that each of the files in the qrc can cause a change - for qrc_inner_file in qtbuild.qrc_list(qrc_file) { + for qrc_inner_file in qtbuild.rcc().list(qrc_file) { println!("cargo::rerun-if-changed={}", qrc_inner_file.display()); } // We need to link this using an object file or +whole-achive, the static initializer of // the qrc file isn't lost. - qtbuild.qrc(qrc_file) + qtbuild.rcc().compile(qrc_file) }) .collect() } diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 3cf6b0193..6eb1d70e4 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -399,7 +399,7 @@ Q_IMPORT_PLUGIN({plugin_class_name}); )), }; - let rcc = self.qrc(&qrc_path); + let rcc = self.rcc().compile(&qrc_path); QmlModuleRegistrationFiles { // The rcc file is automatically initialized when importing the plugin. // so we don't need to treat it like an initializer here. @@ -424,24 +424,4 @@ Q_IMPORT_PLUGIN({plugin_class_name}); pub fn qmltyperegistrar(&self) -> QtToolQmlTypeRegistrar { QtToolQmlTypeRegistrar::new(self.qt_installation.as_ref()) } - - /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). - /// The path to the generated C++ file is returned, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file). - /// This function also returns a String that contains the name of the resource initializer - /// function. - /// The build system must ensure that if the .cpp file is built into a static library, either - /// the `+whole-archive` flag is used, or the initializer function is called by the - /// application. - pub fn qrc(&mut self, input_file: impl AsRef<Path>) -> Initializer { - // TODO: later change to just have a rcc() -> QtToolRcc - // but keep this for compat for now - QtToolRcc::new(self.qt_installation.as_ref()).compile(input_file) - } - - /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and return the paths of the sources - pub fn qrc_list(&mut self, input_file: impl AsRef<Path>) -> Vec<PathBuf> { - // TODO: later change to just have a rcc() -> QtToolRcc - // but keep this for compat for now - QtToolRcc::new(self.qt_installation.as_ref()).list(input_file) - } } From 42b24e9c66d0e24c472e38c922d403e79aa6853b Mon Sep 17 00:00:00 2001 From: Andrew Hayzen <andrew.hayzen@kdab.com> Date: Tue, 13 May 2025 11:08:57 +0100 Subject: [PATCH 25/27] cxx-qt-build: use moc() method to retrieve the tool not compile --- crates/cxx-qt-build/src/lib.rs | 4 ++-- crates/qt-build-utils/src/lib.rs | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 81913d34b..e11233dc8 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -702,7 +702,7 @@ impl CxxQtBuilder { moc_arguments, } in &self.qobject_headers { - let moc_products = qtbuild.moc(path, moc_arguments.clone()); + let moc_products = qtbuild.moc().compile(path, moc_arguments.clone()); // Include the moc folder if let Some(dir) = moc_products.cpp.parent() { self.cc_builder.include(dir); @@ -827,7 +827,7 @@ impl CxxQtBuilder { } cc_builder.file(&qobject); - let moc_products = qtbuild.moc( + let moc_products = qtbuild.moc().compile( qobject_header, MocArguments::default().uri(qml_module.uri.clone()), ); diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 6eb1d70e4..47fad8111 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -187,13 +187,11 @@ impl QtBuild { self.qt_installation.version() } - /// Run moc on a C++ header file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html). - /// The return value contains the path to the generated C++ file, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file), - /// as well as the path to the generated metatypes.json file, which can be passed to [register_qml_module](Self::register_qml_module). - pub fn moc(&mut self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts { - // TODO: do we change this API to just be moc() -> QtToolMoc ? + /// Create a [QtToolMoc] for this [QtBuild] + /// + /// This allows for using [moc](https://doc.qt.io/qt-6/moc.html) + pub fn moc(&mut self) -> QtToolMoc { QtToolMoc::new(self.qt_installation.as_ref(), &self.qt_modules) - .compile(input_file, arguments) } /// Generate C++ files to automatically register a QML module at build time using the JSON output from [moc](Self::moc). @@ -380,7 +378,7 @@ public: ) .expect("Failed to write plugin definition"); - let moc_product = self.moc( + let moc_product = self.moc().compile( &qml_plugin_cpp_path, MocArguments::default().uri(uri.to_owned()), ); From e4b8567cd162277911cc3499b2b517d5ecc52f63 Mon Sep 17 00:00:00 2001 From: Leon Matthes <leon.matthes@kdab.com> Date: Thu, 22 May 2025 19:09:55 +0200 Subject: [PATCH 26/27] qt-build-utils: Return Result in try_find_tool This now also prints out the paths that were attempted. --- crates/qt-build-utils/src/installation/mod.rs | 2 +- .../qt-build-utils/src/installation/qmake.rs | 45 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/crates/qt-build-utils/src/installation/mod.rs b/crates/qt-build-utils/src/installation/mod.rs index 3847024f8..cdb91710f 100644 --- a/crates/qt-build-utils/src/installation/mod.rs +++ b/crates/qt-build-utils/src/installation/mod.rs @@ -36,7 +36,7 @@ pub trait QtInstallation { // } fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]); /// Find the path to a given Qt tool for the Qt installation - fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf>; + fn try_find_tool(&self, tool: QtTool) -> anyhow::Result<PathBuf>; /// Version of the detected Qt installation fn version(&self) -> Version; } diff --git a/crates/qt-build-utils/src/installation/qmake.rs b/crates/qt-build-utils/src/installation/qmake.rs index 5b3820792..920a48cf3 100644 --- a/crates/qt-build-utils/src/installation/qmake.rs +++ b/crates/qt-build-utils/src/installation/qmake.rs @@ -20,7 +20,13 @@ pub struct QtInstallationQMake { qmake_path: PathBuf, qmake_version: Version, // Internal cache of paths for tools - tool_cache: RefCell<HashMap<QtTool, Option<PathBuf>>>, + // + // Note that this only stores valid resolved paths. + // If we failed to find the tool, we will not cache the failure and instead retry if called + // again. + // This is partially because anyhow::Error is not Clone, and partially because retrying gives + // the caller the ability to change the environment and try again. + tool_cache: RefCell<HashMap<QtTool, PathBuf>>, } impl QtInstallationQMake { @@ -250,18 +256,24 @@ impl QtInstallation for QtInstallationQMake { } } - fn try_find_tool(&self, tool: QtTool) -> Option<PathBuf> { - let find_tool_closure = |tool: &QtTool| self.try_qmake_find_tool(tool.binary_name()); + fn try_find_tool(&self, tool: QtTool) -> anyhow::Result<PathBuf> { + let find_tool = || self.try_qmake_find_tool(tool.binary_name()); // Attempt to use the cache if let Ok(mut tool_cache) = self.tool_cache.try_borrow_mut() { // Read the tool from the cache or insert - tool_cache - .entry(tool) - .or_insert_with_key(find_tool_closure) - .to_owned() + let path = tool_cache.get(&tool); + let path = match path { + Some(path) => path.clone(), + None => { + let path = find_tool()?; + tool_cache.insert(tool, path.clone()); + path + } + }; + Ok(path) } else { - find_tool_closure(&tool) + find_tool() } } @@ -372,7 +384,7 @@ impl QtInstallationQMake { .to_string() } - fn try_qmake_find_tool(&self, tool_name: &str) -> Option<PathBuf> { + fn try_qmake_find_tool(&self, tool_name: &str) -> anyhow::Result<PathBuf> { // "qmake -query" exposes a list of paths that describe where Qt executables and libraries // are located, as well as where new executables & libraries should be installed to. // We can use these variables to find any Qt tool. @@ -405,6 +417,7 @@ impl QtInstallationQMake { // To check & debug all variables available on your system, simply run: // // qmake -query + let mut failed_paths = vec![]; [ "QT_HOST_LIBEXECS/get", "QT_HOST_LIBEXECS", @@ -419,11 +432,15 @@ impl QtInstallationQMake { // Find the first valid executable path .find_map(|qmake_query_var| { let executable_path = PathBuf::from(self.qmake_query(qmake_query_var)).join(tool_name); - Command::new(&executable_path) - .args(["-help"]) - .output() - .map(|_| executable_path) - .ok() + let test_output = Command::new(&executable_path).args(["-help"]).output(); + match test_output { + Err(_err) => { + failed_paths.push(executable_path); + None + } + Ok(_) => Some(executable_path), + } }) + .ok_or_else(|| anyhow::anyhow!("Failed to find {tool_name}, tried: {failed_paths:?}")) } } From 6c2ce2f342b22761ca47d3c06cee7725890336b4 Mon Sep 17 00:00:00 2001 From: Leon Matthes <leon.matthes@kdab.com> Date: Thu, 22 May 2025 19:12:36 +0200 Subject: [PATCH 27/27] qt-build-utils: new_with_default/new -> new/with_installation Also only compile in the `new` function if the `qmake` feature is provided. Previously this function would not run anyhow. If we add a `cmake` feature, we can conditionally enable the function if either `cmake` or `qmake` is enabled. --- crates/cxx-qt-build/src/lib.rs | 6 ++---- crates/cxx-qt-lib/build.rs | 2 +- crates/qt-build-utils/src/lib.rs | 21 +++++++++++---------- tests/qt_types_standalone/rust/build.rs | 2 +- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index e11233dc8..ae810915f 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -1124,10 +1124,8 @@ extern "C" bool {init_fun}() {{ let header_root = dir::header_root(); - let mut qtbuild = qt_build_utils::QtBuild::new_with_default_installation( - qt_modules.iter().cloned().collect(), - ) - .expect("Could not find Qt installation"); + let mut qtbuild = qt_build_utils::QtBuild::new(qt_modules.iter().cloned().collect()) + .expect("Could not find Qt installation"); qtbuild.cargo_link_libraries(&mut self.cc_builder); Self::define_qt_version_cfg_variables(qtbuild.version()); diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index 5a06120ed..e132c51fc 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -99,7 +99,7 @@ fn write_headers() { } fn main() { - let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(vec!["Core".to_owned()]) + let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) .expect("Could not find Qt installation"); write_headers(); diff --git a/crates/qt-build-utils/src/lib.rs b/crates/qt-build-utils/src/lib.rs index 47fad8111..1d0debeb9 100644 --- a/crates/qt-build-utils/src/lib.rs +++ b/crates/qt-build-utils/src/lib.rs @@ -136,7 +136,7 @@ pub struct QmlModuleRegistrationFiles { /// .iter() /// .map(|m| String::from(*m)) /// .collect(); -/// let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(qt_modules).expect("Could not find Qt installation"); +/// let qtbuild = qt_build_utils::QtBuild::new(qt_modules).expect("Could not find Qt installation"); /// ``` pub struct QtBuild { qt_installation: Box<dyn QtInstallation>, @@ -147,20 +147,21 @@ impl QtBuild { /// Create a [QtBuild] using the default [QtInstallation] (currently uses [QtInstallationQMake]) /// and specify which Qt modules you are linking, ommitting the `Qt` prefix (`"Core"` /// rather than `"QtCore"`). - // - // TODO: is there a better name for this method or a sane way to create a default QtInstallation? - pub fn new_with_default_installation(qt_modules: Vec<String>) -> anyhow::Result<Self> { - #[cfg(feature = "qmake")] + /// + /// Currently this function is only available when the `qmake` feature is enabled. + /// Use [Self::with_installation] to create a [QtBuild] with a custom [QtInstallation]. + #[cfg(feature = "qmake")] + pub fn new(qt_modules: Vec<String>) -> anyhow::Result<Self> { let qt_installation = Box::new(QtInstallationQMake::new()?); - #[cfg(not(feature = "qmake"))] - unsupported!("Only qmake feature is supported"); - - Ok(Self::new(qt_installation, qt_modules)) + Ok(Self::with_installation(qt_installation, qt_modules)) } /// Create a [QtBuild] using the given [QtInstallation] and specify which /// Qt modules you are linking, ommitting the `Qt` prefix (`"Core"` rather than `"QtCore"`). - pub fn new(qt_installation: Box<dyn QtInstallation>, mut qt_modules: Vec<String>) -> Self { + 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_string()); } diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 72993b6ed..51f6df496 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -6,7 +6,7 @@ use cxx_qt_build::CxxQtBuilder; fn main() { - let qtbuild = qt_build_utils::QtBuild::new_with_default_installation(vec!["Core".to_owned()]) + let qtbuild = qt_build_utils::QtBuild::new(vec!["Core".to_owned()]) .expect("Could not find Qt installation"); let mut builder = CxxQtBuilder::new()