1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
// SPDX-FileContributor: Be Wilson <be.wilson@kdab.com>
// SPDX-FileContributor: Gerhard de Clercq <gerhard.declercq@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#![deny(missing_docs)]
//! This crate provides a builder which parses given Rust source code to search
//! for CXX-Qt or CXX macros and generate any resulting C++ code. It also builds
//! the C++ code into a binary with any cxx-qt-lib code and Qt linked.
mod diagnostics;
use diagnostics::{Diagnostic, GeneratedError};
mod qml_modules;
use qml_modules::OwningQmlModule;
pub use qml_modules::QmlModule;
use convert_case::{Case, Casing};
use quote::ToTokens;
use std::{
collections::HashSet,
env,
fs::File,
io::Write,
path::{Path, PathBuf},
};
use cxx_qt_gen::{
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks,
GeneratedRustBlocks, Parser,
};
// TODO: we need to eventually support having multiple modules defined in a single file. This
// is currently an issue because we are using the Rust file name to derive the cpp file name
// and are blindly re-writing files.
//
// As we use struct names for the QObject files, we should actually be able to support multiple
// QObject macros and at most one "raw CXX" macro per file already. For now this remains a TODO
// as to keep things simpler. We also want to able to warn users about duplicate names eventually.
struct GeneratedCppFilePaths {
plain_cpp: PathBuf,
qobject: Option<PathBuf>,
qobject_header: Option<PathBuf>,
}
struct GeneratedCpp {
cxx_qt: Option<CppFragment>,
cxx: cxx_gen::GeneratedCode,
file_ident: String,
}
impl GeneratedCpp {
/// Generate QObject and cxx header/source C++ file contents
pub fn new(rust_file_path: impl AsRef<Path>) -> Result<Self, Diagnostic> {
let to_diagnostic = |err| Diagnostic::new(rust_file_path.as_ref().to_owned(), err);
let rust_file_path = rust_file_path.as_ref();
let file = parse_qt_file(rust_file_path)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let mut cxx_qt = None;
// TODO: later change how the resultant filename is chosen, can we match the input file like
// CXX does?
//
// For CXX-Qt generating one header per QObject likely makes sense, but what happens with CXX data?
// for now this uses the module ident
let mut file_ident: String = "".to_owned();
let mut tokens = proc_macro2::TokenStream::new();
// Add any attributes in the file into the tokenstream
for attr in &file.attrs {
tokens.extend(attr.into_token_stream());
}
// Loop through the items looking for any CXX or CXX-Qt blocks
for item in &file.items {
match item {
CxxQtItem::Cxx(m) => {
// TODO: later we will allow for multiple CXX or CXX-Qt blocks in one file
if !file_ident.is_empty() {
panic!(
"Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
The file {} has more than one of these.",
rust_file_path.display());
}
file_ident = m.ident.to_string().to_case(Case::Snake);
tokens.extend(m.into_token_stream());
}
CxxQtItem::CxxQt(m) => {
// TODO: later we will allow for multiple CXX or CXX-Qt blocks in one file
if !file_ident.is_empty() {
panic!(
"Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
The file {} has more than one of these.",
rust_file_path.display());
}
let parser = Parser::from(m.clone())
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let generated_cpp = GeneratedCppBlocks::from(&parser)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
// TODO: we'll have to extend the C++ data here rather than overwriting
// assuming we share the same file
cxx_qt = Some(write_cpp(&generated_cpp));
let generated_rust = GeneratedRustBlocks::from(&parser)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let rust_tokens = write_rust(&generated_rust);
file_ident = parser.cxx_file_stem.clone();
// We need to do this and can't rely on the macro, as we need to generate the
// CXX bridge Rust code that is then fed into the cxx_gen generation.
tokens.extend(rust_tokens);
}
CxxQtItem::Item(item) => {
tokens.extend(item.into_token_stream());
}
}
}
let opt = cxx_gen::Opt::default();
let cxx = cxx_gen::generate_header_and_cc(tokens, &opt)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
Ok(GeneratedCpp {
cxx_qt,
cxx,
file_ident,
})
}
/// Write generated .cpp and .h files to specified directories. Returns the paths of all files written.
pub fn write_to_directories(
self,
cpp_directory: impl AsRef<Path>,
header_directory: impl AsRef<Path>,
) -> GeneratedCppFilePaths {
let cpp_directory = cpp_directory.as_ref();
let header_directory = header_directory.as_ref();
for directory in [cpp_directory, header_directory] {
std::fs::create_dir_all(directory)
.expect("Could not create directory to write cxx-qt generated files");
}
let mut cpp_file_paths = GeneratedCppFilePaths {
plain_cpp: PathBuf::new(),
qobject: None,
qobject_header: None,
};
if let Some(cxx_qt_generated) = &self.cxx_qt {
let header_path = PathBuf::from(format!(
"{}/{}.cxxqt.h",
header_directory.display(),
self.file_ident
));
let mut header =
File::create(&header_path).expect("Could not create cxx-qt header file");
let header_generated = match cxx_qt_generated {
CppFragment::Pair { header, source: _ } => header,
CppFragment::Header(header) => header,
CppFragment::Source(_) => panic!("Unexpected call for source fragment."),
};
header
.write_all(header_generated.as_bytes())
.expect("Could not write cxx-qt header file");
cpp_file_paths.qobject_header = Some(header_path);
let cpp_path = PathBuf::from(format!(
"{}/{}.cxxqt.cpp",
cpp_directory.display(),
self.file_ident
));
let mut cpp = File::create(&cpp_path).expect("Could not create cxx-qt source file");
let source_generated = match cxx_qt_generated {
CppFragment::Pair { header: _, source } => source,
CppFragment::Header(_) => panic!("Unexpected call for header fragment."),
CppFragment::Source(source) => source,
};
cpp.write_all(source_generated.as_bytes())
.expect("Could not write cxx-qt source file");
cpp_file_paths.qobject = Some(cpp_path);
}
let header_path = PathBuf::from(format!(
"{}/{}.cxx.h",
header_directory.display(),
self.file_ident
));
let mut header = File::create(header_path).expect("Could not create cxx header file");
header
.write_all(&self.cxx.header)
.expect("Could not write cxx header file");
let cpp_path = PathBuf::from(format!(
"{}/{}.cxx.cpp",
cpp_directory.display(),
self.file_ident
));
let mut cpp = File::create(&cpp_path).expect("Could not create cxx source file");
cpp.write_all(&self.cxx.implementation)
.expect("Could not write cxx source file");
cpp_file_paths.plain_cpp = cpp_path;
cpp_file_paths
}
}
/// Generate C++ files from a given list of Rust files, returning the generated paths
fn generate_cxxqt_cpp_files(
rs_source: &[impl AsRef<Path>],
header_dir: impl AsRef<Path>,
) -> Vec<GeneratedCppFilePaths> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut generated_file_paths: Vec<GeneratedCppFilePaths> = Vec::with_capacity(rs_source.len());
for rs_path in rs_source {
let cpp_directory = format!("{}/cxx-qt-gen/src", env::var("OUT_DIR").unwrap());
let path = format!("{manifest_dir}/{}", rs_path.as_ref().display());
println!("cargo:rerun-if-changed={path}");
let generated_code = match GeneratedCpp::new(&path) {
Ok(v) => v,
Err(diagnostic) => {
diagnostic.report();
std::process::exit(1);
}
};
generated_file_paths.push(generated_code.write_to_directories(cpp_directory, &header_dir));
}
generated_file_paths
}
fn panic_duplicate_file_and_qml_module(
path: impl AsRef<Path>,
uri: &str,
version_major: usize,
version_minor: usize,
) {
panic!("CXX-Qt bridge Rust file {} specified in QML module {uri} (version {version_major}.{version_minor}), but also specified via CxxQtBuilder::file. Bridge files must be specified via CxxQtBuilder::file or CxxQtBuilder::qml_module, but not both.", path.as_ref().display());
}
/// Run cxx-qt's C++ code generator on Rust modules marked with the `cxx_qt::bridge` macro, compile
/// the code, and link to Qt. This is the complement of the `cxx_qt::bridge` macro, which the Rust
/// compiler uses to generate the corresponding Rust code. No dependencies besides Qt, a C++17 compiler,
/// and Rust toolchain are required.
///
/// For example, if your `cxx_qt::bridge` module is in a file called `src/lib.rs` within your crate,
/// put this in your [build.rs](https://doc.rust-lang.org/cargo/reference/build-scripts.html):
///
/// ```no_run
/// use cxx_qt_build::CxxQtBuilder;
///
/// CxxQtBuilder::new()
/// .file("src/lib.rs")
/// .build();
/// ```
///
/// If you have multiple major versions of Qt installed (for example, 5 and 6), you can tell
/// [CxxQtBuilder] which one to use by setting the `QT_VERSION_MAJOR` environment variable to when
/// running `cargo build`. Otherwise [CxxQtBuilder] prefers the newer version by default.
///
/// To use [CxxQtBuilder] for a library to link with a C++ application, specify a directory to output
/// cxx-qt's autogenerated headers by having the C++ build system set the `CXXQT_EXPORT_DIR`
/// environment variable before calling `cargo build`. Then, add the same directory path to the C++
/// include paths. Also, set the `QMAKE` environment variable to the path of the `qmake` executable
/// for the Qt installation found by the C++ build system. This ensures that the C++ build system and
/// [CxxQtBuilder] link to the same installation of Qt.
///
/// Under the hood, [CxxQtBuilder] uses [cc::Build], which allows compiling aditional C++ files as well.
/// Refer to [CxxQtBuilder::cc_builder] for details.
///
/// In addition to autogenerating and building QObject C++ subclasses, manually written QObject
/// subclasses can be parsed by moc and built using [CxxQtBuilder::qobject_header].
#[derive(Default)]
pub struct CxxQtBuilder {
rust_sources: Vec<PathBuf>,
qobject_headers: Vec<PathBuf>,
qrc_files: Vec<PathBuf>,
qt_modules: HashSet<String>,
qml_modules: Vec<OwningQmlModule>,
cc_builder: cc::Build,
}
impl CxxQtBuilder {
/// Create a new builder
pub fn new() -> Self {
let mut qt_modules = HashSet::new();
qt_modules.insert("Core".to_owned());
#[cfg(feature = "qt_gui")]
qt_modules.insert("Gui".to_owned());
#[cfg(feature = "qt_qml")]
qt_modules.insert("Qml".to_owned());
Self {
rust_sources: vec![],
qobject_headers: vec![],
qrc_files: vec![],
qt_modules,
qml_modules: vec![],
cc_builder: cc::Build::new(),
}
}
/// Specify rust file paths to parse through the cxx-qt marco
/// Relative paths are treated as relative to the path of your crate's Cargo.toml file
pub fn file(mut self, rust_source: impl AsRef<Path>) -> Self {
let rust_source = rust_source.as_ref().to_path_buf();
for qml_module in &self.qml_modules {
if qml_module.rust_files.contains(&rust_source) {
panic_duplicate_file_and_qml_module(
&rust_source,
&qml_module.uri,
qml_module.version_major,
qml_module.version_minor,
);
}
}
println!("cargo:rerun-if-changed={}", rust_source.display());
self.rust_sources.push(rust_source);
self
}
/// Generate C++ files from [Qt resource .qrc files](https://doc.qt.io/qt-6/resources.html).
/// The generated file needs to be `#include`d in another .cpp file. For example:
/// ```no_run
/// # use cxx_qt_build::CxxQtBuilder;
/// CxxQtBuilder::new()
/// .file("src/cxxqt_module.rs")
/// .qrc("src/my_resources.qrc")
/// .cc_builder(|cc| {
/// cc.file("file_with_include.cpp");
/// })
/// .build();
/// ```
///
/// In `file_with_include.cpp`:
/// ```C++
/// #include "my_resources.qrc.cpp"
/// ```
///
/// You also need to [explicitly load](https://doc.qt.io/qt-6/resources.html#explicit-loading-and-unloading-of-embedded-resources)
/// the resources in your .cpp file by calling `qInitResources()` once before starting your application.
pub fn qrc(mut self, qrc_file: impl AsRef<Path>) -> Self {
let qrc_file = qrc_file.as_ref();
self.qrc_files.push(qrc_file.to_path_buf());
println!("cargo:rerun-if-changed={}", qrc_file.display());
self
}
/// Link additional [Qt modules](https://doc.qt.io/qt-6/qtmodules.html).
/// Specify their names without the `Qt` prefix, for example `"Widgets"`.
/// The Core and any feature enabled modules are linked automatically; there is no need to specify them.
pub fn qt_module(mut self, module: &str) -> Self {
self.qt_modules.insert(module.to_owned());
self
}
/// Register a QML module at build time. The `rust_files` of the [QmlModule] struct
/// should contain `#[cxx_qt::bridge]` modules with QObject types annotated with `#[qml_element]`.
///
/// The QmlModule struct's `qml_files` are registered with the [Qt Resource System](https://doc.qt.io/qt-6/resources.html) in
/// the [default QML import path](https://doc.qt.io/qt-6/qtqml-syntax-imports.html#qml-import-path) `qrc:/qt/qml/uri/of/module/`.
/// Additional resources such as images can be added to the Qt resources for the QML module by specifying
/// the `qrc_files` field.
///
/// When using Qt 6, this will [run qmlcachegen](https://doc.qt.io/qt-6/qtqml-qtquick-compiler-tech.html)
/// to compile the specified `.qml` files ahead-of-time.
///
/// ```no_run
/// use cxx_qt_build::{CxxQtBuilder, QmlModule};
///
/// CxxQtBuilder::new()
/// .qml_module(QmlModule {
/// uri: "com.kdab.cxx_qt.demo",
/// rust_files: &["src/cxxqt_object.rs"],
/// qml_files: &["qml/main.qml"],
/// ..Default::default()
/// })
/// .build();
/// ```
pub fn qml_module<A: AsRef<Path>, B: AsRef<Path>>(
mut self,
qml_module: QmlModule<A, B>,
) -> CxxQtBuilder {
let qml_module = OwningQmlModule::from(qml_module);
for path in &qml_module.rust_files {
if self.rust_sources.contains(path) {
panic_duplicate_file_and_qml_module(
path,
&qml_module.uri,
qml_module.version_major,
qml_module.version_minor,
);
}
}
self.qml_modules.push(qml_module);
self
}
/// Specify a C++ header containing a Q_OBJECT macro to run [moc](https://doc.qt.io/qt-6/moc.html) on.
/// This allows building QObject C++ subclasses besides the ones autogenerated by cxx-qt.
pub fn qobject_header(mut self, path: impl AsRef<Path>) -> Self {
let path = path.as_ref();
self.qobject_headers.push(path.to_owned());
println!("cargo:rerun-if-changed={}", path.display());
self
}
/// Use a closure to run additional customization on [CxxQtBuilder]'s internal [cc::Build]
/// before calling [CxxQtBuilder::build]. This allows to add extra include paths, compiler flags,
/// or anything else available via [cc::Build]'s API. For example, to add an include path for
/// manually written C++ headers located in a directory called `include` within your crate:
///
/// ```no_run
/// # use cxx_qt_build::CxxQtBuilder;
///
/// CxxQtBuilder::new()
/// .file("src/lib.rs")
/// .cc_builder(|cc| {
/// cc.include("include");
/// })
/// .build();
/// ```
pub fn cc_builder(mut self, mut callback: impl FnMut(&mut cc::Build)) -> Self {
callback(&mut self.cc_builder);
self
}
/// Generate and compile cxx-qt C++ code, as well as compile any additional files from
/// [CxxQtBuilder::qobject_header] and [CxxQtBuilder::cc_builder].
pub fn build(mut self) {
// Ensure that the linker is setup correctly for Cargo builds
qt_build_utils::setup_linker();
// The include directory needs to be namespaced by crate name when exporting for a C++ build system,
// but for using cargo build without a C++ build system, OUT_DIR is already namespaced by crate name.
let header_root = match env::var("CXXQT_EXPORT_DIR") {
Ok(export_dir) => format!("{export_dir}/{}", env::var("CARGO_PKG_NAME").unwrap()),
Err(_) => env::var("OUT_DIR").unwrap(),
};
let generated_header_dir = format!("{header_root}/cxx-qt-gen");
let mut qtbuild = qt_build_utils::QtBuild::new(self.qt_modules.into_iter().collect())
.expect("Could not find Qt installation");
qtbuild.cargo_link_libraries(&mut self.cc_builder);
// Write cxx-qt-gen, cxx-qt-lib and cxx headers
cxx_qt_gen::write_headers(format!("{header_root}/cxx-qt-common"));
cxx_qt_lib_headers::write_headers(format!("{header_root}/cxx-qt-lib"));
std::fs::create_dir_all(format!("{header_root}/rust"))
.expect("Could not create cxx header directory");
let h_path = format!("{header_root}/rust/cxx.h");
// Wrap the File in a block scope so the file is closed before the compiler is run.
// Otherwise MSVC fails to open cxx.h because the process for this build script already has it open.
{
let mut header = File::create(h_path).expect("Could not create cxx.h");
write!(header, "{}", cxx_gen::HEADER).expect("Could not write cxx.h");
}
// Setup compiler
// Static QML plugin and Qt resource initialization need to be linked with +whole-archive
// because they use static variables which need to be initialized before main
// (regardless of whether main is in Rust or C++). Normally linkers only copy symbols referenced
// from within main when static linking, which would result in discarding those static variables.
// Use a separate cc::Build for the little amount of code that needs to be linked with +whole-archive
// to avoid bloating the binary.
let mut cc_builder_whole_archive = cc::Build::new();
cc_builder_whole_archive.link_lib_modifier("+whole-archive");
// Ensure we are not using rustc 1.69
if let Some(version) = version_check::Version::read() {
let (major, minor, _) = version.to_mmp();
if major == 1 && minor == 69 {
// rustc 1.69 had a regression where +whole-archive wouldn't
// work without specifying -bundle.
// https://github.com/rust-lang/rust/pull/110917
//
// However, we need to not have -bundle for qt-static-initializers to work
// with CMake builds, otherwise the statement below occurs where it's missing
// from the final binary.
//
// When building a staticlib -bundle means that the native static library
// is simply not included into the archive and some higher level build
// system will need to add it later during linking of the final binary.
// https://doc.rust-lang.org/rustc/command-line-arguments.html#option-l-link-lib
panic!("rustc 1.69.x is not supported with CXX-Qt due to a compiler bug.\nSee: https://github.com/rust-lang/rust/pull/110917\nPlease update your compiler using 'rustup update' or use an older compiler.");
}
}
for builder in [&mut self.cc_builder, &mut cc_builder_whole_archive] {
// Note, ensure our settings stay in sync across cxx-qt-build and cxx-qt-lib
builder.cpp(true);
// MSVC
builder.flag_if_supported("/std:c++17");
builder.flag_if_supported("/Zc:__cplusplus");
builder.flag_if_supported("/permissive-");
builder.flag_if_supported("/bigobj");
// GCC + Clang
builder.flag_if_supported("-std=c++17");
// MinGW requires big-obj otherwise debug builds fail
builder.flag_if_supported("-Wa,-mbig-obj");
// Enable Qt Gui in C++ if the feature is enabled
#[cfg(feature = "qt_gui")]
builder.define("CXX_QT_GUI_FEATURE", None);
// Enable Qt Gui in C++ if the feature is enabled
#[cfg(feature = "qt_qml")]
builder.define("CXX_QT_QML_FEATURE", None);
for include_dir in qtbuild.include_paths() {
builder.include(&include_dir);
}
builder.include(&header_root);
builder.include(&generated_header_dir);
}
// Generate files
for files in generate_cxxqt_cpp_files(&self.rust_sources, &generated_header_dir) {
self.cc_builder.file(files.plain_cpp);
if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header) {
self.cc_builder.file(&qobject);
self.qobject_headers.push(qobject_header);
}
}
// Run moc on C++ headers with Q_OBJECT macro
for qobject_header in self.qobject_headers {
let moc_products = qtbuild.moc(&qobject_header, None);
self.cc_builder.file(moc_products.cpp);
}
let mut cc_builder_whole_archive_files_added = false;
let lib_name = "cxx-qt-generated";
// Bridges for QML modules are handled separately because
// the metatypes_json generated by moc needs to be passed to qmltyperegistrar
for qml_module in self.qml_modules {
let mut qml_metatypes_json = Vec::new();
for files in generate_cxxqt_cpp_files(&qml_module.rust_files, &generated_header_dir) {
self.cc_builder.file(files.plain_cpp);
if let (Some(qobject), Some(qobject_header)) = (files.qobject, files.qobject_header)
{
self.cc_builder.file(&qobject);
let moc_products = qtbuild.moc(qobject_header, Some(&qml_module.uri));
self.cc_builder.file(moc_products.cpp);
qml_metatypes_json.push(moc_products.metatypes_json);
}
}
let qml_module_registration_files = qtbuild.register_qml_module(
&qml_metatypes_json,
&qml_module.uri,
qml_module.version_major,
qml_module.version_minor,
lib_name,
&qml_module.qml_files,
&qml_module.qrc_files,
);
self.cc_builder
.file(qml_module_registration_files.qmltyperegistrar);
self.cc_builder.file(qml_module_registration_files.plugin);
cc_builder_whole_archive.file(qml_module_registration_files.plugin_init);
cc_builder_whole_archive.file(qml_module_registration_files.rcc);
for qmlcachegen_file in qml_module_registration_files.qmlcachegen {
cc_builder_whole_archive.file(qmlcachegen_file);
}
self.cc_builder.define("QT_STATICPLUGIN", None);
cc_builder_whole_archive_files_added = true;
// If any of the files inside the qml module change, then trigger a rerun
for path in qml_module.qml_files.iter().chain(
qml_module
.rust_files
.iter()
.chain(qml_module.qrc_files.iter()),
) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
for qrc_file in self.qrc_files {
cc_builder_whole_archive.file(qtbuild.qrc(&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) {
println!("cargo:rerun-if-changed={}", qrc_inner_file.display());
}
cc_builder_whole_archive_files_added = true;
}
// If we are using Qt 5 then write the std_types source
// This registers std numbers as a type for use in QML
//
// Note that we need this to be compiled into the whole_archive builder
// as they are stored in statics in the source.
//
// TODO: once +whole-archive and +bundle are allowed together in rlibs
// we should be able to move this into cxx-qt so that it's only built
// once rather than for every cxx-qt-build. When this happens also
// ensure that in a multi project that numbers work everywhere.
//
// Also then it should be possible to use CARGO_MANIFEST_DIR/src/std_types_qt5.cpp
// as path for cc::Build rather than copying the .cpp file
//
// https://github.com/rust-lang/rust/issues/108081
// https://github.com/KDAB/cxx-qt/pull/598
if qtbuild.version().major == 5 {
let std_types_contents = include_str!("std_types_qt5.cpp");
let std_types_path = format!(
"{out_dir}/std_types_qt5.cpp",
out_dir = env::var("OUT_DIR").unwrap()
);
let mut source =
File::create(&std_types_path).expect("Could not create std_types source");
write!(source, "{std_types_contents}").expect("Could not write std_types source");
cc_builder_whole_archive.file(&std_types_path);
cc_builder_whole_archive_files_added = true;
}
if cc_builder_whole_archive_files_added {
cc_builder_whole_archive.compile("qt-static-initializers");
}
// Only compile if we have added files to the builder
// otherwise we end up with no static library but ask cargo to link to it which causes an error
if self.cc_builder.get_files().count() > 0 {
self.cc_builder.compile(lib_name);
}
}
}