From c26161c9df912e1c51d7252441e4e546ec7b3123 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Mon, 29 May 2023 11:27:23 +0200 Subject: [PATCH] Add Cxx-Qt-Macro crate This allows us to add other items to cxx-qt, other than proc-macros. e.g. a QtObject trait or similar. It might even allow us to move CxxQtThread into cxx-qt, instead of cxx-qt-lib. --- Cargo.toml | 1 + crates/cxx-qt-macro/Cargo.toml | 28 +++++ crates/cxx-qt-macro/src/lib.rs | 181 ++++++++++++++++++++++++++++++++ crates/cxx-qt/Cargo.toml | 12 +-- crates/cxx-qt/src/lib.rs | 185 ++------------------------------- 5 files changed, 221 insertions(+), 186 deletions(-) create mode 100644 crates/cxx-qt-macro/Cargo.toml create mode 100644 crates/cxx-qt-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b4e247921..ccd960d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ version = "0.5.3" # we publish, otherwise crates.io complains as it doesn't know the version. [workspace.dependencies] cxx-qt = { path = "crates/cxx-qt" } +cxx-qt-macro = { path = "crates/cxx-qt-macro" } cxx-qt-build = { path = "crates/cxx-qt-build" } cxx-qt-gen = { path = "crates/cxx-qt-gen", version = "0.5.3" } cxx-qt-lib = { path = "crates/cxx-qt-lib" } diff --git a/crates/cxx-qt-macro/Cargo.toml b/crates/cxx-qt-macro/Cargo.toml new file mode 100644 index 000000000..7f6531f9d --- /dev/null +++ b/crates/cxx-qt-macro/Cargo.toml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# SPDX-FileContributor: Gerhard de Clercq +# SPDX-FileContributor: Leon Matthes +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +[package] +name = "cxx-qt-macro" +version.workspace = true +authors = ["Andrew Hayzen ","Leon Matthes ", "Gerhard de Clercq "] +edition.workspace = true +license.workspace = true +description = "A set of macros for Qt/C++ interop in Rust" +repository.workspace = true +homepage = "https://kdab.github.io/cxx-qt/book/" + +[lib] +proc-macro = true + +[dependencies] +cxx-qt-gen.workspace = true +proc-macro2.workspace = true +syn.workspace = true + +[dev-dependencies] +cxx.workspace = true +cxx-qt-lib.workspace = true +cxx-qt.workspace = true diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs new file mode 100644 index 000000000..d765032f9 --- /dev/null +++ b/crates/cxx-qt-macro/src/lib.rs @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// SPDX-FileContributor: Gerhard de Clercq +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#![deny(missing_docs)] +//! The cxx-qt-macro crate provides the procedural attribute macros which are used with cxx-qt. + +use proc_macro::TokenStream; +use syn::{parse_macro_input, ItemMod}; + +use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser}; + +/// A procedural macro which generates a QObject for a struct inside a module. +/// +/// # Example +/// +/// ```rust +/// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")] +/// mod my_object { +/// #[cxx_qt::qobject] +/// #[derive(Default)] +/// # // Note that we can't use properties as this confuses the linker on Windows +/// pub struct MyObject; +/// +/// impl qobject::MyObject { +/// #[qinvokable] +/// fn invokable(&self, a: i32, b: i32) -> i32 { +/// a + b +/// } +/// } +/// } +/// +/// # // Note that we need a fake main for doc tests to build +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { + // Parse the TokenStream of a macro + // this triggers a compile failure if the tokens fail to parse. + let mut module = parse_macro_input!(input as ItemMod); + + // Macros do not typically need to do anything with their own attribute name, + // so rustc does not include that in the `args` or `input` TokenStreams. + // + // However, other code paths that use the parser do not enter from a macro invocation, + // so they rely on parsing the `cxx_qt::bridge` attribute to identify where to start parsing. + // + // To keep the inputs to the parser consistent for all code paths, + // add the attribute to the module before giving it to the parser. + let args_input = format!("#[cxx_qt::bridge({args})] mod dummy;"); + let attrs = syn::parse_str::(&args_input).unwrap().attrs; + module.attrs = attrs.into_iter().chain(module.attrs.into_iter()).collect(); + + // Extract and generate the rust code + extract_and_generate(module) +} + +/// A macro which describes that an enum defines the signals for a QObject. +/// +/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. +/// +/// # Example +/// +/// ```rust +/// #[cxx_qt::bridge] +/// mod my_object { +/// #[cxx_qt::qobject] +/// #[derive(Default)] +/// # // Note that we can't use properties as this confuses the linker on Windows +/// pub struct MyObject; +/// +/// #[cxx_qt::qsignals(MyObject)] +/// pub enum MySignals { +/// Ready, +/// } +/// } +/// +/// # // Note that we need a fake main for doc tests to build +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn qsignals(_args: TokenStream, _input: TokenStream) -> TokenStream { + unreachable!("cxx_qt::qsignals should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") +} + +/// A macro which describes that a struct should be made into a QObject. +/// +/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. +/// +/// # Example +/// +/// ```rust +/// #[cxx_qt::bridge] +/// mod my_object { +/// #[cxx_qt::qobject] +/// #[derive(Default)] +/// # // Note that we can't use properties as this confuses the linker on Windows +/// pub struct MyObject; +/// } +/// +/// # // Note that we need a fake main for doc tests to build +/// # fn main() {} +/// ``` +/// +/// You can also specify a custom base class by using `#[cxx_qt::qobject(base = "QStringListModel")]`, you must then use CXX to add any includes needed. +/// +/// # Example +/// +/// ```rust +/// #[cxx_qt::bridge] +/// mod my_object { +/// #[cxx_qt::qobject(base = "QStringListModel")] +/// #[derive(Default)] +/// # // Note that we can't use properties as this confuses the linker on Windows +/// pub struct MyModel; +/// +/// unsafe extern "C++" { +/// include!(); +/// } +/// } +/// +/// # // Note that we need a fake main for doc tests to build +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn qobject(_args: TokenStream, _input: TokenStream) -> TokenStream { + unreachable!("cxx_qt::qobject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") +} + +/// A macro which allows you to access base class methods from within Rust. +/// +/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. +/// Furthermore, the macro must be placed within the `impl` block of a `qobject::T`. +/// See [the book page](https://kdab.github.io/cxx-qt/book/concepts/inheritance.html) for more +/// details. +/// +/// # Example +/// ``` rust +/// #[cxx_qt::bridge] +/// mod my_object { +/// extern "C++" { +/// include!("cxx-qt-lib/qmodelindex.h"); +/// type QModelIndex = cxx_qt_lib::QModelIndex; +/// } +/// +/// #[cxx_qt::qobject(base="QAbstractListModel")] +/// #[derive(Default)] +/// # // Note that we can't use properties as this confuses the linker on Windows +/// pub struct MyObject; +/// +/// #[cxx_qt::inherit] +/// extern "C++" { +/// // Unsafe to call +/// unsafe fn begin_insert_rows(self: Pin<&mut qobject::MyObject>, parent: &QModelIndex, first: i32, last: i32); +/// } +/// +/// #[cxx_qt::inherit] +/// unsafe extern "C++" { +/// // Safe to call - you are responsible to ensure this is true! +/// fn end_insert_rows(self: Pin<&mut qobject::MyObject>); +/// } +/// } +/// +/// # // Note that we need a fake main for doc tests to build +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn inherit(_args: TokenStream, _input: TokenStream) -> TokenStream { + unreachable!("cxx_qt::inherit should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") +} + +// Take the module and C++ namespace and generate the rust code +fn extract_and_generate(module: ItemMod) -> TokenStream { + Parser::from(module) + .and_then(|parser| GeneratedRustBlocks::from(&parser)) + .map(|generated_rust| write_rust(&generated_rust)) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/crates/cxx-qt/Cargo.toml b/crates/cxx-qt/Cargo.toml index 31cf72421..ac9d04167 100644 --- a/crates/cxx-qt/Cargo.toml +++ b/crates/cxx-qt/Cargo.toml @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company # SPDX-FileContributor: Andrew Hayzen # SPDX-FileContributor: Gerhard de Clercq +# SPDX-FileContributor: Leon Matthes # # SPDX-License-Identifier: MIT OR Apache-2.0 [package] @@ -16,14 +17,5 @@ readme = "README.md" keywords = ["cxx", "ffi", "QML", "Qt"] categories = ["api-bindings", "gui"] -[lib] -proc-macro = true - [dependencies] -cxx-qt-gen.workspace = true -proc-macro2.workspace = true -syn.workspace = true - -[dev-dependencies] -cxx.workspace = true -cxx-qt-lib.workspace = true +cxx-qt-macro.workspace = true diff --git a/crates/cxx-qt/src/lib.rs b/crates/cxx-qt/src/lib.rs index ed82005ac..d009938ed 100644 --- a/crates/cxx-qt/src/lib.rs +++ b/crates/cxx-qt/src/lib.rs @@ -1,182 +1,15 @@ -// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company -// SPDX-FileContributor: Andrew Hayzen -// SPDX-FileContributor: Gerhard de Clercq +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Leon Matthes // // SPDX-License-Identifier: MIT OR Apache-2.0 #![deny(missing_docs)] -//! The cxx-qt crate provides the procedural attribute macros which are used with cxx-qt. +//! This crate and its associated crates provide a framework for generating QObjects from Rust. +//! +//! See the [book](https://kdab.github.io/cxx-qt/book/) for more information. -use proc_macro::TokenStream; -use syn::{parse_macro_input, ItemMod}; - -use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser}; - -/// A procedural macro which generates a QObject for a struct inside a module. -/// -/// # Example -/// -/// ```rust -/// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")] -/// mod my_object { -/// #[cxx_qt::qobject] -/// #[derive(Default)] -/// # // Note that we can't use properties as this confuses the linker on Windows -/// pub struct MyObject; -/// -/// impl qobject::MyObject { -/// #[qinvokable] -/// fn invokable(&self, a: i32, b: i32) -> i32 { -/// a + b -/// } -/// } -/// } -/// -/// # // Note that we need a fake main for doc tests to build -/// # fn main() {} -/// ``` -#[proc_macro_attribute] -pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { - // Parse the TokenStream of a macro - // this triggers a compile failure if the tokens fail to parse. - let mut module = parse_macro_input!(input as ItemMod); - - // Macros do not typically need to do anything with their own attribute name, - // so rustc does not include that in the `args` or `input` TokenStreams. - // - // However, other code paths that use the parser do not enter from a macro invocation, - // so they rely on parsing the `cxx_qt::bridge` attribute to identify where to start parsing. - // - // To keep the inputs to the parser consistent for all code paths, - // add the attribute to the module before giving it to the parser. - let args_input = format!("#[cxx_qt::bridge({args})] mod dummy;"); - let attrs = syn::parse_str::(&args_input).unwrap().attrs; - module.attrs = attrs.into_iter().chain(module.attrs.into_iter()).collect(); - - // Extract and generate the rust code - extract_and_generate(module) -} - -/// A macro which describes that an enum defines the signals for a QObject. -/// -/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. -/// -/// # Example -/// -/// ```rust -/// #[cxx_qt::bridge] -/// mod my_object { -/// #[cxx_qt::qobject] -/// #[derive(Default)] -/// # // Note that we can't use properties as this confuses the linker on Windows -/// pub struct MyObject; -/// -/// #[cxx_qt::qsignals(MyObject)] -/// pub enum MySignals { -/// Ready, -/// } -/// } -/// -/// # // Note that we need a fake main for doc tests to build -/// # fn main() {} -/// ``` -#[proc_macro_attribute] -pub fn qsignals(_args: TokenStream, _input: TokenStream) -> TokenStream { - unreachable!("cxx_qt::qsignals should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") -} - -/// A macro which describes that a struct should be made into a QObject. -/// -/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. -/// -/// # Example -/// -/// ```rust -/// #[cxx_qt::bridge] -/// mod my_object { -/// #[cxx_qt::qobject] -/// #[derive(Default)] -/// # // Note that we can't use properties as this confuses the linker on Windows -/// pub struct MyObject; -/// } -/// -/// # // Note that we need a fake main for doc tests to build -/// # fn main() {} -/// ``` -/// -/// You can also specify a custom base class by using `#[cxx_qt::qobject(base = "QStringListModel")]`, you must then use CXX to add any includes needed. -/// -/// # Example -/// -/// ```rust -/// #[cxx_qt::bridge] -/// mod my_object { -/// #[cxx_qt::qobject(base = "QStringListModel")] -/// #[derive(Default)] -/// # // Note that we can't use properties as this confuses the linker on Windows -/// pub struct MyModel; -/// -/// unsafe extern "C++" { -/// include!(); -/// } -/// } -/// -/// # // Note that we need a fake main for doc tests to build -/// # fn main() {} -/// ``` -#[proc_macro_attribute] -pub fn qobject(_args: TokenStream, _input: TokenStream) -> TokenStream { - unreachable!("cxx_qt::qobject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") -} - -/// A macro which allows you to access base class methods from within Rust. -/// -/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition. -/// Furthermore, the macro must be placed within the `impl` block of a `qobject::T`. -/// See [the book page](https://kdab.github.io/cxx-qt/book/concepts/inheritance.html) for more -/// details. -/// -/// # Example -/// ``` rust -/// #[cxx_qt::bridge] -/// mod my_object { -/// extern "C++" { -/// include!("cxx-qt-lib/qmodelindex.h"); -/// type QModelIndex = cxx_qt_lib::QModelIndex; -/// } -/// -/// #[cxx_qt::qobject(base="QAbstractListModel")] -/// #[derive(Default)] -/// # // Note that we can't use properties as this confuses the linker on Windows -/// pub struct MyObject; -/// -/// #[cxx_qt::inherit] -/// extern "C++" { -/// // Unsafe to call -/// unsafe fn begin_insert_rows(self: Pin<&mut qobject::MyObject>, parent: &QModelIndex, first: i32, last: i32); -/// } -/// -/// #[cxx_qt::inherit] -/// unsafe extern "C++" { -/// // Safe to call - you are responsible to ensure this is true! -/// fn end_insert_rows(self: Pin<&mut qobject::MyObject>); -/// } -/// } -/// -/// # // Note that we need a fake main for doc tests to build -/// # fn main() {} -/// ``` -#[proc_macro_attribute] -pub fn inherit(_args: TokenStream, _input: TokenStream) -> TokenStream { - unreachable!("cxx_qt::inherit should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition") -} - -// Take the module and C++ namespace and generate the rust code -fn extract_and_generate(module: ItemMod) -> TokenStream { - Parser::from(module) - .and_then(|parser| GeneratedRustBlocks::from(&parser)) - .map(|generated_rust| write_rust(&generated_rust)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} +pub use cxx_qt_macro::bridge; +pub use cxx_qt_macro::inherit; +pub use cxx_qt_macro::qobject; +pub use cxx_qt_macro::qsignals;