Skip to content

Commit 96d3233

Browse files
qt-build-utils: build system changes to have a QtInstallation (#1286)
* qt-build-utils: use u64 as major type this allows us to move to SemVer * qt-build-utils: add anyhow and semver crates for later * qt-build-utils: add a QtTool enum for later * qt-build-utils: add QtInstallation trait * qt-build-utils: add qmake implementation of QtInstallation * 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 * cxx-qt-build: use version from QtInstallation * qt-build-utils: ask QtInstallation for location of Qt tools * qt-build-utils: split out common is_*_target * qt-build-utils: move linking of Qt modules to Qt Installation * qt-build-utils: move include paths to qmake installation * qt-build-utils: remove qmake executable path as it is in installation * qt-build-utils: parse_cflags is now only needed for qmake builds * qt-build-utils: fix docs and add new method with QtInstallation arg * qt-build-utils: add back a cache for finding the Qt tool * qt-build-utils: split error into separate module * qt-build-utils: split initialiser into module * qt-build-utils: move tool to a folder * qt-build-utils: split out rcc into a tool * qt-build-utils: split Moc into a separate tool * qt-build-utils: have a command writable path method for tools * qt-build-utils: split out qmltyperegistrar into a separate tool * qt-build-utils: split out qmlcachegen into a separate tool * cxx-qt-build: use new rcc method instead of qrc * cxx-qt-build: use moc() method to retrieve the tool not compile * qt-build-utils: Return Result in try_find_tool This now also prints out the paths that were attempted. * 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. --------- Co-authored-by: Andrew Hayzen <[email protected]>
1 parent 4979207 commit 96d3233

File tree

16 files changed

+1223
-872
lines changed

16 files changed

+1223
-872
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ cxx-gen = "0.7.121"
5959
proc-macro2 = "1.0"
6060
syn = { version = "2.0", features = ["extra-traits", "full"] }
6161
quote = "1.0"
62+
semver = "1.0"
6263
serde = { version = "1.0", features = ["derive"] }
6364
serde_json = "1.0"
6465
thiserror = "1.0"

crates/cxx-qt-build/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ proc-macro2.workspace = true
2121
quote.workspace = true
2222
qt-build-utils = { workspace = true, features = ["serde"] }
2323
codespan-reporting = "0.11"
24-
version_check = "0.9"
2524
serde.workspace = true
2625
serde_json = "1.0"
26+
semver.workspace = true
2727

2828
[features]
2929
link_qt_object_files = ["qt-build-utils/link_qt_object_files"]

crates/cxx-qt-build/src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use qml_modules::OwningQmlModule;
3232
pub use qml_modules::QmlModule;
3333

3434
pub use qt_build_utils::MocArguments;
35-
use qt_build_utils::SemVer;
3635
use quote::ToTokens;
36+
use semver::Version;
3737
use std::{
3838
collections::HashSet,
3939
env,
@@ -600,7 +600,7 @@ impl CxxQtBuilder {
600600
}
601601
}
602602

603-
fn define_qt_version_cfg_variables(version: &SemVer) {
603+
fn define_qt_version_cfg_variables(version: Version) {
604604
// Allow for Qt 5 or Qt 6 as valid values
605605
CxxQtBuilder::define_cfg_check_variable(
606606
"cxxqt_qt_version_major".to_owned(),
@@ -702,7 +702,7 @@ impl CxxQtBuilder {
702702
moc_arguments,
703703
} in &self.qobject_headers
704704
{
705-
let moc_products = qtbuild.moc(path, moc_arguments.clone());
705+
let moc_products = qtbuild.moc().compile(path, moc_arguments.clone());
706706
// Include the moc folder
707707
if let Some(dir) = moc_products.cpp.parent() {
708708
self.cc_builder.include(dir);
@@ -827,7 +827,7 @@ impl CxxQtBuilder {
827827
}
828828

829829
cc_builder.file(&qobject);
830-
let moc_products = qtbuild.moc(
830+
let moc_products = qtbuild.moc().compile(
831831
qobject_header,
832832
MocArguments::default().uri(qml_module.uri.clone()),
833833
);
@@ -852,8 +852,10 @@ impl CxxQtBuilder {
852852
&qml_module.qml_files,
853853
&qml_module.qrc_files,
854854
);
855+
if let Some(qmltyperegistrar) = qml_module_registration_files.qmltyperegistrar {
856+
cc_builder.file(qmltyperegistrar);
857+
}
855858
cc_builder
856-
.file(qml_module_registration_files.qmltyperegistrar)
857859
.file(qml_module_registration_files.plugin)
858860
// In comparison to the other RCC files, we don't need to link this with whole-archive or
859861
// anything like that.
@@ -1030,12 +1032,12 @@ extern "C" bool {init_fun}() {{
10301032
.iter()
10311033
.map(|qrc_file| {
10321034
// Also ensure that each of the files in the qrc can cause a change
1033-
for qrc_inner_file in qtbuild.qrc_list(&qrc_file) {
1035+
for qrc_inner_file in qtbuild.rcc().list(qrc_file) {
10341036
println!("cargo::rerun-if-changed={}", qrc_inner_file.display());
10351037
}
10361038
// We need to link this using an object file or +whole-achive, the static initializer of
10371039
// the qrc file isn't lost.
1038-
qtbuild.qrc(&qrc_file)
1040+
qtbuild.rcc().compile(qrc_file)
10391041
})
10401042
.collect()
10411043
}

crates/qt-build-utils/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ repository.workspace = true
1414
rust-version.workspace = true
1515

1616
[dependencies]
17+
anyhow = "1.0"
1718
cc.workspace = true
19+
semver.workspace = true
1820
serde = { workspace = true, optional = true }
19-
versions = "6.3"
2021
thiserror.workspace = true
2122

2223
[features]
24+
# TODO: should we default to qmake or let downstream crates specify, such as cxx-qt-build
25+
default = ["qmake"]
2326
# When Cargo links an executable, whether a bin crate or test executable,
2427
# and Qt 6 is linked statically, this feature must be enabled to link
2528
# unarchived .o files with static symbols that Qt ships (for example
@@ -31,6 +34,7 @@ thiserror.workspace = true
3134
#
3235
# When linking Qt dynamically, this makes no difference.
3336
link_qt_object_files = []
37+
qmake = []
3438
serde = ["dep:serde"]
3539

3640
[lints]

crates/qt-build-utils/src/error.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
use thiserror::Error;
7+
8+
#[derive(Error, Debug)]
9+
/// Errors that can occur while using [crate::QtBuild]
10+
pub enum QtBuildError {
11+
/// `QMAKE` environment variable was set but Qt was not detected
12+
#[error("QMAKE environment variable specified as {qmake_env_var} but could not detect Qt: {error:?}")]
13+
QMakeSetQtMissing {
14+
/// The value of the qmake environment variable when the error occurred
15+
qmake_env_var: String,
16+
/// The inner error that occurred
17+
error: Box<anyhow::Error>,
18+
},
19+
/// Qt was not found
20+
#[error("Could not find Qt")]
21+
QtMissing,
22+
/// Executing `qmake -query` failed
23+
#[error("Executing `qmake -query` failed: {0:?}")]
24+
QmakeFailed(#[from] std::io::Error),
25+
/// `QT_VERSION_MAJOR` environment variable was specified but could not be parsed as an integer
26+
#[error("QT_VERSION_MAJOR environment variable specified as {qt_version_major_env_var} but could not parse as integer: {source:?}")]
27+
QtVersionMajorInvalid {
28+
/// The Qt major version from `QT_VERSION_MAJOR`
29+
qt_version_major_env_var: String,
30+
/// The [std::num::ParseIntError] when parsing the `QT_VERSION_MAJOR`
31+
source: std::num::ParseIntError,
32+
},
33+
/// `QT_VERSION_MAJOR` environment variable was specified but the Qt version specified by `qmake -query QT_VERSION` did not match
34+
#[error("qmake version ({qmake_version}) does not match version specified by QT_VERSION_MAJOR ({qt_version_major})")]
35+
QtVersionMajorDoesNotMatch {
36+
/// The qmake version
37+
qmake_version: u64,
38+
/// The Qt major version from `QT_VERSION_MAJOR`
39+
qt_version_major: u64,
40+
},
41+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
use std::path::PathBuf;
7+
8+
#[doc(hidden)]
9+
#[derive(Clone)]
10+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11+
pub struct Initializer {
12+
pub file: Option<PathBuf>,
13+
pub init_call: Option<String>,
14+
pub init_declaration: Option<String>,
15+
}
16+
17+
impl Initializer {
18+
#[doc(hidden)]
19+
pub fn default_signature(name: &str) -> Self {
20+
Self {
21+
file: None,
22+
init_call: Some(format!("{name}();")),
23+
init_declaration: Some(format!("extern \"C\" bool {name}();")),
24+
}
25+
}
26+
27+
#[doc(hidden)]
28+
// Strip the init files from the public initializers
29+
// For downstream dependencies, it's often enough to just declare the init function and
30+
// call it.
31+
pub fn strip_file(mut self) -> Self {
32+
self.file = None;
33+
self
34+
}
35+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
#[cfg(feature = "qmake")]
7+
pub(crate) mod qmake;
8+
9+
use semver::Version;
10+
use std::path::PathBuf;
11+
12+
use crate::QtTool;
13+
14+
/// A Qt Installation that can be used by cxx-qt-build to run Qt related tasks
15+
///
16+
/// Note that it is the responsbility of the QtInstallation implementation
17+
/// to print any cargo::rerun-if-changed lines
18+
pub trait QtInstallation {
19+
/// Return the include paths for Qt, including Qt module subdirectories.
20+
///
21+
/// This is intended to be passed to whichever tool you are using to invoke the C++ compiler.
22+
fn include_paths(&self, qt_modules: &[String]) -> Vec<PathBuf>;
23+
/// Configure the given cc::Build and cargo to link to the given Qt modules
24+
///
25+
// TODO: should we hand in a cc::Build or should we instead return a struct
26+
// with details of the rustc-link-lib / search paths ? and then have the
27+
// calling function apply those and any flags to the cc::Build?
28+
// eg return the following?
29+
//
30+
// pub struct LinkArgs {
31+
// builder_flag_if_supported: Vec<String>,
32+
// builder_object: Vec<String>,
33+
// rustc_link_arg: Vec<String>,
34+
// rustc_link_lib: Vec<String>,
35+
// rustc_link_search: Vec<String>,
36+
// }
37+
fn link_modules(&self, builder: &mut cc::Build, qt_modules: &[String]);
38+
/// Find the path to a given Qt tool for the Qt installation
39+
fn try_find_tool(&self, tool: QtTool) -> anyhow::Result<PathBuf>;
40+
/// Version of the detected Qt installation
41+
fn version(&self) -> Version;
42+
}

0 commit comments

Comments
 (0)