From b2fa78dec3bc4a9513c6f3948d1253cfff1e4f12 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 02:25:19 +0300 Subject: [PATCH 01/12] Add log forwarding from Qt to log!() facade --- qmetaobject/Cargo.toml | 1 + qmetaobject/src/lib.rs | 5 +- qmetaobject/src/log.rs | 107 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 qmetaobject/src/log.rs diff --git a/qmetaobject/Cargo.toml b/qmetaobject/Cargo.toml index 1573b5c3..39666f78 100644 --- a/qmetaobject/Cargo.toml +++ b/qmetaobject/Cargo.toml @@ -16,6 +16,7 @@ chrono_qdatetime = ["chrono"] [dependencies] qmetaobject_impl = { path = "../qmetaobject_impl", version = "=0.1.4"} lazy_static = "1.0" +log = "0.4" cpp = "0.5.4" chrono = { version = "0.4", optional = true } diff --git a/qmetaobject/src/lib.rs b/qmetaobject/src/lib.rs index 7bd0eaea..6efa78e9 100644 --- a/qmetaobject/src/lib.rs +++ b/qmetaobject/src/lib.rs @@ -863,13 +863,15 @@ impl QMessageLogContext { /// Wrap Qt's QtMsgType enum #[repr(C)] -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub enum QtMsgType { QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg, QtInfoMsg, + // there is also one level defined in C++ code: + // QtSystemMsg = QtCriticalMsg } /// Wrap qt's qInstallMessageHandler. @@ -1022,6 +1024,7 @@ pub mod itemmodel; pub use itemmodel::*; pub mod listmodel; pub use listmodel::*; +pub mod log; pub mod qtdeclarative; pub use qtdeclarative::*; pub mod qmetatype; diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs new file mode 100644 index 00000000..78eeb0c9 --- /dev/null +++ b/qmetaobject/src/log.rs @@ -0,0 +1,107 @@ +//! Logging facilities and forwarding + +use log::{Level, logger, Record}; + +use crate::{install_message_handler, QMessageLogContext, QString, QtMsgType}; +use crate::QtMsgType::*; + +/// Mapping from Qt logging levels to Rust logging facade's levels. +/// +/// Due to the limited range of levels from both sides, +/// [`QtCriticalMsg`][`QtMsgType`] and [`QtFatalMsg`][`QtMsgType`] +/// both map to [`log::Level::Error`][Level], +/// while [`log::Level::Trace`][Level] is never returned. +/// +/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html +/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum +pub fn map_level(lvl: &QtMsgType) -> Level { + match lvl { + QtDebugMsg => Level::Debug, + QtInfoMsg => Level::Info, + QtWarningMsg => Level::Warn, + QtCriticalMsg => Level::Error, + QtFatalMsg => Level::Error, + // XXX: What are the external guarantees about possible values of QtMsgType? + // XXX: Are they promised to be limited to the valid enum variants? + } +} + +/// Mapping back from Rust logging facade's levels to Qt logging levels. +/// +/// Not used internally, exists just for completeness of API. +/// +/// Due to the limited range of levels from both sides, +/// [`log::Level::Debug`][Level] and [`log::Level::Trace`][Level] +/// both map to [`QtDebugMsg`][`QtMsgType`], +/// while [`QtFatalMsg`][`QtMsgType`] is never returned. +/// +/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html +/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum +pub fn unmap_level(lvl: Level) -> QtMsgType { + match lvl { + Level::Error => QtCriticalMsg, + Level::Warn => QtWarningMsg, + Level::Info => QtInfoMsg, + Level::Debug => QtDebugMsg, + Level::Trace => QtDebugMsg, + } +} + +// Logging middleware, pass-though, or just proxy function. +// It is called from Qt code, then it converts Qt logging data +// into Rust logging facade's log::Record object, and sends it +// to the currently active logger. +extern "C" fn log_capture(msg_type: QtMsgType, + context: &QMessageLogContext, + message: &QString) { + let level = map_level(&msg_type); + let target = match context.category() { + "" => "default", + x => x, + }; + let file = match context.file() { + "" => None, + x => Some(x), + }; + let line = match context.line() { + // In Qt, line numbers start from 1, while 0 is just a placeholder + 0 => None, + x => Some(x as _), + }; + let mut record = Record::builder(); + record.level(level) + .target(target) + .file(file) + .line(line) + .module_path(None); + // (inner) match with single all-capturing arm is a hack that allows us + // to extend the lifetime of a matched object for "a little longer". + // Basically, it retains bounded temporary values together with their + // intermediate values etc. This is also the way how println! macro works. + match context.function() { + "" => match format_args!("{}", message) { + args => logger().log(&record.args(args).build()), + }, + f => match format_args!("[in {}] {}", f, message) { + args => logger().log(&record.args(args).build()), + } + } +} + +/// Installs into Qt logging system a function which forwards messages to the +/// [Rust logging facade][log]. +/// +/// Most metadata from Qt logging context is retained and passed to [`log::Record`][]. +/// Logging levels are mapped with [this][map_level] algorithm. +/// +/// This function may be called more than once. +/// +/// [log]: https://docs.rs/log +/// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html +/// [map_level]: ./fn.map_level.html +pub fn init_qt_to_rust() { + // The reason it is named so complex instead of simple `init` is that + // such descriptive name is future-proof. Consider if someone someday + // would want to implement the opposite forwarding logger? + install_message_handler(log_capture); +} From 8cf67f9a34d2bb0b1e9ea394bc8d2a6bad608f5f Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 02:32:17 +0300 Subject: [PATCH 02/12] Move logging-related code to mod log --- qmetaobject/src/lib.rs | 67 +----------------------------------- qmetaobject/src/log.rs | 77 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/qmetaobject/src/lib.rs b/qmetaobject/src/lib.rs index 6efa78e9..0f2f7298 100644 --- a/qmetaobject/src/lib.rs +++ b/qmetaobject/src/lib.rs @@ -814,72 +814,6 @@ fn add_to_hash(hash: *mut c_void, key: i32, value: QByteArray) { /// Refer to the documentation of Qt::UserRole pub const USER_ROLE: i32 = 0x0100; -cpp_class!( - /// Wrapper for Qt's QMessageLogContext - pub unsafe struct QMessageLogContext as "QMessageLogContext" -); -impl QMessageLogContext { - // Return QMessageLogContext::line - pub fn line(&self) -> i32 { - cpp!(unsafe [self as "QMessageLogContext*"] -> i32 as "int" { return self->line; }) - } - // Return QMessageLogContext::file - pub fn file(&self) -> &str { - unsafe { - let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { - return self->file; - }); - if x.is_null() { - return ""; - } - std::ffi::CStr::from_ptr(x).to_str().unwrap() - } - } - // Return QMessageLogContext::function - pub fn function(&self) -> &str { - unsafe { - let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { - return self->function; - }); - if x.is_null() { - return ""; - } - std::ffi::CStr::from_ptr(x).to_str().unwrap() - } - } - // Return QMessageLogContext::category - pub fn category(&self) -> &str { - unsafe { - let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { - return self->category; - }); - if x.is_null() { - return ""; - } - std::ffi::CStr::from_ptr(x).to_str().unwrap() - } - } -} - -/// Wrap Qt's QtMsgType enum -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub enum QtMsgType { - QtDebugMsg, - QtWarningMsg, - QtCriticalMsg, - QtFatalMsg, - QtInfoMsg, - // there is also one level defined in C++ code: - // QtSystemMsg = QtCriticalMsg -} - -/// Wrap qt's qInstallMessageHandler. -/// Useful in order to forward the log to a rust logging framework -pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogContext, &QString)) { - cpp!(unsafe [logger as "QtMessageHandler"] { qInstallMessageHandler(logger); }) -} - /// Embed files and made them available to the Qt resource system. /// /// The macro accepts an identifier with optional preceding visibility modifier, @@ -1025,6 +959,7 @@ pub use itemmodel::*; pub mod listmodel; pub use listmodel::*; pub mod log; +pub use crate::log::*; pub mod qtdeclarative; pub use qtdeclarative::*; pub mod qmetatype; diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 78eeb0c9..56410fb2 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -1,9 +1,82 @@ //! Logging facilities and forwarding +use std::os::raw::c_char; + use log::{Level, logger, Record}; -use crate::{install_message_handler, QMessageLogContext, QString, QtMsgType}; -use crate::QtMsgType::*; +use crate::QString; + +use self::QtMsgType::*; + +cpp! {{ + #include +}} + +cpp_class!( + /// Wrapper for Qt's QMessageLogContext + pub unsafe struct QMessageLogContext as "QMessageLogContext" +); +impl QMessageLogContext { + // Return QMessageLogContext::line + pub fn line(&self) -> i32 { + cpp!(unsafe [self as "QMessageLogContext*"] -> i32 as "int" { return self->line; }) + } + // Return QMessageLogContext::file + pub fn file(&self) -> &str { + unsafe { + let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { + return self->file; + }); + if x.is_null() { + return ""; + } + std::ffi::CStr::from_ptr(x).to_str().unwrap() + } + } + // Return QMessageLogContext::function + pub fn function(&self) -> &str { + unsafe { + let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { + return self->function; + }); + if x.is_null() { + return ""; + } + std::ffi::CStr::from_ptr(x).to_str().unwrap() + } + } + // Return QMessageLogContext::category + pub fn category(&self) -> &str { + unsafe { + let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { + return self->category; + }); + if x.is_null() { + return ""; + } + std::ffi::CStr::from_ptr(x).to_str().unwrap() + } + } +} + +/// Wrap Qt's QtMsgType enum +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum QtMsgType { + QtDebugMsg, + QtWarningMsg, + QtCriticalMsg, + QtFatalMsg, + QtInfoMsg, + // there is also one level defined in C++ code: + // QtSystemMsg = QtCriticalMsg +} + +/// Wrap qt's qInstallMessageHandler. +/// Useful in order to forward the log to a rust logging framework +pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogContext, &QString)) { + cpp!(unsafe [logger as "QtMessageHandler"] { qInstallMessageHandler(logger); }) +} /// Mapping from Qt logging levels to Rust logging facade's levels. /// From 0875babdc70e4b9425574c2e5ab8bf1550fe36b9 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 03:09:12 +0300 Subject: [PATCH 03/12] log: impl From for QtMsgType<=>Level --- qmetaobject/src/log.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 56410fb2..2c9731b4 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -87,7 +87,7 @@ pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogCont /// /// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html /// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum -pub fn map_level(lvl: &QtMsgType) -> Level { +pub fn map_level(lvl: QtMsgType) -> Level { match lvl { QtDebugMsg => Level::Debug, QtInfoMsg => Level::Info, @@ -120,6 +120,24 @@ pub fn unmap_level(lvl: Level) -> QtMsgType { } } +impl From for Level { + /// Delegates to [default][] mapping algorithm. + /// + /// [default]: ./fn.map_level.html + fn from(lvl: QtMsgType) -> Self { + map_level(lvl) + } +} + +impl From for QtMsgType { + /// Delegates to [default][] mapping algorithm. + /// + /// [default]: ./fn.unmap_level.html + fn from(lvl: Level) -> Self { + unmap_level(lvl) + } +} + // Logging middleware, pass-though, or just proxy function. // It is called from Qt code, then it converts Qt logging data // into Rust logging facade's log::Record object, and sends it @@ -127,7 +145,7 @@ pub fn unmap_level(lvl: Level) -> QtMsgType { extern "C" fn log_capture(msg_type: QtMsgType, context: &QMessageLogContext, message: &QString) { - let level = map_level(&msg_type); + let level = msg_type.into(); let target = match context.category() { "" => "default", x => x, @@ -165,7 +183,7 @@ extern "C" fn log_capture(msg_type: QtMsgType, /// [Rust logging facade][log]. /// /// Most metadata from Qt logging context is retained and passed to [`log::Record`][]. -/// Logging levels are mapped with [this][map_level] algorithm. +/// Logging levels are mapped with the [default][map_level] algorithm. /// /// This function may be called more than once. /// From 70ecda995c6843a179cabf54d11afbd21255fc90 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 03:09:19 +0300 Subject: [PATCH 04/12] cosmit --- qmetaobject/src/log.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 2c9731b4..a7921773 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -16,6 +16,7 @@ cpp_class!( /// Wrapper for Qt's QMessageLogContext pub unsafe struct QMessageLogContext as "QMessageLogContext" ); + impl QMessageLogContext { // Return QMessageLogContext::line pub fn line(&self) -> i32 { @@ -72,12 +73,6 @@ pub enum QtMsgType { // QtSystemMsg = QtCriticalMsg } -/// Wrap qt's qInstallMessageHandler. -/// Useful in order to forward the log to a rust logging framework -pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogContext, &QString)) { - cpp!(unsafe [logger as "QtMessageHandler"] { qInstallMessageHandler(logger); }) -} - /// Mapping from Qt logging levels to Rust logging facade's levels. /// /// Due to the limited range of levels from both sides, @@ -138,6 +133,12 @@ impl From for QtMsgType { } } +/// Wrap qt's qInstallMessageHandler. +/// Useful in order to forward the log to a rust logging framework +pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogContext, &QString)) { + cpp!(unsafe [logger as "QtMessageHandler"] { qInstallMessageHandler(logger); }) +} + // Logging middleware, pass-though, or just proxy function. // It is called from Qt code, then it converts Qt logging data // into Rust logging facade's log::Record object, and sends it From 0f8d6ff80fc0188741ccf9cec7e8afa0f5ea37ee Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 03:22:55 +0300 Subject: [PATCH 05/12] log: docs & tests --- README.md | 1 + qmetaobject/src/log.rs | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 58b5e832..eafe2243 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ struct Greeter { } fn main() { + qmetaobject::log::init_qt_to_rust(); qml_register_type::(cstr!("Greeter"), 1, 0, cstr!("Greeter")); let mut engine = QmlEngine::new(); engine.load_data(r#"import QtQuick 2.6; import QtQuick.Window 2.0; diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index a7921773..a17c5728 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -62,7 +62,7 @@ impl QMessageLogContext { /// Wrap Qt's QtMsgType enum #[repr(C)] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum QtMsgType { QtDebugMsg, QtWarningMsg, @@ -180,7 +180,7 @@ extern "C" fn log_capture(msg_type: QtMsgType, } } -/// Installs into Qt logging system a function which forwards messages to the +/// Installs into [Qt logging system][qt-log] a function which forwards messages to the /// [Rust logging facade][log]. /// /// Most metadata from Qt logging context is retained and passed to [`log::Record`][]. @@ -188,6 +188,7 @@ extern "C" fn log_capture(msg_type: QtMsgType, /// /// This function may be called more than once. /// +/// [qt-log]: https://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler /// [log]: https://docs.rs/log /// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html /// [map_level]: ./fn.map_level.html @@ -197,3 +198,21 @@ pub fn init_qt_to_rust() { // would want to implement the opposite forwarding logger? install_message_handler(log_capture); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_double_init() { + // must not crash + init_qt_to_rust(); + init_qt_to_rust(); + } + + #[test] + fn test_convert() { + assert_eq!(Level::from(QtInfoMsg), Level::Info); + assert_eq!(QtCriticalMsg, QtMsgType::from(Level::Error)) + } +} From ddd53a174ce2c072814f4fdd717de23431c0a428 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 10:54:55 +0300 Subject: [PATCH 06/12] log: Revert level mapping to being impl From --- qmetaobject/src/log.rs | 86 ++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index a17c5728..78193644 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -73,63 +73,49 @@ pub enum QtMsgType { // QtSystemMsg = QtCriticalMsg } -/// Mapping from Qt logging levels to Rust logging facade's levels. -/// -/// Due to the limited range of levels from both sides, -/// [`QtCriticalMsg`][`QtMsgType`] and [`QtFatalMsg`][`QtMsgType`] -/// both map to [`log::Level::Error`][Level], -/// while [`log::Level::Trace`][Level] is never returned. -/// -/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html -/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum -pub fn map_level(lvl: QtMsgType) -> Level { - match lvl { - QtDebugMsg => Level::Debug, - QtInfoMsg => Level::Info, - QtWarningMsg => Level::Warn, - QtCriticalMsg => Level::Error, - QtFatalMsg => Level::Error, - // XXX: What are the external guarantees about possible values of QtMsgType? - // XXX: Are they promised to be limited to the valid enum variants? - } -} - -/// Mapping back from Rust logging facade's levels to Qt logging levels. -/// -/// Not used internally, exists just for completeness of API. -/// -/// Due to the limited range of levels from both sides, -/// [`log::Level::Debug`][Level] and [`log::Level::Trace`][Level] -/// both map to [`QtDebugMsg`][`QtMsgType`], -/// while [`QtFatalMsg`][`QtMsgType`] is never returned. -/// -/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html -/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum -pub fn unmap_level(lvl: Level) -> QtMsgType { - match lvl { - Level::Error => QtCriticalMsg, - Level::Warn => QtWarningMsg, - Level::Info => QtInfoMsg, - Level::Debug => QtDebugMsg, - Level::Trace => QtDebugMsg, - } -} - impl From for Level { - /// Delegates to [default][] mapping algorithm. + /// Mapping from Qt logging levels to Rust logging facade's levels. + /// + /// Due to the limited range of levels from both sides, + /// [`QtCriticalMsg`][`Qt::QtMsgType`] and [`QtFatalMsg`][`Qt::QtMsgType`] + /// both map to [`log::Level::Error`][Level], + /// while [`log::Level::Trace`][Level] is never returned. /// - /// [default]: ./fn.map_level.html + /// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html + /// [`Qt::QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum fn from(lvl: QtMsgType) -> Self { - map_level(lvl) + match lvl { + QtDebugMsg => Level::Debug, + QtInfoMsg => Level::Info, + QtWarningMsg => Level::Warn, + QtCriticalMsg => Level::Error, + QtFatalMsg => Level::Error, + // XXX: What are the external guarantees about possible values of QtMsgType? + // XXX: Are they promised to be limited to the valid enum variants? + } } } impl From for QtMsgType { - /// Delegates to [default][] mapping algorithm. + /// Mapping back from Rust logging facade's levels to Qt logging levels. + /// + /// Not used internally, exists just for completeness of API. /// - /// [default]: ./fn.unmap_level.html + /// Due to the limited range of levels from both sides, + /// [`log::Level::Debug`][Level] and [`log::Level::Trace`][Level] + /// both map to [`QtDebugMsg`][`Qt::QtMsgType`], + /// while [`QtFatalMsg`][`Qt::QtMsgType`] is never returned. + /// + /// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html + /// [`Qt::QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum fn from(lvl: Level) -> Self { - unmap_level(lvl) + match lvl { + Level::Error => QtCriticalMsg, + Level::Warn => QtWarningMsg, + Level::Info => QtInfoMsg, + Level::Debug => QtDebugMsg, + Level::Trace => QtDebugMsg, + } } } @@ -184,14 +170,14 @@ extern "C" fn log_capture(msg_type: QtMsgType, /// [Rust logging facade][log]. /// /// Most metadata from Qt logging context is retained and passed to [`log::Record`][]. -/// Logging levels are mapped with the [default][map_level] algorithm. +/// Logging levels are mapped with the default [`From`][lvl] implementation. /// /// This function may be called more than once. /// /// [qt-log]: https://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler /// [log]: https://docs.rs/log /// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html -/// [map_level]: ./fn.map_level.html +/// [lvl]: ./struct.QtMsgType.html pub fn init_qt_to_rust() { // The reason it is named so complex instead of simple `init` is that // such descriptive name is future-proof. Consider if someone someday From 20f481480ee636ec7aef39fb045a8c393776299f Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 11:17:37 +0300 Subject: [PATCH 07/12] log: add a note about ordering of QtMsgType --- qmetaobject/src/log.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 78193644..1af87624 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -62,6 +62,9 @@ impl QMessageLogContext { /// Wrap Qt's QtMsgType enum #[repr(C)] +// XXX: Do NOT derive Ord and PartialOrd. +// XXX: Variants are not ordered by severity. +// XXX: Also, levels ordering is not implemented in Qt, only == equality. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum QtMsgType { QtDebugMsg, From 6f1cfc68125d42719d3367d8fc07a41b4c283691 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 12:51:40 +0300 Subject: [PATCH 08/12] log: don't use self::QtMsgType::*; --- qmetaobject/src/log.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 1af87624..2c9d3258 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -6,8 +6,6 @@ use log::{Level, logger, Record}; use crate::QString; -use self::QtMsgType::*; - cpp! {{ #include }} @@ -88,11 +86,11 @@ impl From for Level { /// [`Qt::QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum fn from(lvl: QtMsgType) -> Self { match lvl { - QtDebugMsg => Level::Debug, - QtInfoMsg => Level::Info, - QtWarningMsg => Level::Warn, - QtCriticalMsg => Level::Error, - QtFatalMsg => Level::Error, + QtMsgType::QtDebugMsg => Level::Debug, + QtMsgType::QtInfoMsg => Level::Info, + QtMsgType::QtWarningMsg => Level::Warn, + QtMsgType::QtCriticalMsg => Level::Error, + QtMsgType::QtFatalMsg => Level::Error, // XXX: What are the external guarantees about possible values of QtMsgType? // XXX: Are they promised to be limited to the valid enum variants? } @@ -113,11 +111,11 @@ impl From for QtMsgType { /// [`Qt::QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum fn from(lvl: Level) -> Self { match lvl { - Level::Error => QtCriticalMsg, - Level::Warn => QtWarningMsg, - Level::Info => QtInfoMsg, - Level::Debug => QtDebugMsg, - Level::Trace => QtDebugMsg, + Level::Error => QtMsgType::QtCriticalMsg, + Level::Warn => QtMsgType::QtWarningMsg, + Level::Info => QtMsgType::QtInfoMsg, + Level::Debug => QtMsgType::QtDebugMsg, + Level::Trace => QtMsgType::QtDebugMsg, } } } @@ -201,7 +199,7 @@ mod tests { #[test] fn test_convert() { - assert_eq!(Level::from(QtInfoMsg), Level::Info); - assert_eq!(QtCriticalMsg, QtMsgType::from(Level::Error)) + assert_eq!(Level::from(QtMsgType::QtInfoMsg), Level::Info); + assert_eq!(QtMsgType::QtCriticalMsg, QtMsgType::from(Level::Error)) } } From 8aa15d1798dae675ff5d619a4f3f0dff13b0f3e3 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 12:52:54 +0300 Subject: [PATCH 09/12] log: Make it optional feature --- qmetaobject/Cargo.toml | 4 ++-- qmetaobject/src/log.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/qmetaobject/Cargo.toml b/qmetaobject/Cargo.toml index 39666f78..e3d68fee 100644 --- a/qmetaobject/Cargo.toml +++ b/qmetaobject/Cargo.toml @@ -11,14 +11,15 @@ keywords = ["Qt", "QML", "QMetaObject",] repository = "https://github.com/woboq/qmetaobject-rs" [features] +default = ["log"] chrono_qdatetime = ["chrono"] [dependencies] qmetaobject_impl = { path = "../qmetaobject_impl", version = "=0.1.4"} lazy_static = "1.0" -log = "0.4" cpp = "0.5.4" chrono = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } [build-dependencies] cpp_build = "0.5.4" @@ -29,7 +30,6 @@ cstr = "0.1" if_rust_version = "1" tempfile = "^3" - [package.metadata.docs.rs] dependencies = [ "qtbase5-dev", "qtdeclarative5-dev" ] rustc-args = [ "--cfg feature=\"docs-only\"" ] diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index 2c9d3258..a472a2d3 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -2,6 +2,7 @@ use std::os::raw::c_char; +#[cfg(feature = "log")] use log::{Level, logger, Record}; use crate::QString; @@ -74,6 +75,7 @@ pub enum QtMsgType { // QtSystemMsg = QtCriticalMsg } +#[cfg(feature = "log")] impl From for Level { /// Mapping from Qt logging levels to Rust logging facade's levels. /// @@ -97,6 +99,7 @@ impl From for Level { } } +#[cfg(feature = "log")] impl From for QtMsgType { /// Mapping back from Rust logging facade's levels to Qt logging levels. /// @@ -130,6 +133,7 @@ pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogCont // It is called from Qt code, then it converts Qt logging data // into Rust logging facade's log::Record object, and sends it // to the currently active logger. +#[cfg(feature = "log")] extern "C" fn log_capture(msg_type: QtMsgType, context: &QMessageLogContext, message: &QString) { @@ -179,6 +183,7 @@ extern "C" fn log_capture(msg_type: QtMsgType, /// [log]: https://docs.rs/log /// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html /// [lvl]: ./struct.QtMsgType.html +#[cfg(feature = "log")] pub fn init_qt_to_rust() { // The reason it is named so complex instead of simple `init` is that // such descriptive name is future-proof. Consider if someone someday @@ -187,6 +192,7 @@ pub fn init_qt_to_rust() { } #[cfg(test)] +#[cfg(feature = "log")] mod tests { use super::*; From bc6122eac1a6daec5805bf94e16d3d7e83ce8c5f Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 13:44:51 +0300 Subject: [PATCH 10/12] update README: document optional features --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eafe2243..51610979 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,33 @@ Window { Requires Qt >= 5.8 +## Cargo features + +Cargo provides a way to enable (or disable default) optional [features](https://doc.rust-lang.org/cargo/reference/features.html). + +### `log` + +By default, Qt's logging system is not initialized, and messages from e.g. QML's `console.log` don't go anywhere. +The "log" feature enables integration with [`log`](https://crates.io/crates/log) crate, the Rust logging facade. + +The feature is enabled by default. To activate it, execute the following code as early as possible in `main()`: + +```rust +fn main() { + qmetaobject::log::init_qt_to_rust(); + // don't forget to set up env_logger or any other logging backend. +} +``` + +### `chrono_qdatetime` + +Enables interoperability of `QDate` and `QTime` with Rust [`chrono`](https://crates.io/crates/chrono) package. + +This feature is disabled by default. + ## What if a binding for the Qt C++ API you want to use is missing? -It is quite likely that you would like to call a particular Qt function wich is not wrapped by +It is quite likely that you would like to call a particular Qt function which is not wrapped by this crate. In this case, it is always possible to access C++ directly from your rust code using the cpp! macro. From 3da8fe712d1ff3ca9ea92938d0d5114b3fc20bea Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 17:36:06 +0300 Subject: [PATCH 11/12] log: install_message_handler takes Option + docs This is breaking change for old code. --- qmetaobject/src/log.rs | 22 +++++++++++++++++----- qmetaobject/tests/common/mod.rs | 8 ++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index a472a2d3..d2f1c8f3 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -123,10 +123,22 @@ impl From for QtMsgType { } } -/// Wrap qt's qInstallMessageHandler. -/// Useful in order to forward the log to a rust logging framework -pub fn install_message_handler(logger: extern "C" fn(QtMsgType, &QMessageLogContext, &QString)) { - cpp!(unsafe [logger as "QtMessageHandler"] { qInstallMessageHandler(logger); }) +/// Wrapper for [`QtMessageHandler`][] typedef. +/// +/// [`QtMessageHandler`]: https://doc.qt.io/qt-5/qtglobal.html#QtMessageHandler-typedef +pub type QtMessageHandler = Option; + +/// Wrapper for [`qInstallMessageHandler`] function. +/// +/// # Wrapper-specific behavior +/// +/// To restore the message handler, call `install_message_handler(None)`. +/// +/// [`qInstallMessageHandler`]: https://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler +pub fn install_message_handler(logger: QtMessageHandler) -> QtMessageHandler { + cpp!(unsafe [logger as "QtMessageHandler"] -> QtMessageHandler as "QtMessageHandler" { + return qInstallMessageHandler(logger); + }) } // Logging middleware, pass-though, or just proxy function. @@ -188,7 +200,7 @@ pub fn init_qt_to_rust() { // The reason it is named so complex instead of simple `init` is that // such descriptive name is future-proof. Consider if someone someday // would want to implement the opposite forwarding logger? - install_message_handler(log_capture); + install_message_handler(Some(log_capture)); } #[cfg(test)] diff --git a/qmetaobject/tests/common/mod.rs b/qmetaobject/tests/common/mod.rs index 16409033..bcbd1d2b 100644 --- a/qmetaobject/tests/common/mod.rs +++ b/qmetaobject/tests/common/mod.rs @@ -52,7 +52,7 @@ pub fn do_test(obj: T, qml: &str) -> bool { let _lock = lock_for_test(); QML_LOGS.lock().unwrap_or_else(|e| e.into_inner()).clear(); - install_message_handler(log_capture); + install_message_handler(Some(log_capture)); let qml_text = "import QtQuick 2.0\n".to_owned() + qml; @@ -69,7 +69,7 @@ pub fn do_test_error_with_url(obj: T, qml: &str, url: &str) let _lock = lock_for_test(); QML_LOGS.lock().unwrap_or_else(|e| e.into_inner()).clear(); - install_message_handler(log_capture); + install_message_handler(Some(log_capture)); let qml_text = "import QtQuick 2.0\n".to_owned() + qml; @@ -87,7 +87,7 @@ pub fn do_test_variant(obj: QVariant, qml: &str) -> bool { let _lock = lock_for_test(); QML_LOGS.lock().unwrap_or_else(|e| e.into_inner()).clear(); - install_message_handler(log_capture); + install_message_handler(Some(log_capture)); let qml_text = "import QtQuick 2.0\n".to_owned() + qml; @@ -101,7 +101,7 @@ pub fn test_loading_logs(qml: &str, log: &str) -> bool { let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); QML_LOGS.lock().unwrap_or_else(|e| e.into_inner()).clear(); - install_message_handler(log_capture); + install_message_handler(Some(log_capture)); let qml_text = "import QtQuick 2.0\n".to_owned() + qml; From 91521f84de9e1fd4b1e5d049dd68cce5740e74b4 Mon Sep 17 00:00:00 2001 From: ivan tkachenko Date: Fri, 1 May 2020 17:47:25 +0300 Subject: [PATCH 12/12] log: Reformat docs --- qmetaobject/src/log.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/qmetaobject/src/log.rs b/qmetaobject/src/log.rs index d2f1c8f3..41e4c291 100644 --- a/qmetaobject/src/log.rs +++ b/qmetaobject/src/log.rs @@ -12,16 +12,19 @@ cpp! {{ }} cpp_class!( - /// Wrapper for Qt's QMessageLogContext + /// Wrapper for [`QMessageLogContext`] class. + /// + /// [`QMessageLogContext`]: https://doc.qt.io/qt-5/qmessagelogcontext.html pub unsafe struct QMessageLogContext as "QMessageLogContext" ); impl QMessageLogContext { - // Return QMessageLogContext::line + /// Wrapper for `QMessageLogContext::line`. pub fn line(&self) -> i32 { cpp!(unsafe [self as "QMessageLogContext*"] -> i32 as "int" { return self->line; }) } - // Return QMessageLogContext::file + + /// Wrapper for `QMessageLogContext::file`. pub fn file(&self) -> &str { unsafe { let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { @@ -33,7 +36,8 @@ impl QMessageLogContext { std::ffi::CStr::from_ptr(x).to_str().unwrap() } } - // Return QMessageLogContext::function + + /// Wrapper for `QMessageLogContext::function`. pub fn function(&self) -> &str { unsafe { let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { @@ -45,7 +49,8 @@ impl QMessageLogContext { std::ffi::CStr::from_ptr(x).to_str().unwrap() } } - // Return QMessageLogContext::category + + /// Wrapper for `QMessageLogContext::category`. pub fn category(&self) -> &str { unsafe { let x = cpp!([self as "QMessageLogContext*"] -> *const c_char as "const char*" { @@ -59,7 +64,9 @@ impl QMessageLogContext { } } -/// Wrap Qt's QtMsgType enum +/// Wrapper for [`Qt::QtMsgType`][] enum. +/// +/// [`Qt::QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum #[repr(C)] // XXX: Do NOT derive Ord and PartialOrd. // XXX: Variants are not ordered by severity. @@ -192,7 +199,7 @@ extern "C" fn log_capture(msg_type: QtMsgType, /// This function may be called more than once. /// /// [qt-log]: https://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler -/// [log]: https://docs.rs/log +/// [log]: https://crates.io/crates/log /// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html /// [lvl]: ./struct.QtMsgType.html #[cfg(feature = "log")]