diff --git a/Cargo.toml b/Cargo.toml index ae34f3637..97030008e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ members = [ "examples/demo_threading/rust", "examples/qml_features/rust", "examples/qml_minimal/rust", + "examples/meta_project/rust/main", + "examples/meta_project/rust/sub1", + "examples/meta_project/rust/sub2", "tests/basic_cxx_only/rust", "tests/basic_cxx_qt/rust", diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7cec23b99..f7dc8f19c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,6 +9,7 @@ # When using `cargo test` add_subdirectory(qml_features) add_subdirectory(qml_minimal) +add_subdirectory(meta_project) # TODO: get demo_threading working for wasm builds if(NOT BUILD_WASM) diff --git a/examples/meta_project/CMakeLists.txt b/examples/meta_project/CMakeLists.txt new file mode 100644 index 000000000..516fa532d --- /dev/null +++ b/examples/meta_project/CMakeLists.txt @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +cmake_minimum_required(VERSION 3.24) + +project(example_meta_project) + +# Rust always links against non-debug Windows runtime on *-msvc targets +# Note it is best to set this on the command line to ensure all targets are consistent +# https://github.com/corrosion-rs/corrosion/blob/master/doc/src/common_issues.md#linking-debug-cc-libraries-into-rust-fails-on-windows-msvc-targets +# https://github.com/rust-lang/rust/issues/39016 +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") +endif() + +if(BUILD_WASM) + # Ensure Rust build for the correct target + set(Rust_CARGO_TARGET wasm32-unknown-emscripten) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CXXQT_QTCOMPONENTS Core Gui Qml QuickControls2 QuickTest Test) +if(NOT BUILD_WASM) + set(CXXQT_QTCOMPONENTS ${CXXQT_QTCOMPONENTS} QmlImportScanner) +endif() + +if(NOT USE_QT5) + find_package(Qt6 COMPONENTS ${CXXQT_QTCOMPONENTS}) +endif() +if(NOT Qt6_FOUND) + find_package(Qt5 5.15 COMPONENTS ${CXXQT_QTCOMPONENTS} REQUIRED) +endif() + +find_package(CxxQt QUIET) +if(NOT CxxQt_FOUND) + include(FetchContent) + FetchContent_Declare( + CxxQt + GIT_REPOSITORY https://github.com/kdab/cxx-qt-cmake.git + GIT_TAG main + ) + + FetchContent_MakeAvailable(CxxQt) +endif() + +cxx_qt_import_crate(MANIFEST_PATH rust/main/Cargo.toml CRATES qml_meta_project) +target_link_libraries(qml_meta_project INTERFACE Qt::Core Qt::Gui Qt::Qml Qt::QuickControls2) + +cxx_qt_import_qml_module(qml_meta_project_main + URI "com.kdab.cxx_qt.demo" + SOURCE_CRATE qml_meta_project) + +cxx_qt_import_qml_module(qml_meta_project_sub1 + URI "com.kdab.cxx_qt.demo.sub1" + SOURCE_CRATE qml_meta_project) + +cxx_qt_import_qml_module(qml_meta_project_sub2 + URI "com.kdab.cxx_qt.demo.sub2" + SOURCE_CRATE qml_meta_project) + +# Define the executable with the C++ source +if(BUILD_WASM) + # Currently need to use qt_add_executable + # for WASM builds, otherwise there is no + # HTML output. + # + # TODO: Figure out how to configure such that + # we can use add_executable for WASM + qt_add_executable(example_meta_project cpp/main.cpp) +else() + add_executable(example_meta_project cpp/main.cpp) +endif() + +# Link to the qml module, which in turn links to the Rust qml_meta_project library +target_link_libraries(example_meta_project PRIVATE Qt::Core Qt::Gui Qt::Qml qml_meta_project_main qml_meta_project_sub1 qml_meta_project_sub2) + +# If we are using a statically linked Qt then we need to import any qml plugins +qt_import_qml_plugins(example_meta_project) diff --git a/examples/meta_project/cpp/main.cpp b/examples/meta_project/cpp/main.cpp new file mode 100644 index 000000000..e6613bb93 --- /dev/null +++ b/examples/meta_project/cpp/main.cpp @@ -0,0 +1,32 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#include +#include + +int +main(int argc, char* argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + const QUrl url( + QStringLiteral("qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml")); + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreated, + &app, + [url](QObject* obj, const QUrl& objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + + engine.load(url); + + return app.exec(); +} diff --git a/examples/meta_project/qml/main.qml b/examples/meta_project/qml/main.qml new file mode 100644 index 000000000..18301e50f --- /dev/null +++ b/examples/meta_project/qml/main.qml @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 + +import com.kdab.cxx_qt.demo 1.0 +import com.kdab.cxx_qt.demo.sub1 1.0 +import com.kdab.cxx_qt.demo.sub2 1.0 + +ApplicationWindow { + id: window + minimumHeight: 480 + minimumWidth: 640 + title: qsTr("CXX-Qt: Hello World") + visible: true + + MainObject { + id: main + } + + Sub1Object { + id: sub1 + } + + Sub2Object { + id: sub2 + } + + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Label { + text: "Main: " + main.string + } + + Label { + text: "Sub1: " + sub1.string + } + + Label { + text: "Sub2: " + sub2.string + } + + Button { + text: "Increment Number" + + onClicked: { + main.increment(); + sub1.increment(); + sub2.increment(); + } + } + } +} diff --git a/examples/meta_project/rust/main/Cargo.toml b/examples/meta_project/rust/main/Cargo.toml new file mode 100644 index 000000000..c2d9504e2 --- /dev/null +++ b/examples/meta_project/rust/main/Cargo.toml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +[package] +name = "qml_meta_project" +version = "0.1.0" +authors = ["Andrew Hayzen "] +edition = "2021" +license = "MIT OR Apache-2.0" +# Linking CXX-Qt crates as rlibs requires a compiler feature +# that was stabilized in Rust 1.74, +# combining +whole-archive and +bundle link modifiers: https://github.com/rust-lang/rust/pull/113301 +rust-version = "1.74" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +sub1 = { path = "../sub1" } +sub2 = { path = "../sub2" } + +cxx.workspace = true +cxx-qt.workspace = true +cxx-qt-lib.workspace = true + +[build-dependencies] +cxx-qt-build.workspace = true diff --git a/examples/meta_project/rust/main/build.rs b/examples/meta_project/rust/main/build.rs new file mode 100644 index 000000000..194054c84 --- /dev/null +++ b/examples/meta_project/rust/main/build.rs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_build::{CxxQtBuilder, QmlModule}; + +fn main() { + CxxQtBuilder::new() + .qt_module("Network") + .qml_module(QmlModule::<_, &str> { + uri: "com.kdab.cxx_qt.demo", + rust_files: &["src/main_object.rs"], + qml_files: &["../../qml/main.qml"], + ..Default::default() + }) + .build(); +} diff --git a/examples/meta_project/rust/main/src/lib.rs b/examples/meta_project/rust/main/src/lib.rs new file mode 100644 index 000000000..9133ec875 --- /dev/null +++ b/examples/meta_project/rust/main/src/lib.rs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +mod main_object; + +// Ensure the symbols from the rlib dependencies end up +// in the staticlib (if you use Rust symbols from these +// crates in this crate, you can skip these `extern crate` statements). +extern crate sub1; +extern crate sub2; diff --git a/examples/meta_project/rust/main/src/main_object.rs b/examples/meta_project/rust/main/src/main_object.rs new file mode 100644 index 000000000..9fc69cef5 --- /dev/null +++ b/examples/meta_project/rust/main/src/main_object.rs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + unsafe extern "RustQt" { + #[qobject] + #[qml_element] + #[qproperty(QString, string)] + type MainObject = super::MainObjectRust; + + #[qinvokable] + fn increment(self: Pin<&mut MainObject>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +#[derive(Default)] +pub struct MainObjectRust { + string: QString, + + pub counter: u32, +} + +impl qobject::MainObject { + pub fn increment(mut self: Pin<&mut Self>) { + self.as_mut().rust_mut().counter = self.rust().counter + 1; + + let new_string = QString::from(&self.rust().counter.to_string()); + self.as_mut().set_string(new_string); + } +} diff --git a/examples/meta_project/rust/sub1/Cargo.toml b/examples/meta_project/rust/sub1/Cargo.toml new file mode 100644 index 000000000..f38f96056 --- /dev/null +++ b/examples/meta_project/rust/sub1/Cargo.toml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +[package] +name = "sub1" +version = "0.1.0" +authors = [ + "Andrew Hayzen ", +] +edition = "2021" +license = "MIT OR Apache-2.0" + +links = "sub1" + +[dependencies] +cxx.workspace = true +cxx-qt.workspace = true +cxx-qt-lib.workspace = true + +[build-dependencies] +cxx-qt-build.workspace = true diff --git a/examples/meta_project/rust/sub1/build.rs b/examples/meta_project/rust/sub1/build.rs new file mode 100644 index 000000000..6652b1c0d --- /dev/null +++ b/examples/meta_project/rust/sub1/build.rs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_build::{CxxQtBuilder, Interface, QmlModule}; + +fn main() { + let interface = Interface::default(); + CxxQtBuilder::library(interface) + .qt_module("Network") + .qml_module(QmlModule::<_, &str> { + uri: "com.kdab.cxx_qt.demo.sub1", + rust_files: &["src/sub1_object.rs"], + ..Default::default() + }) + .build(); +} diff --git a/examples/meta_project/rust/sub1/src/lib.rs b/examples/meta_project/rust/sub1/src/lib.rs new file mode 100644 index 000000000..eaedc0171 --- /dev/null +++ b/examples/meta_project/rust/sub1/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// We need to enable packed bundled libs to allow for +bundle and +whole-archive +// https://github.com/rust-lang/rust/issues/108081 + +mod sub1_object; + +pub fn increment(number: u32) -> u32 { + number + 2 +} diff --git a/examples/meta_project/rust/sub1/src/sub1_object.rs b/examples/meta_project/rust/sub1/src/sub1_object.rs new file mode 100644 index 000000000..f2bdf0679 --- /dev/null +++ b/examples/meta_project/rust/sub1/src/sub1_object.rs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + unsafe extern "RustQt" { + #[qobject] + #[qml_element] + #[qproperty(QString, string)] + type Sub1Object = super::Sub1ObjectRust; + + #[qinvokable] + fn increment(self: Pin<&mut Sub1Object>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +#[derive(Default)] +pub struct Sub1ObjectRust { + string: QString, + + pub counter: u32, +} + +impl qobject::Sub1Object { + pub fn increment(mut self: Pin<&mut Self>) { + self.as_mut().rust_mut().counter = crate::increment(self.rust().counter); + + let new_string = QString::from(&self.rust().counter.to_string()); + self.as_mut().set_string(new_string); + } +} diff --git a/examples/meta_project/rust/sub2/Cargo.toml b/examples/meta_project/rust/sub2/Cargo.toml new file mode 100644 index 000000000..97f0fbac4 --- /dev/null +++ b/examples/meta_project/rust/sub2/Cargo.toml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +[package] +name = "sub2" +version = "0.1.0" +authors = [ + "Andrew Hayzen ", +] +edition = "2021" +license = "MIT OR Apache-2.0" + +links = "sub2" + +[dependencies] +cxx.workspace = true +cxx-qt.workspace = true +cxx-qt-lib.workspace = true + +[build-dependencies] +cxx-qt-build.workspace = true diff --git a/examples/meta_project/rust/sub2/build.rs b/examples/meta_project/rust/sub2/build.rs new file mode 100644 index 000000000..cdbb1c9a2 --- /dev/null +++ b/examples/meta_project/rust/sub2/build.rs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_build::{CxxQtBuilder, Interface, QmlModule}; + +fn main() { + let interface = Interface::default(); + CxxQtBuilder::library(interface) + .qml_module(QmlModule::<_, &str> { + uri: "com.kdab.cxx_qt.demo.sub2", + rust_files: &["src/sub2_object.rs"], + ..Default::default() + }) + .build(); +} diff --git a/examples/meta_project/rust/sub2/src/lib.rs b/examples/meta_project/rust/sub2/src/lib.rs new file mode 100644 index 000000000..689f1ece5 --- /dev/null +++ b/examples/meta_project/rust/sub2/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// We need to enable packed bundled libs to allow for +bundle and +whole-archive +// https://github.com/rust-lang/rust/issues/108081 + +mod sub2_object; + +pub fn increment(number: u32) -> u32 { + number + 3 +} diff --git a/examples/meta_project/rust/sub2/src/sub2_object.rs b/examples/meta_project/rust/sub2/src/sub2_object.rs new file mode 100644 index 000000000..f520f235f --- /dev/null +++ b/examples/meta_project/rust/sub2/src/sub2_object.rs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + unsafe extern "RustQt" { + #[qobject] + #[qml_element] + #[qproperty(QString, string)] + type Sub2Object = super::Sub2ObjectRust; + + #[qinvokable] + fn increment(self: Pin<&mut Sub2Object>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +#[derive(Default)] +pub struct Sub2ObjectRust { + string: QString, + + pub counter: u32, +} + +impl qobject::Sub2Object { + pub fn increment(mut self: Pin<&mut Self>) { + self.as_mut().rust_mut().counter = crate::increment(self.rust().counter); + + let new_string = QString::from(&self.rust().counter.to_string()); + self.as_mut().set_string(new_string); + } +}