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 241e55e
Show file tree
Hide file tree
Showing 22 changed files with 330 additions and 175 deletions.
159 changes: 120 additions & 39 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,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 +214,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 +239,86 @@ 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());
}

/// Metadata for registering a QML module with [CxxQtBuilder::qml_module]
pub struct QmlModule<'a, A, B>
where
A: AsRef<Path>,
// Use a separate generic to allow using different types that impl AsRef<Path>
B: AsRef<Path>,
{
/// The URI of the QML module
pub uri: &'a str,
/// The major version of the QML module
pub version_major: usize,
/// The minor version of the QML module
pub version_minor: usize,
/// The .rs files containing a `#[cxx_qt::bridge]` module with at least one QObject type annotated with `#[qml_element]`
pub rust_files: &'a [A],
/// `.qml` files included in the module
pub qml_files: &'a [B],
/// Other QRC resources (such as images) included in the module
//
// Reuse the `A` generic from rust_files to allow the compiler to infer the
// type when constructing the struct with Default::default. Using a separate
// generic for this field would be more flexible, but it would require users
// to explicitly specify the type even for an empty slice (like `&[] as &[&str; 0]`)
// and an empty slice is likely desired in most cases; most users probably don't
// care about this field.
pub qrc_files: &'a [A],
}

impl<'a, A, B> Default for QmlModule<'a, A, B>
where
A: AsRef<Path>,
B: AsRef<Path>,
{
fn default() -> Self {
QmlModule {
uri: "com.example.cxx_qt_module",
version_major: 1,
version_minor: 0,
rust_files: &[],
qml_files: &[],
qrc_files: &[],
}
}
}

/// Same as [QmlModule], but this struct owns the data instead of references it.
/// This avoids needing to specify generics to instantiate a [CxxQtBuilder], which
/// contains a `Vec<OwningQmlModule>` member.
struct OwningQmlModule {
pub uri: String,
pub version_major: usize,
pub version_minor: usize,
pub rust_files: Vec<PathBuf>,
pub qml_files: Vec<PathBuf>,
pub qrc_files: Vec<PathBuf>,
}

fn collect_pathbuf_vec(asref: &[impl AsRef<Path>]) -> Vec<PathBuf> {
asref.iter().map(|p| p.as_ref().to_path_buf()).collect()
}

impl<A: AsRef<Path>, B: AsRef<Path>> From<QmlModule<'_, A, B>> for OwningQmlModule {
fn from(other: QmlModule<'_, A, B>) -> Self {
OwningQmlModule {
uri: other.uri.to_string(),
version_major: other.version_major,
version_minor: other.version_minor,
rust_files: collect_pathbuf_vec(other.rust_files),
qml_files: collect_pathbuf_vec(other.qml_files),
qrc_files: collect_pathbuf_vec(other.qrc_files),
}
}
}

/// Run cxx-qt's C++ code generator on Rust modules marked with the `cxx_qt::bridge` macro, compile
Expand Down Expand Up @@ -297,7 +359,7 @@ pub struct CxxQtBuilder {
qobject_headers: Vec<PathBuf>,
qrc_files: Vec<PathBuf>,
qt_modules: HashSet<String>,
qml_modules: Vec<QmlModule>,
qml_modules: Vec<OwningQmlModule>,
cc_builder: cc::Build,
}

Expand Down Expand Up @@ -374,29 +436,42 @@ impl CxxQtBuilder {
self
}

/// Register a QML module at build time
pub fn qml_module(
/// 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.
///
/// ```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,
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 {
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, uri, version_major, version_minor);
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 Down Expand Up @@ -532,6 +607,8 @@ 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 {
Expand All @@ -548,16 +625,20 @@ impl CxxQtBuilder {
}
}

let qml_type_registration_files = qtbuild.register_qml_types(
let qml_type_registration_files = qtbuild.register_qml_module(
&qml_metatypes_json,
&qml_module.uri,
qml_module.version_major,
qml_module.version_minor,
&qml_module.uri,
lib_name,
&qml_module.qml_files,
&qml_module.qrc_files,
);
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.rcc);
self.cc_builder.define("QT_STATICPLUGIN", None);
cc_builder_whole_archive_files_added = true;
}
Expand Down Expand Up @@ -599,6 +680,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 241e55e

Please sign in to comment.