Skip to content

Commit

Permalink
register QML modules at build time
Browse files Browse the repository at this point in the history
This is a prerequisite for using qmlcachegen
KDAB#242
  • Loading branch information
Be-ing committed Aug 8, 2023
1 parent d45d516 commit 0e9f40e
Show file tree
Hide file tree
Showing 25 changed files with 293 additions and 192 deletions.
95 changes: 41 additions & 54 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use std::{
path::{Path, PathBuf},
};

pub use qt_build_utils::QmlModule;

use cxx_qt_gen::{
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks,
GeneratedRustBlocks, Parser,
Expand All @@ -49,18 +51,6 @@ struct GeneratedCpp {
file_ident: String,
}

/// Metadata for registering a QML module
struct QmlModule {
/// The URI of the QML module
pub uri: String,
/// The minor version of the QML module
pub version_minor: usize,
/// The major version of the QML module
pub version_major: usize,
/// The .rs files with #[qml_element] attribute(s)
pub rust_files: Vec<PathBuf>,
}

impl GeneratedCpp {
/// Generate QObject and cxx header/source C++ file contents
pub fn new(rust_file_path: impl AsRef<Path>) -> Result<Self, Diagnostic> {
Expand Down Expand Up @@ -226,15 +216,15 @@ impl GeneratedCpp {

/// Generate C++ files from a given list of Rust files, returning the generated paths
fn generate_cxxqt_cpp_files(
rs_source: &[PathBuf],
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.display());
let path = format!("{manifest_dir}/{}", rs_path.as_ref().display());
println!("cargo:rerun-if-changed={path}");

let generated_code = match GeneratedCpp::new(&path) {
Expand All @@ -251,12 +241,12 @@ fn generate_cxxqt_cpp_files(
}

fn panic_duplicate_file_and_qml_module(
path: &Path,
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.display());
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
Expand All @@ -270,7 +260,7 @@ fn panic_duplicate_file_and_qml_module(
/// ```no_run
/// use cxx_qt_build::CxxQtBuilder;
///
/// CxxQtBuilder::new()
/// CxxQtBuilder::<&str, &str>::new()
/// .file("src/lib.rs")
/// .build();
/// ```
Expand All @@ -292,16 +282,24 @@ fn panic_duplicate_file_and_qml_module(
/// 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 {
pub struct CxxQtBuilder<'a, A, B>
where
A: AsRef<Path>,
B: AsRef<Path>,
{
rust_sources: Vec<PathBuf>,
qobject_headers: Vec<PathBuf>,
qrc_files: Vec<PathBuf>,
qt_modules: HashSet<String>,
qml_modules: Vec<QmlModule>,
qml_modules: Vec<QmlModule<'a, A, B>>,
cc_builder: cc::Build,
}

impl CxxQtBuilder {
impl<'a, A, B> CxxQtBuilder<'a, A, B>
where
A: AsRef<Path> + PartialEq,
B: AsRef<Path>,
{
/// Create a new builder
pub fn new() -> Self {
let mut qt_modules = HashSet::new();
Expand All @@ -322,18 +320,18 @@ impl CxxQtBuilder {

/// 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();
pub fn file(mut self, rust_source: A) -> Self {
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.uri,
qml_module.version_major,
qml_module.version_minor,
);
}
}
let rust_source = rust_source.as_ref().to_path_buf();
println!("cargo:rerun-if-changed={}", rust_source.display());
self.rust_sources.push(rust_source);
self
Expand All @@ -343,7 +341,7 @@ impl CxxQtBuilder {
/// The generated file needs to be `#include`d in another .cpp file. For example:
/// ```no_run
/// # use cxx_qt_build::CxxQtBuilder;
/// CxxQtBuilder::new()
/// CxxQtBuilder::<&str, &str>::new()
/// .file("src/cxxqt_module.rs")
/// .qrc("src/my_resources.qrc")
/// .cc_builder(|cc| {
Expand Down Expand Up @@ -375,28 +373,18 @@ impl CxxQtBuilder {
}

/// Register a QML module at build time
pub fn qml_module(
mut self,
uri: &str,
version_major: usize,
version_minor: usize,
rust_files: &[impl AsRef<Path>],
) -> Self {
let rust_files: Vec<PathBuf> = rust_files
.iter()
.map(|p| p.as_ref().to_path_buf())
.collect();
for path in &rust_files {
if self.rust_sources.contains(path) {
panic_duplicate_file_and_qml_module(path, uri, version_major, version_minor);
pub fn qml_module(mut self, qml_module: QmlModule<'a, A, B>) -> CxxQtBuilder<'a, A, B> {
for path in qml_module.rust_files {
if self.rust_sources.contains(&path.as_ref().to_path_buf()) {
panic_duplicate_file_and_qml_module(
path,
qml_module.uri,
qml_module.version_major,
qml_module.version_minor,
);
}
}
self.qml_modules.push(QmlModule {
uri: uri.to_owned(),
version_major,
version_minor,
rust_files,
});
self.qml_modules.push(qml_module);
self
}

Expand All @@ -417,7 +405,7 @@ impl CxxQtBuilder {
/// ```no_run
/// # use cxx_qt_build::CxxQtBuilder;
///
/// CxxQtBuilder::new()
/// CxxQtBuilder::<&str, &str>::new()
/// .file("src/lib.rs")
/// .cc_builder(|cc| {
/// cc.include("include");
Expand Down Expand Up @@ -532,32 +520,31 @@ impl CxxQtBuilder {

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) {
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));
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_type_registration_files = qtbuild.register_qml_types(
&qml_metatypes_json,
qml_module.version_major,
qml_module.version_minor,
&qml_module.uri,
);
let qml_type_registration_files =
qtbuild.register_qml_module(&qml_metatypes_json, &qml_module, lib_name);
self.cc_builder
.file(qml_type_registration_files.qmltyperegistrar);
self.cc_builder.file(qml_type_registration_files.plugin);
cc_builder_whole_archive.file(qml_type_registration_files.plugin_init);
cc_builder_whole_archive.file(qml_type_registration_files.qrc);
self.cc_builder.define("QT_STATICPLUGIN", None);
cc_builder_whole_archive_files_added = true;
}
Expand Down Expand Up @@ -599,6 +586,6 @@ impl CxxQtBuilder {
if cc_builder_whole_archive_files_added {
cc_builder_whole_archive.compile("qt-static-initializers");
}
self.cc_builder.compile("cxx-qt-gen");
self.cc_builder.compile(lib_name);
}
}
Loading

0 comments on commit 0e9f40e

Please sign in to comment.