Skip to content

Commit

Permalink
Merge branch 'main' into quuid
Browse files Browse the repository at this point in the history
  • Loading branch information
jnbooth committed Jan 13, 2025
2 parents 9971b2d + 1f72cdd commit ec120fe
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/github-cxx-qt-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v18
- uses: DavidAnson/markdownlint-cli2-action@v19
with:
globs: '**/*.md'

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `QDateTime::from_string` to parse `QDateTime` from a `QString`.
- Support for further types: `QUuid`

### Fixed
Expand Down
85 changes: 84 additions & 1 deletion crates/cxx-qt-build/src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@
use crate::{crate_name, module_name_from_uri};
use std::io::Result;
use std::{
env,
env, fs,
path::{Path, PathBuf},
};

/// On Unix platforms, included files are symlinked into destination folders.
/// On non-Unix platforms, due to poor support for symlinking, included files are deep copied.
#[cfg(unix)]
pub(crate) const INCLUDE_VERB: &str = "create symlink";
/// On Unix platforms, included files are symlinked into destination folders.
/// On non-Unix platforms, due to poor support for symlinking, included files are deep copied.
#[cfg(not(unix))]
pub(crate) const INCLUDE_VERB: &str = "deep copy files";

// Clean a directory by removing it and recreating it.
pub(crate) fn clean(path: impl AsRef<Path>) -> Result<()> {
let result = std::fs::remove_dir_all(&path);
Expand Down Expand Up @@ -81,3 +90,77 @@ pub(crate) fn out() -> PathBuf {
pub(crate) fn is_exporting() -> bool {
export().is_some()
}

#[cfg(unix)]
pub(crate) fn symlink_or_copy_directory(
source: impl AsRef<Path>,
dest: impl AsRef<Path>,
) -> Result<bool> {
match std::os::unix::fs::symlink(&source, &dest) {
Ok(()) => Ok(true),
Err(e) if e.kind() != std::io::ErrorKind::AlreadyExists => Err(e),
// Two dependencies may be reexporting the same shared dependency, which will
// result in conflicting symlinks.
// Try detecting this by resolving the symlinks and checking whether this leads us
// to the same paths. If so, it's the same include path for the same prefix, which
// is fine.
Err(_) => Ok(fs::canonicalize(source)? == fs::canonicalize(dest)?),
}
}

#[cfg(not(unix))]
pub(crate) fn symlink_or_copy_directory(
source: impl AsRef<Path>,
dest: impl AsRef<Path>,
) -> Result<bool> {
deep_copy_directory(source.as_ref(), dest.as_ref())
}

#[cfg(not(unix))]
fn deep_copy_directory(source: &Path, dest: &Path) -> Result<bool> {
fs::create_dir_all(dest)?;
for entry in fs::read_dir(source)? {
let entry = entry?;
let source_path = entry.path();
let dest_path = dest.join(entry.file_name());
if entry.file_type()?.is_dir() {
if deep_copy_directory(&source_path, &dest_path)? {
continue;
}
return Ok(false);
}
if !dest_path.try_exists()? {
fs::copy(&source_path, &dest_path)?;
} else if files_conflict(&source_path, &dest_path)? {
return Ok(false);
}
}
Ok(true)
}

#[cfg(not(unix))]
fn files_conflict(source: &Path, dest: &Path) -> Result<bool> {
use fs::File;
use std::io::{BufRead, BufReader};
let source = File::open(source)?;
let dest = File::open(dest)?;
if source.metadata()?.len() != dest.metadata()?.len() {
return Ok(true);
}
let mut source = BufReader::new(source);
let mut dest = BufReader::new(dest);
loop {
let source_bytes = source.fill_buf()?;
let bytes_len = source_bytes.len();
let dest_bytes = dest.fill_buf()?;
let bytes_len = bytes_len.min(dest_bytes.len());
if bytes_len == 0 {
return Ok(false);
}
if source_bytes[..bytes_len] != dest_bytes[..bytes_len] {
return Ok(true);
}
source.consume(bytes_len);
dest.consume(bytes_len);
}
}
71 changes: 26 additions & 45 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod diagnostics;
use diagnostics::{Diagnostic, GeneratedError};

pub mod dir;
use dir::INCLUDE_VERB;

mod dependencies;
pub use dependencies::Interface;
Expand Down Expand Up @@ -630,49 +631,28 @@ impl CxxQtBuilder {
}
}

fn symlink_directory(target: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
#[cfg(unix)]
let result = std::os::unix::fs::symlink(target, link);

#[cfg(windows)]
let result = std::os::windows::fs::symlink_dir(target, link);

// TODO: If it's neither unix nor windows, we should probably just deep-copy the
// dependency headers into our own include directory.
#[cfg(not(any(unix, windows)))]
panic!("Cxx-Qt-build: Unsupported platform! Only unix and windows are currently supported! Please file a bug report in the CXX-Qt repository.");

result
}

// A dependency can specify which of its own include paths it wants to export.
// Set up each of these exported include paths as symlinks in our own include directory.
// Set up each of these exported include paths as symlinks in our own include directory,
// or deep copy the files if the platform does not support symlinks.
fn include_dependency(&mut self, dependency: &Dependency) {
let header_root = dir::header_root();
let dependency_root = dependency.path.join("include");
for include_prefix in &dependency.manifest.exported_include_prefixes {
// setup include directory
let target = dependency.path.join("include").join(include_prefix);

let symlink = dir::header_root().join(include_prefix);
if symlink.exists() {
// Two dependencies may be reexporting the same shared dependency, which will
// result in conflicting symlinks.
// Try detecting this by resolving the symlinks and checking whether this leads us
// to the same paths. If so, it's the same include path for the same prefix, which
// is fine.
let symlink =
std::fs::canonicalize(symlink).expect("Failed to canonicalize symlink!");
let target =
std::fs::canonicalize(target).expect("Failed to canonicalize symlink target!");
if symlink != target {
let source = dependency_root.join(include_prefix);
let dest = header_root.join(include_prefix);

match dir::symlink_or_copy_directory(source, dest) {
Ok(true) => (),
Ok(false) => {
panic!(
"Conflicting include_prefixes for {include_prefix}!\nDependency {dep_name} conflicts with existing include path",
dep_name = dependency.manifest.name,
);
}
} else {
Self::symlink_directory(target, symlink).unwrap_or_else(|_| {
panic!("Could not create symlink for include_prefix {include_prefix}!")
});
Err(e) => {
panic!("Could not {INCLUDE_VERB} for include_prefix {include_prefix}: {e:?}");
}
}
}
}
Expand Down Expand Up @@ -1019,17 +999,18 @@ impl CxxQtBuilder {
}

fn write_interface_include_dirs(&self) {
if let Some(interface) = &self.public_interface {
for (header_dir, symlink) in &interface.exported_include_directories {
Self::symlink_directory(header_dir, dir::header_root().join(symlink))
.unwrap_or_else(|_| {
panic!(
"Failed to create symlink `{}` for export_include_directory: {}",
symlink,
header_dir.to_string_lossy()
)
});
}
let Some(interface) = &self.public_interface else {
return;
};
let header_root = dir::header_root();
for (header_dir, dest) in &interface.exported_include_directories {
let dest_dir = header_root.join(dest);
if let Err(e) = dir::symlink_or_copy_directory(header_dir, dest_dir) {
panic!(
"Failed to {INCLUDE_VERB} `{dest}` for export_include_directory `{dir_name}`: {e:?}",
dir_name = header_dir.to_string_lossy()
)
};
}
}

Expand Down
9 changes: 9 additions & 0 deletions crates/cxx-qt-lib/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
# SPDX-FileContributor: Joshua Booth <[email protected]>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

src/core/qlist/qlist_*.rs linguist-generated=true
src/core/qset/qset_*.rs linguist-generated=true
src/core/qvariant/qvariant_*.rs linguist-generated=true
src/core/qvector/qvector_*.rs linguist-generated=true
2 changes: 2 additions & 0 deletions crates/cxx-qt-lib/include/core/qdatetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@ ::std::int64_t
qdatetimeToSecsSinceEpoch(const QDateTime& datetime);
void
qdatetimeSetTimeZone(QDateTime& datetime, const QTimeZone& timeZone);
QDateTime
qdatetimeFromQString(const QString& string, Qt::DateFormat format);
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/include/core/qstringlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ struct IsRelocatable<QStringList> : ::std::true_type
namespace rust {
namespace cxxqtlib1 {

const QList<QString>&
qstringlistAsQListQStringRef(const QStringList& list);
QList<QString>&
qstringlistAsQListQStringRef(QStringList& list);
QStringList
qstringlistFromQListQString(const QList<QString>& list);
QList<QString>
Expand Down
6 changes: 6 additions & 0 deletions crates/cxx-qt-lib/src/core/qdatetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,11 @@ qdatetimeSetTimeZone(QDateTime& datetime, const QTimeZone& timeZone)
#endif
}

QDateTime
qdatetimeFromQString(const QString& string, const Qt::DateFormat format)
{
return QDateTime::fromString(string, format);
}

}
}
13 changes: 13 additions & 0 deletions crates/cxx-qt-lib/src/core/qdatetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod ffi {
unsafe extern "C++" {
include!("cxx-qt-lib/qt.h");
type TimeSpec = crate::TimeSpec;
type DateFormat = crate::DateFormat;
}

unsafe extern "C++" {
Expand Down Expand Up @@ -169,6 +170,8 @@ mod ffi {
fn qdatetimeToSecsSinceEpoch(datetime: &QDateTime) -> i64;
#[rust_name = "qdatetime_settimezone"]
fn qdatetimeSetTimeZone(datetime: &mut QDateTime, time_zone: &QTimeZone);
#[rust_name = "qdatetime_from_string"]
fn qdatetimeFromQString(string: &QString, format: DateFormat) -> QDateTime;
}

#[namespace = "rust::cxxqtlib1"]
Expand Down Expand Up @@ -298,6 +301,16 @@ impl QDateTime {
ffi::qdatetime_from_secs_since_epoch(secs, time_zone)
}

/// Returns the datetime represented in the string as a QDateTime using the format given, or None if this is not possible.
pub fn from_string(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> {
let date = ffi::qdatetime_from_string(string, format);
if date.is_valid() {
Some(date)
} else {
None
}
}

/// Returns the number of milliseconds from this datetime to the other datetime.
/// If the other datetime is earlier than this datetime, the value returned is negative.
pub fn msecs_to(&self, other: &Self) -> i64 {
Expand Down
12 changes: 12 additions & 0 deletions crates/cxx-qt-lib/src/core/qstringlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ static_assert(QTypeInfo<QStringList>::isRelocatable);
namespace rust {
namespace cxxqtlib1 {

const QList<QString>&
qstringlistAsQListQStringRef(const QStringList& list)
{
return static_cast<const QList<QString>&>(list);
}

QList<QString>&
qstringlistAsQListQStringRef(QStringList& list)
{
return static_cast<QList<QString>&>(list);
}

QStringList
qstringlistFromQListQString(const QList<QString>& list)
{
Expand Down
35 changes: 35 additions & 0 deletions crates/cxx-qt-lib/src/core/qstringlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::{QList, QString};
use core::mem::MaybeUninit;
use cxx::{type_id, ExternType};
use std::ops::{Deref, DerefMut};

#[cxx::bridge]
mod ffi {
Expand Down Expand Up @@ -79,6 +80,11 @@ mod ffi {

#[namespace = "rust::cxxqtlib1"]
unsafe extern "C++" {
#[doc(hidden)]
#[rust_name = "qstringlist_as_qlist_qstring_ref"]
fn qstringlistAsQListQStringRef(list: &QStringList) -> &QList_QString;
#[rust_name = "qstringlist_as_qlist_qstring_ref_mut"]
fn qstringlistAsQListQStringRef(list: &mut QStringList) -> &mut QList_QString;
#[doc(hidden)]
#[rust_name = "qstringlist_from_qlist_qstring"]
fn qstringlistFromQListQString(list: &QList_QString) -> QStringList;
Expand Down Expand Up @@ -174,10 +180,39 @@ impl From<&QStringList> for QList<QString> {
}
}

impl Deref for QStringList {
type Target = QList<QString>;

fn deref(&self) -> &Self::Target {
ffi::qstringlist_as_qlist_qstring_ref(self)
}
}

impl DerefMut for QStringList {
fn deref_mut(&mut self) -> &mut Self::Target {
ffi::qstringlist_as_qlist_qstring_ref_mut(self)
}
}

// Safety:
//
// Static checks on the C++ side to ensure the size is the same.
unsafe impl ExternType for QStringList {
type Id = type_id!("QStringList");
type Kind = cxx::kind::Trivial;
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn deref() {
let mut list = QStringList::default();
list.append(QString::from("element"));
assert_eq!(
list.get(0).map(|s| s.to_string()),
Some("element".to_owned())
);
}
}

0 comments on commit ec120fe

Please sign in to comment.