Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ compile_commands.json

# clangd cache
.cache/

*.qmlls.ini
94 changes: 94 additions & 0 deletions crates/cxx-qt-build/cpp/builtins.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// clang-format off
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// clang-format on
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#pragma once

#include <cstdint>

#include <QtQml/QQmlEngine>

// This is similar to the builtins file in qtdeclarative
// https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/qml/qqmlbuiltins_p.h?h=v6.9.3
//
// We need this to be able to alias namespaced std numerics to types that QML
// understands.
//
// We do not need to build this file but the moc JSON output to give to
// qmltyperegistrar so that qmllint and qmlls can understand the types.
//
// If Qt ever registered qualified versions of the numerics this could be
// removed.
//
// qqmlbuiltins uses the following values for QML_USING so we should copy
// i8, u8 -> qint8, quint8
// i16, u16 -> short, ushort
// i32, u32 -> int, uint
// i64, u64 -> qlonglong, qulonglong

struct QQmlCxxQtStdInt8TForeign
{
Q_GADGET
QML_FOREIGN(::std::int8_t)
QML_USING(qint8)
};
static_assert(sizeof(::std::int8_t) == sizeof(qint8));

struct QQmlCxxQtStdUInt8TForeign
{
Q_GADGET
QML_FOREIGN(::std::uint8_t)
QML_USING(quint8)
};
static_assert(sizeof(::std::uint8_t) == sizeof(quint8));

struct QQmlCxxQtStdInt16TForeign
{
Q_GADGET
QML_FOREIGN(::std::int16_t)
QML_USING(short)
};
static_assert(sizeof(::std::int16_t) == sizeof(short));

struct QQmlCxxQtStdUInt16TForeign
{
Q_GADGET
QML_FOREIGN(::std::uint16_t)
QML_USING(ushort)
};
static_assert(sizeof(::std::uint16_t) == sizeof(ushort));

struct QQmlCxxQtStdInt32TForeign
{
Q_GADGET
QML_FOREIGN(::std::int32_t)
QML_USING(int)
};
static_assert(sizeof(::std::int32_t) == sizeof(int));

struct QQmlCxxQtStdUInt32TForeign
{
Q_GADGET
QML_FOREIGN(::std::uint32_t)
QML_USING(uint)
};
static_assert(sizeof(::std::uint32_t) == sizeof(uint));

struct QQmlCxxQtStdInt64TForeign
{
Q_GADGET
QML_FOREIGN(::std::int64_t)
QML_USING(qlonglong)
};
static_assert(sizeof(::std::int64_t) == sizeof(qlonglong));

struct QQmlCxxQtStdUInt64TForeign
{
Q_GADGET
QML_FOREIGN(::std::uint64_t)
QML_USING(qulonglong)
};
static_assert(sizeof(::std::uint64_t) == sizeof(qulonglong));
21 changes: 12 additions & 9 deletions crates/cxx-qt-build/src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

//! This modules contains information about the paths where artifacts are placed by cxx-qt-build.

