Building with CMake
In this example, we will demonstrate how to integrate CXX-Qt code into a C++ application using CMake. Cargo builds the CXX-Qt code as a static library, then CMake links it into a C++ executable.
If you don't want to use CMake, and only want to use Cargo to build your project, refer to the previous section.
We'll first want to modify our project structure to separate the different parts of our project.
tutorial
- cpp
- qml
- rust
Move the rust project into the rust
folder.
Pull out the qml
folder back to the top level.
C++ executable
To start our QML application, we'll need a small main.cpp
file with an ordinary main
function. Put this in a cpp
folder to clearly separate the C++ and Rust code:
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
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();
}
You can add as much C++ code as you want in addition to this.
Using Rust QObjects in C++
For every #[cxx_qt::bridge]
that we define in Rust, CXX-Qt will generate a corresponding C++ header file.
To include any of the generated files, use the crates name as the include directory.
The name of the header file will be the folder names, combined with the input rust file name of your #[cxx_qt::bridge]
, followed by .cxxqt.h
.
So in our case: #include <qml_minimal/src/cxxqt_object.cxxqt.h>
π Note: any folders relative to the
Cargo.toml
file are considered hence thesrc
folder.
Including the generated header allows us to access the MyObject
C++ class, just like any other C++ class.
Inherit from it, connect signals and slots to it, put it in a QVector, do whatever you want with it.
That's the power of CXX-Qt.
Cargo setup
Before we can get started on building Qt with CMake, we first need to make our Cargo build ready for it.
If you've generated your project with e.g. cargo new --lib qml_minimal
or cargo init --lib [folder]
command, your Cargo.toml
should look something like this:
[package]
name = "qml_minimal"
version = "0.1.0"
edition = "2021"
[dependencies]
We'll have to do multiple things:
- Instruct cargo to create a static library
- Add
cxx
,cxx-qt
, as well ascxx-qt-lib
as dependencies - Add
cxx-qt-build
as a build dependency
If you've already followed the Cargo setup, most of this should already be done. Make sure to change the
crate-type
to"staticlib"
though!
In the end, your Cargo.toml
should look similar to this.
[package]
name = "qml_minimal"
version = "0.1.0"
authors = [
"Andrew Hayzen <andrew.hayzen@kdab.com>",
"Gerhard de Clercq <gerhard.declercq@kdab.com>",
"Leon Matthes <leon.matthes@kdab.com>"
]
edition = "2021"
license = "MIT OR Apache-2.0"
# This will instruct Cargo to create a static
# library which CMake can link against
[lib]
crate-type = ["staticlib"]
[dependencies]
cxx = "1.0.95"
cxx-qt = "0.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] }
[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] }
We'll then also need to add a script named build.rs
next to the Cargo.toml
:
If you've already followed the Cargo build tutorial, simply modify the existing
build.rs
file.
use cxx_qt_build::{CxxQtBuilder, QmlModule};
fn main() {
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();
}
This is what generates and compiles the C++ code for our MyObject
class at build time.
Every Rust source file that uses the #[cxx_qt::bridge]
macro need to be included in this script.
In our case, this is only the src/cxxqt_object.rs
file.
This is also where the QML module is defined with a QML URI and version. The files and resources in the module are then exposed in the same way as the qt_add_qml_module CMake function.
Note that in order for CXX-Qt to work, the
qmake
executable must be located. This is because CXX-Qt relies onqmake
to locate the necessary Qt libraries and header files on your system.Usually, the CMake code that CXX-Qt provides you to import a crate should already take care of this.
To overwrite the path to qmake, you may pass the
QMAKE
option to cxx_qt_import_crate, ensuring that CMake and Cargo use the same Qt binaries.
We'll also need to remove the src/main.rs
and replace it with a src/lib.rs
file.
This file only needs to include a single line:
pub mod cxxqt_object;
This simply ensures that our rust module is included in our library.
Feel free to add additional rust modules in your library as well.
CMake setup
Now add a CMakeLists.txt
file in the root of your project folder.
Start the CMakeLists.txt
file like any other C++ project using Qt.
For this example, we are supporting both Qt5 and Qt6 with CMake:
cmake_minimum_required(VERSION 3.24)
project(example_qml_minimal)
# 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()
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CXXQT_QTCOMPONENTS Core Gui Qml QuickControls2 QuickTest Test)
set(CXXQT_QTCOMPONENTS ${CXXQT_QTCOMPONENTS} QmlImportScanner)
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()
Download CXX-Qts CMake code with FetchContent:
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 v0.7.0
)
FetchContent_MakeAvailable(CxxQt)
endif()
This provides you with a few wrappers around Corrosion, a tool for integrating Rust libraries into CMake:
cxx_qt_import_crate
- A wrapper around corrosion_import_crate. It supports the same arguments as corrosion_import_crate, with three new arguments:QT_MODULES
(required) - The Qt modules to link to. Specify the corresponding CMake targets here.CXX_QT_EXPORT_DIR
(optional) - Manually specify the path where CXX-Qt artifacts will be exported to.- This is usually not necessary. However, if you're importing the same crate with different feature sets in the same CMake build configuration, you will need to specify seperate
CXX_QT_EXPORT_DIR
s to avoid multiple versions of the crate exporting to the same directory.
- This is usually not necessary. However, if you're importing the same crate with different feature sets in the same CMake build configuration, you will need to specify seperate
QMAKE
(optional) - Override the path to the QMAKE executable
cxx_qt_import_qml_module
- This function imports a QML modules as a new target. It requires the following arguments:TARGET_NAME
- Specify the name of the CMake target that this function will createURI
- The URI of the qml module to import - this needs to exactly match the URI in theCxxQtBuilder::qml_module
call in your build script.SOURCE_CRATE
The crate that exports the QML module (this crate must have been imported withcxx_qt_import_crate
).
# CXX-Qt (using Corrosion) creates a CMake target with the same name as the crate.
cxx_qt_import_crate(
MANIFEST_PATH rust/Cargo.toml
CRATES qml_minimal
QT_MODULES Qt::Core Qt::Gui Qt::Qml Qt::QuickControls2)
cxx_qt_import_qml_module(qml_minimal_qml_module
URI "com.kdab.cxx_qt.demo"
SOURCE_CRATE qml_minimal)
This will create two new CMake targets:
qml_minimal
- The static library exported by our crateqml_minimal_qml_module
- The QML Module exported by our crate- The
_qml_module
target will automatically link to theqml_minimal
target, so linking to the_qml_module
is sufficient for our executable target
- The
Finally, we can create the CMake executable target and link it to our crate:
add_executable(example_qml_minimal cpp/main.cpp)
# Link to the qml module, which in turn links to the Rust qml_minimal library
target_link_libraries(example_qml_minimal PRIVATE qml_minimal_qml_module)
# If we are using a statically linked Qt then we need to import any qml plugins
qt_import_qml_plugins(example_qml_minimal)
Your project should now have a structure similar to this:
$ tree -I target/ -I tests
.
βββ CMakeLists.txt
βββ cpp
βΒ Β βββ main.cpp
βββ qml
βΒ Β βββ main.qml
βββ rust
βββ build.rs
βββ Cargo.toml
βββ src
βββ cxxqt_object.rs
βββ lib.rs
5 directories, 7 files
Build the project like any other CMake project:
$ cmake -S . -B build
$ cmake --build build
If this fails for any reason, take a look at the examples/qml_minimal
folder, which contains the complete example code.
This should now configure and compile our project. If this was successful, you can now run our little project.
$ ./build/examples/qml_minimal/example_qml_minimal
You should now see the two Labels that display the state of our MyObject
, as well as the two buttons to call our two Rust functions.
Windows with MSVC
If you're building CXX-Qt on Windows using MSVC generator, you need to ensure that set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
is set in CMake (or use the -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL
flag) when building with the Debug
configuration. This flag is necessary to ensure that the correct C Runtime Library is used. Then you can build using cmake --build build --config Debug
.
This issue is caused by a bug in the cc crate (as described in this pull request), which has not been merged yet. Specifically, the problem is that cc generated code always links to the MultiThreaded runtime, even when building in Debug mode. We hope that this step won't be necessary in the future, once the cc crate fix is merged and released.