Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example application built entirely with Cargo #221

Merged
merged 5 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/github-cxx-qt-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,13 @@ jobs:
# Ubuntu 22.04 uses GCC 11 by default. Somehow when using that compiler,
# GNU ld fails to link, though lld works. To test a full GNU toolchain,
# use GCC 12 (with the default GNU ld).
#
# mold (or lld) is required for qml-minimal-no-cmake to link.
run: >-
sudo apt-get update &&
sudo apt-get install -y
gcc-12
mold
ninja-build
clang-format-12
libssl-dev
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"cxx-qt-lib",
"cxx-qt-lib-headers",

"examples/cargo_without_cmake",
"examples/demo_threading",
"examples/qml_extension_plugin/core",
"examples/qml_features",
Expand Down
35 changes: 35 additions & 0 deletions cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ fn generate_cxxqt_cpp_files(
pub struct CxxQtBuilder {
rust_sources: Vec<PathBuf>,
qobject_headers: Vec<PathBuf>,
qrc_files: Vec<PathBuf>,
qt_modules: HashSet<String>,
cc_builder: cc::Build,
}
Expand All @@ -250,6 +251,7 @@ impl CxxQtBuilder {
Self {
rust_sources: vec![],
qobject_headers: vec![],
qrc_files: vec![],
qt_modules,
cc_builder: cc::Build::new(),
}
Expand All @@ -264,6 +266,33 @@ impl CxxQtBuilder {
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 Gui modules are linked automatically; there is no need to specify them.
Expand Down Expand Up @@ -356,6 +385,12 @@ impl CxxQtBuilder {
self.cc_builder.file(qtbuild.moc(&qobject_header));
}

// Generate code from .qrc files, but do not compile it. Instead, the user needs to #include them
// in a .cpp file. Otherwise, MSVC won't link if the generated C++ is built separately.
for qrc_file in self.qrc_files {
qtbuild.qrc(&qrc_file);
}

self.cc_builder.compile("cxx-qt-gen");
}
}
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#
# SPDX-License-Identifier: MIT OR Apache-2.0

add_subdirectory(cargo_without_cmake)
add_subdirectory(qml_extension_plugin)
add_subdirectory(qml_features)
add_subdirectory(qml_minimal)
Expand Down
47 changes: 47 additions & 0 deletions examples/cargo_without_cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
# SPDX-FileContributor: Be Wilson <[email protected]>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

# This CMakeLists.txt does not build any C++ directly. It is only here to integrate building
# this crate into the larger CMake build of this repository. For development and testing, you
# can run
#
# cargo build -p qml-minimal-no-cmake
#
# from any directory in this repository to build this crate without using CMake at all.

find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner REQUIRED)
get_target_property(QMAKE Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)

# Use Corrosion to create a target for the crate that can be built without CMake.
# This allows it to share built dependencies with the other crates in the workspace.
find_package(Corrosion QUIET)
if(NOT Corrosion_FOUND)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.2.1
)

FetchContent_MakeAvailable(Corrosion)
endif()

set(CRATE qml-minimal-no-cmake)
corrosion_import_crate(MANIFEST_PATH Cargo.toml CRATES ${CRATE})
corrosion_set_env_vars(${CRATE} "QMAKE=${QMAKE}")
if(UNIX AND NOT APPLE)
find_program(MOLD_EXECUTABLE mold)
if(MOLD_EXECUTABLE)
corrosion_add_target_rustflags(${CRATE} "-Clink-arg=-fuse-ld=mold")
else()
find_program(LLD_EXECUTABLE lld)
if(LLD_EXECUTABLE)
corrosion_add_target_rustflags(${CRATE} "-Clink-arg=-fuse-ld=lld")
else()
message(WARNING "Neither mold nor lld linkers were found. ${CRATE} will likely fail to link without using one of these.")
endif()
endif()
endif()
24 changes: 24 additions & 0 deletions examples/cargo_without_cmake/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
# SPDX-FileContributor: Andrew Hayzen <[email protected]>
# SPDX-FileContributor: Gerhard de Clercq <[email protected]>
#
# SPDX-License-Identifier: MIT OR Apache-2.0
[package]
name = "qml-minimal-no-cmake"
version = "0.3.0"
authors = [
"Andrew Hayzen <[email protected]>",
"Be Wilson <[email protected]>",
"Gerhard de Clercq <[email protected]>",
"Leon Matthes <[email protected]>"
]
edition = "2018"
license = "MIT OR Apache-2.0"

[dependencies]
cxx = "1.0"
cxx-qt = { path = "../../cxx-qt" }
cxx-qt-lib = { path = "../../cxx-qt-lib" }