use crate::{crate_name, module_name_from_uri};
use crate::crate_name;
use std::io::Result;
use std::{
env, fs,
Expand Down Expand Up @@ -57,32 +57,35 @@ pub(crate) fn crate_target() -> PathBuf {
}

/// The target directory, namespaced by QML module
pub(crate) fn module_target(module_uri: &str) -> PathBuf {
pub(crate) fn module_target(module_uri: &qt_build_utils::QmlUri) -> PathBuf {
module_export(module_uri).unwrap_or_else(|| {
out()
// Use a short name due to the Windows file path limit!
.join("cxxqtqml")
.join(module_name_from_uri(module_uri))
.join(module_uri.as_dirs())
})
}

/// The export directory, namespaced by QML module
///
/// In conctrast to the crate_export directory, this is `Some` for downstream dependencies as well.
/// In contrast to the crate_export directory, this is `Some` for downstream dependencies as well.
/// This allows CMake to import QML modules from dependencies.
///
/// TODO: This may conflict if two dependencies are building QML modules with the same name!
/// We should probably include a lockfile here to avoid this.
pub(crate) fn module_export(module_uri: &str) -> Option<PathBuf> {
pub(crate) fn module_export(module_uri: &qt_build_utils::QmlUri) -> Option<PathBuf> {
// In contrast to crate_export, we don't need to check for the specific crate here.
// QML modules should always be exported.
module_export_qml_modules().map(|dir| dir.join(module_uri.as_dirs()))
}

pub(crate) fn module_export_qml_modules() -> Option<PathBuf> {
// In contrast to crate_export, we don't need to check for the specific crate here.
// QML modules should always be exported.
env::var("CXX_QT_EXPORT_DIR")
.ok()
.map(PathBuf::from)
.map(|dir| {
dir.join("qml_modules")
.join(module_name_from_uri(module_uri))
})
.map(|dir| dir.join("qml_modules"))
}

/// The target directory or another directory where we can write files that will be shared
Expand Down
66 changes: 61 additions & 5 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub use qml_modules::QmlModule;

pub use qt_build_utils::MocArguments;
use qt_build_utils::MocProducts;
use qt_build_utils::QmlLsIniBuilder;
use quote::ToTokens;
use semver::Version;
use std::{
Expand Down Expand Up @@ -732,6 +733,10 @@ impl CxxQtBuilder {
{

let mut moc_arguments = moc_arguments.clone();
// Ensure that header root is in the include path of moc
// otherwise it cannot read the .cxx.h paths
moc_arguments = moc_arguments.include_path(dir::header_root());

if let Some(qml_module) = &self.qml_module {
// Ensure that the generated QObject header is in the include path
// so that qmltyperegistar can include them later
Expand Down Expand Up @@ -814,8 +819,14 @@ impl CxxQtBuilder {
// Extract qml_modules out of self so we don't have to hold onto `self` for the duration of
// the loop.
if let Some(qml_module) = self.qml_module.take() {
dir::clean(dir::module_target(&qml_module.uri))
.expect("Failed to clean qml module export directory!");
// TODO: likely qml_module will have a QmlUri already
let qml_uri = qt_build_utils::QmlUri::from(qml_module.uri.as_str());

// TODO: clean the old module target
// however if there is a sub uri this cleans that too
// so we should only remove files and not sub folders?
// dir::clean(dir::module_target(&qml_uri))
// .expect("Failed to clean qml module export directory!");

// Check that all rust files are within the same directory
//
Expand Down Expand Up @@ -853,11 +864,22 @@ impl CxxQtBuilder {
let cc_builder = &mut self.cc_builder;
qtbuild.cargo_link_libraries(cc_builder);

let qml_metatypes_json: Vec<PathBuf> = moc_products
let mut qml_metatypes_json: Vec<PathBuf> = moc_products
.iter()
.map(|products| products.metatypes_json.clone())
.collect();

// Inject CXX-Qt builtin meta types
let builtins_path = dir::out().join("builtins.h");
std::fs::write(&builtins_path, include_str!("../cpp/builtins.h"))
.expect("Failed to write builtins.h");
qml_metatypes_json.push(
qtbuild
.moc()
.compile(builtins_path, MocArguments::default())
.metatypes_json,
);

let qml_module_registration_files = qtbuild.register_qml_module(
&qml_metatypes_json,
&qml_module.uri,
Expand All @@ -869,6 +891,7 @@ impl CxxQtBuilder {
&module_name_from_uri(&qml_module.uri),
&qml_module.qml_files,
&qml_module.qrc_files,
&qml_module.depends,
);
if let Some(qmltyperegistrar) = qml_module_registration_files.qmltyperegistrar {
cc_builder.file(qmltyperegistrar);
Expand All @@ -890,7 +913,7 @@ impl CxxQtBuilder {
for qmlcachegen_file in qml_module_registration_files.qmlcachegen {
cc_builder.file(qmlcachegen_file);
}
// This is required, as described here: plugin_builder
// This is required, as described here: https://doc.qt.io/qt-6/plugins-howto.html#creating-static-plugins
cc_builder.define("QT_STATICPLUGIN", None);

// If any of the files inside the qml module change, then trigger a rerun
Expand All @@ -902,14 +925,47 @@ impl CxxQtBuilder {
println!("cargo::rerun-if-changed={}", path.display());
}

// Export the .qmltypes and qmldir files into a stable path, so that tools like
// qmllint/qmlls can find them.
let plugin_dir = dir::module_export(&qml_uri);
if let Some(plugin_dir) = &plugin_dir {
std::fs::create_dir_all(plugin_dir).expect("Could not create plugin directory");
std::fs::copy(
qml_module_registration_files.qmltypes,
plugin_dir.join("plugin.qmltypes"),
)
.expect("Could not copy plugin.qmltypes to export directory");
std::fs::copy(
qml_module_registration_files.qmldir,
plugin_dir.join("qmldir"),
)
.expect("Could not copy qmldir to export directory");
}

// Create a .qmlls.ini file with the build dir set similar to QT_QML_GENERATE_QMLLS_INI
if let (Some(qml_modules_dir), Some(manifest_dir)) =
(dir::module_export_qml_modules(), dir::manifest())
{
// TODO: manifest dir is not enough as QML files might be in a parent
// so this should likely be an argument given to cxx-qt-build
// that optionally exports?
let mut file = File::create(manifest_dir.parent().unwrap().join(".qmlls.ini"))
.expect("Could not create qmlls.ini file");
QmlLsIniBuilder::new()
.build_dir(qml_modules_dir)
.no_cmake_calls(true)
.write(&mut file)
.expect("Could not write qmlls.ini")
}

let module_init_key = qml_module_init_key(&qml_module.uri);
let private_initializers = [qml_module_registration_files.plugin_init];
let public_initializer =
Self::generate_public_initializer(&private_initializers, &module_init_key);
self.build_initializers(
&private_initializers,
&public_initializer,
dir::module_export(&qml_module.uri).map(|dir| dir.join("plugin_init.o")),
plugin_dir.map(|dir| dir.join("plugin_init.o")),
&module_init_key,
);

Expand Down
9 changes: 9 additions & 0 deletions crates/cxx-qt-build/src/qml_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ where
// and an empty slice is likely desired in most cases; most users probably don't
// care about this field.
pub qrc_files: &'a [A],
/// Dependencies of the QML module
pub depends: &'a [&'a str],
}

impl<A, B> Default for QmlModule<'_, A, B>
Expand All @@ -45,6 +47,7 @@ where
version_minor: 0,
qml_files: &[],
qrc_files: &[],
depends: &[],
}
}
}
Expand All @@ -58,6 +61,7 @@ pub(crate) struct OwningQmlModule {
pub version_minor: usize,
pub qml_files: Vec<PathBuf>,
pub qrc_files: Vec<PathBuf>,
pub depends: Vec<String>,
}

fn collect_pathbuf_vec(asref: &[impl AsRef<Path>]) -> Vec<PathBuf> {
Expand All @@ -72,6 +76,11 @@ impl<A: AsRef<Path>, B: AsRef<Path>> From<QmlModule<'_, A, B>> for OwningQmlModu
version_minor: other.version_minor,
qml_files: collect_pathbuf_vec(other.qml_files),
qrc_files: collect_pathbuf_vec(other.qrc_files),
depends: other
.depends
.into_iter()
.map(|depend| depend.to_string())
.collect(),
}
}
}
4 changes: 2 additions & 2 deletions crates/cxx-qt-gen/src/generator/cpp/cxxqttype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn generate(qobject_idents: &QObjectNames) -> Result<GeneratedCppQObjectBloc

result
.base_classes
.push(format!("::rust::cxxqt1::CxxQtType<{rust_struct}>"));
.push(format!("private ::rust::cxxqt1::CxxQtType<{rust_struct}>"));

Ok(result)
}
Expand All @@ -42,7 +42,7 @@ mod tests {
assert_eq!(generated.base_classes.len(), 1);
assert_eq!(
generated.base_classes[0],
"::rust::cxxqt1::CxxQtType<MyObjectRust>"
"private ::rust::cxxqt1::CxxQtType<MyObjectRust>"
);
}
}
Loading
Loading