[build-dependencies]
cxx-qt-build = { path = "../../cxx-qt-build" }
18 changes: 18 additions & 0 deletions examples/cargo_without_cmake/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Be Wilson <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use cxx_qt_build::CxxQtBuilder;

fn main() {
CxxQtBuilder::new()
.qt_modules(&["Qml", "Network"])
.file("src/cxxqt_object.rs")
.qrc("src/qml.qrc")
.cc_builder(|cc| {
cc.file("src/run.cpp");
println!("cargo:rerun-if-changed=src/run.cpp");
})
.build();
}
47 changes: 47 additions & 0 deletions examples/cargo_without_cmake/src/cxxqt_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Be Wilson <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#[cxx_qt::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx-qt-lib/include/qt_types.h");
type QString = cxx_qt_lib::QString;
}

pub struct Data {
number: i32,
string: UniquePtr<QString>,
}

impl Default for Data {
fn default() -> Self {
Self {
number: 0,
string: QString::from_str(""),
}
}
}

#[cxx_qt::qobject]
#[derive(Default)]
pub struct MyObject;
impl cxx_qt::QObject<MyObject> {
#[qinvokable]
pub fn increment_number(self: Pin<&mut Self>) {
let previous = self.as_ref().number();
self.set_number(previous + 1);
}

#[qinvokable]
pub fn say_hi(&self, string: &QString, number: i32) {
println!(
"Hi from Rust! String is '{}' and number is {}",
string, number
);
}
}
}
50 changes: 50 additions & 0 deletions examples/cargo_without_cmake/src/main.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

import com.kdab.cxx_qt.demo 1.0

Window {
height: 480
title: qsTr("Hello World")
visible: true
width: 640

MyObject {
id: myObject
number: 1
string: "My String with my number: " + myObject.number
}

Column {
anchors.fill: parent
anchors.margins: 10
spacing: 10

Label {
text: "Number: " + myObject.number
}

Label {
text: "String: " + myObject.string
}

Button {
text: "Increment Number"

onClicked: myObject.incrementNumber()
}

Button {
text: "Say Hi!"

onClicked: myObject.sayHi(myObject.string, myObject.number)
}
}
}
39 changes: 39 additions & 0 deletions examples/cargo_without_cmake/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Be Wilson <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

mod cxxqt_object;

use std::{convert::TryInto, ffi::CString, os::raw::c_char, os::raw::c_int, ptr};

extern "C" {
fn run_cpp(argc: c_int, argv: *const *const c_char) -> c_int;
}

fn main() {
let args: Vec<CString> = std::env::args_os()
.map(|string| {
// Unix OsStrings can be directly converted to CStrings.
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;

// Windows OsStrings are WTF-8 encoded, so they need to be
// converted to UTF-8 Strings before being converted to CStrings.
// https://simonsapin.github.io/wtf-8/
#[cfg(windows)]
let string = string.to_string_lossy();

CString::new(string.as_bytes()).unwrap()
})
.collect();

let mut c_args: Vec<*const c_char> = args.iter().map(|arg| arg.as_ptr()).collect();
c_args.push(ptr::null());

unsafe {
run_cpp(args.len().try_into().unwrap(), c_args.as_ptr());
}
}
13 changes: 13 additions & 0 deletions examples/cargo_without_cmake/src/qml.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE RCC>
<!--
SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
SPDX-FileContributor: Andrew Hayzen <[email protected]>
SPDX-FileContributor: Gerhard de Clercq <[email protected]>

SPDX-License-Identifier: MIT OR Apache-2.0
-->
<RCC version="1.0">
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
48 changes: 48 additions & 0 deletions examples/cargo_without_cmake/src/run.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// clang-format off
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Be Wilson <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>

#include "cxx-qt-gen/include/my_object.cxxqt.h"

// Include the C++ code generated by rcc from the .qrc file
#include "qml.qrc.cpp"

extern "C" int
run_cpp(int argc, char* argv[])
{
// Normally in a C++ program, global variables for the Qt Resource System are
// initialized before the C++ main function runs. However, when building a
// Rust executable with Cargo, the Qt Resource System needs to be initialized
// manually.
// https://doc.qt.io/qt-6/resources.html#explicit-loading-and-unloading-of-embedded-resources
qInitResources();

QGuiApplication app(argc, argv);

QQmlApplicationEngine engine;

const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreated,
&app,
[url](QObject* obj, const QUrl& objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);

qmlRegisterType<MyObject>("com.kdab.cxx_qt.demo", 1, 0, "MyObject");

engine.load(url);

return app.exec();
}
Loading