From f45a66f47dc727e3ccb13037a6c57923af1446c7 Mon Sep 17 00:00:00 2001 From: Grey Date: Wed, 30 Oct 2024 19:59:04 +0800 Subject: [PATCH 01/33] Add a .repos file for jazzy (#425) Signed-off-by: Michael X. Grey Signed-off-by: Michael X. Grey --- ros2_rust_jazzy.repos | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 ros2_rust_jazzy.repos diff --git a/ros2_rust_jazzy.repos b/ros2_rust_jazzy.repos new file mode 100644 index 000000000..3f72f560d --- /dev/null +++ b/ros2_rust_jazzy.repos @@ -0,0 +1,29 @@ +repositories: + ros2/common_interfaces: + type: git + url: https://github.com/ros2/common_interfaces.git + version: jazzy + ros2/example_interfaces: + type: git + url: https://github.com/ros2/example_interfaces.git + version: jazzy + ros2/rcl_interfaces: + type: git + url: https://github.com/ros2/rcl_interfaces.git + version: jazzy + ros2/test_interface_files: + type: git + url: https://github.com/ros2/test_interface_files.git + version: jazzy + ros2/rosidl_core: + type: git + url: https://github.com/ros2/rosidl_core.git + version: jazzy + ros2/rosidl_defaults: + type: git + url: https://github.com/ros2/rosidl_defaults.git + version: jazzy + ros2/unique_identifier_msgs: + type: git + url: https://github.com/ros2/unique_identifier_msgs.git + version: jazzy From 7b512490365eaaf28aa35a4efaaf4ae386a58bcb Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 4 Nov 2024 02:37:02 +0800 Subject: [PATCH 02/33] Introduce Logger and LogParams Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 610 ++++++++++++++++++-------------- rclrs/src/logging/log_params.rs | 240 +++++++++++++ rclrs/src/logging/logger.rs | 108 ++++++ rclrs/src/node.rs | 29 +- rclrs/src/node/builder.rs | 18 +- 5 files changed, 717 insertions(+), 288 deletions(-) create mode 100644 rclrs/src/logging/log_params.rs create mode 100644 rclrs/src/logging/logger.rs diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index e4f789c4d..5a08e189d 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -3,198 +3,25 @@ // Adapted from https://github.com/sequenceplanner/r2r/blob/89cec03d07a1496a225751159cbc7bfb529d9dd1/r2r/src/utils.rs // Further adapted from https://github.com/mvukov/rules_ros2/pull/371 -use std::{ffi::CString, sync::Mutex, time::Duration}; +use std::{ffi::CString, sync::{LazyLock, Mutex}, collections::HashMap}; -use crate::rcl_bindings::*; +use crate::{ + rcl_bindings::*, + ENTITY_LIFECYCLE_MUTEX, +}; -// Used to protect calls to rcl/rcutils in case those functions manipulate global variables -static LOG_GUARD: Mutex<()> = Mutex::new(()); +mod log_params; +pub use log_params::*; -/// Calls the underlying rclutils logging function -/// Don't call this directly, use the logging macros instead. -/// -/// # Panics -/// -/// This function might panic in the following scenarios: -/// - A logger_name is provided that is not a valid c-string, e.g. contains extraneous null characters -/// - The user-supplied "msg" is not a valid c-string, e.g. contains extraneous null characters -/// - When called if the lock is already held by the current thread. -/// - If the construction of CString objects used to create the log output fail, -/// although, this highly unlikely to occur in most cases -#[doc(hidden)] -pub fn log(msg: &str, logger_name: &str, file: &str, line: u32, severity: LogSeverity) { - // currently not possible to get function name in rust. - // see https://github.com/rust-lang/rfcs/pull/2818 - let function = CString::new("").unwrap(); - let file = CString::new(file).unwrap(); - let location = rcutils_log_location_t { - function_name: function.as_ptr(), - file_name: file.as_ptr(), - line_number: line as usize, - }; - let format = CString::new("%s").unwrap(); - let logger_name = CString::new(logger_name) - .expect("Logger name is a valid c style string, e.g. check for extraneous null characters"); - let message = CString::new(msg) - .expect("Valid c style string required, e.g. check for extraneous null characters"); - let severity = severity.to_native(); - - let _guard = LOG_GUARD.lock().unwrap(); - // SAFETY: Global variables are protected via LOG_GUARD, no other preconditions are required - unsafe { - rcutils_log( - &location, - severity as i32, - logger_name.as_ptr(), - format.as_ptr(), - message.as_ptr(), - ); - } -} - -/// Logging severity -#[doc(hidden)] -pub enum LogSeverity { - Unset, - Debug, - Info, - Warn, - Error, - Fatal, -} - -impl LogSeverity { - fn to_native(&self) -> RCUTILS_LOG_SEVERITY { - use crate::rcl_bindings::rcl_log_severity_t::*; - match self { - LogSeverity::Unset => RCUTILS_LOG_SEVERITY_UNSET, - LogSeverity::Debug => RCUTILS_LOG_SEVERITY_DEBUG, - LogSeverity::Info => RCUTILS_LOG_SEVERITY_INFO, - LogSeverity::Warn => RCUTILS_LOG_SEVERITY_WARN, - LogSeverity::Error => RCUTILS_LOG_SEVERITY_ERROR, - LogSeverity::Fatal => RCUTILS_LOG_SEVERITY_FATAL, - } - } -} - -#[derive(Debug)] -/// Specify when a log message should be published -pub enum LoggingOccurrence { - /// The log message will always be published (assuming all other conditions are met) - Always, - /// The message will only be published on the first occurrence (Note: no other conditions apply) - Once, - /// The log message will not be published on the first occurrence, but will be published on - /// each subsequent occurrence (assuming all other conditions are met) - SkipFirst, -} +mod logger; +pub use logger::*; -/// Specify conditions that must be met for a log message to be published -/// -/// The struct provides the following convenience functions to construct conditions that match -/// behaviour available in the `rclcpp` and `rclpy` libraries. -/// -/// When will my log message be output? -/// -/// - `Once`: A message with the [`LoggingOccurrence::Once`] value will be published once only -/// regardless of any other conditions -/// - `SkipFirst`: A message with the [`LoggingOccurrence::SkipFirst`] value will never be published -/// on the first encounter regardless of any other conditions. After the first -/// encounter, the behaviour is identical to the [`LoggingOccurrence::Always`] setting. -/// - `Always`: The log message will be output if all additional conditions are true: -/// - The current time + the `publish_interval` > the last time the message was published. -/// - The default value for `publish_interval` is 0, i.e. the interval check will always pass -/// - The `log_if_true` expression evaluates to TRUE. -/// - The default value for the `log_if_true` field is TRUE. -pub struct LogConditions { - /// Specify when a log message should be published (See[`LoggingOccurrence`] above) - pub occurs: LoggingOccurrence, - /// Specify the publication interval of the message. A value of ZERO (0) indicates that the - /// message should be published every time, otherwise, the message will only be published once - /// the specified interval has elapsed. - /// This field is typically used to limit the output from high-frequency messages, e.g. instead - /// of publishing a log message every 10 milliseconds, the `publish_interval` can be configured - /// such that the message is published every 10 seconds. - pub publish_interval: Duration, - /// The log message will only published if the specified expression evaluates to true - pub log_if_true: bool, -} - -impl LogConditions { - /// Default construct an instance - pub fn new() -> Self { - Self { - occurs: LoggingOccurrence::Always, - publish_interval: Duration::ZERO, - log_if_true: true, - } - } - - /// Only publish this message the first time it is encountered - pub fn once() -> Self { - Self { - occurs: LoggingOccurrence::Once, - publish_interval: Duration::ZERO, - log_if_true: true, - } - } - - /// Do not publish the message the first time it is encountered - pub fn skip_first() -> Self { - Self { - occurs: LoggingOccurrence::SkipFirst, - publish_interval: Duration::ZERO, - log_if_true: true, - } - } - - /// Do not publish the first time this message is encountered and publish - /// at the specified `publish_interval` thereafter - pub fn skip_first_throttle(publish_interval: Duration) -> Self { - Self { - occurs: LoggingOccurrence::SkipFirst, - publish_interval, - log_if_true: true, - } - } - - /// Throttle the message to the supplied publish_interval - /// e.g. set `publish_interval` to 1000ms to limit publishing to once a second - pub fn throttle(publish_interval: Duration) -> Self { - Self { - occurs: LoggingOccurrence::Always, - publish_interval, - log_if_true: true, - } - } - - /// Permitting logging if the supplied expression evaluates to true - /// Uses default LoggingOccurrence (Always) and publish_interval (no throttling) - pub fn log_if_true(log_if_true: bool) -> Self { - Self { - occurs: LoggingOccurrence::Always, - publish_interval: Duration::ZERO, - log_if_true, - } - } -} - -/// log_with_conditions -/// Helper macro to log a message using the ROS2 RCUTILS library -/// -/// The macro supports two general calling formats: -/// 1. Plain message string e.g. as per println! macro -/// 2. With calling conditions that provide some restrictions on the output of the message -/// (see [`LogConditions`] above) -/// -/// It is expected that, typically, the macro will be called using one of the wrapper macros, -/// e.g. log_debug!, etc, however, it is relatively straight forward to call the macro directly -/// if you really want to. +/// log a message to rosout /// /// # Examples /// /// ``` -/// use rclrs::{log_debug, log_error, log_fatal, log_info, log_warn, LogConditions, LoggingOccurrence}; +/// use rclrs::{log, AsLogParams}; /// use std::sync::Mutex; /// use std::time::Duration; /// use std::env; @@ -202,141 +29,382 @@ impl LogConditions { /// let context = rclrs::Context::new(env::args()).unwrap(); /// let node = rclrs::Node::new(&context, "test_node").unwrap(); /// -/// log_debug!(&node.name(), "Simple message"); +/// log!(node.debug(), "Simple debug message"); /// let some_variable = 43; -/// log_debug!(&node.name(), "Simple message {some_variable}"); -/// log_fatal!(&node.name(), "Simple message from {}", node.name()); -/// log_warn!(&node.name(), LogConditions::once(), "Only log this the first time"); -/// log_error!(&node.name(), -/// LogConditions::skip_first_throttle(Duration::from_millis(1000)), -/// "Noisy error that we expect the first time"); +/// log!(node.debug(), "Formatted debug message: {some_variable}"); +/// log!(node.fatal(), "Fatal message from {}", node.name()); +/// log!(node.warn().once(), "Only log this the first time"); +/// log!( +/// node +/// .error() +/// .skip_first() +/// .interval(Duration::from_millis(1000)), +/// "Noisy error that we expect the first time" +/// ); /// /// let count = 0; -/// log_info!(&node.name(), LogConditions { occurs: LoggingOccurrence::Always, -/// publish_interval: Duration::from_millis(1000), -/// log_if_true: count % 10 == 0, }, -/// "Manually constructed LogConditions"); +/// log!( +/// node +/// .info() +/// .interval(Duration::from_millis(1000)) +/// .only_if(count % 10 == 0), +/// "Manually constructed LogConditions", +/// ); /// ``` /// +/// All of the above examples will also work with the severity-specific log macros, +/// but any severity that gets passed in will be overridden: +/// - [`log_debug`] +/// - [`log_info`] +/// - [`log_warn`] +/// - [`log_error`] +/// - [`log_fatal`] +/// /// # Panics /// /// It is theoretically possible for the call to panic if the Mutex used for the throttling is -/// poisoned although this should not be possible. +/// poisoned although this should never happen. #[macro_export] -macro_rules! log_with_conditions { +macro_rules! log { // The variable args captured by the $(, $($args:tt)*)?)) code allows us to omit (or include) - // parameters in the simple message case, e.g. to write + // formatting parameters in the simple message case, e.g. to write // ``` // log_error!(, "Log with no params"); // OR // log_error!(, "Log with useful info {}", error_reason); - ($severity: expr, $logger_name: expr, $msg_start: literal $(, $($args:tt)*)?) => { - $crate::log(&std::fmt::format(format_args!($msg_start, $($($args)*)?)), $logger_name, file!(), line!(), $severity); - }; - ($severity: expr, $logger_name: expr, $conditions: expr, $($args:tt)*) => { + ($as_log_params: expr, $($args:tt)*) => {{ // Adding these use statements here due an issue like this one: // https://github.com/intellij-rust/intellij-rust/issues/9853 // Note: that issue appears to be specific to jetbrains intellisense however, // observed same/similar behaviour with rust-analyzer/rustc - use std::sync::Once; + use std::sync::{Once, LazyLock, Mutex}; use std::time::SystemTime; - let log_conditions: $crate::LogConditions = $conditions; - let mut allow_logging = true; - match log_conditions.occurs { - // Create the static variables here so we get a per-instance static - $crate::LoggingOccurrence::Once => { - static LOG_ONCE: std::sync::Once = std::sync::Once::new(); - LOG_ONCE.call_once(|| { - $crate::log(&std::fmt::format(format_args!($($args)*)), $logger_name, file!(), line!(), $severity); - }); - allow_logging = false; - } - $crate::LoggingOccurrence::SkipFirst => { - // Noop, just make sure we exit the first time... - static SKIP_FIRST: std::sync::Once = std::sync::Once::new(); - SKIP_FIRST.call_once(|| { - // Only disable logging the first time - allow_logging = false; - }); + // We wrap the functional body of the macro inside of a closure which + // we immediately trigger. This allows us to use `return` to exit the + // macro early without causing the calling function to also try to + // return early. + (|| { + let params = $crate::AsLogParams::as_log_params($as_log_params); + if !params.get_user_filter() { + // The user filter is telling us not to log this time, so exit + // before doing anything else. + return; } - // Drop out - $crate::LoggingOccurrence::Always => (), - } - // If we have a throttle period AND logging has not already been disabled due to SkipFirst settings - if log_conditions.publish_interval > std::time::Duration::ZERO && allow_logging { - let mut ignore_first_timeout = false; - // Need to initialise to a constant - static LAST_LOG_TIME: Mutex = Mutex::new(std::time::SystemTime::UNIX_EPOCH); - - static INIT_LAST_LOG_TIME: std::sync::Once = std::sync::Once::new(); - // Set the last log time to "now", but let us log the message the first time we hit this - // code, i.e. initial behaviour is expired. - // Note: If this is part of a SkipFirst macro call, we will only hit this code on the second iteration. - INIT_LAST_LOG_TIME.call_once(|| { - let mut last_log_time = LAST_LOG_TIME.lock().unwrap(); - *last_log_time = std::time::SystemTime::now(); - ignore_first_timeout = true; - }); - - let mut last_log_time = LAST_LOG_TIME.lock().unwrap(); - if std::time::SystemTime::now() >= *last_log_time + log_conditions.publish_interval { - // Update our time stamp - *last_log_time = std::time::SystemTime::now(); + let mut first_time = false; + static REMEMBER_FIRST_TIME: Once = Once::new(); + REMEMBER_FIRST_TIME.call_once(|| first_time = true); + + let logger_name = params.get_logger_name(); + let severity = params.get_severity(); + + match params.get_occurence() { + // Create the static variables here so we get a per-instance static + $crate::LogOccurrence::Once => { + if first_time { + $crate::log_unconditional!(severity, logger_name, $($args)*); + } + // Since we've already logged once, we should never log again, + // so just exit right now. + return; + } + $crate::LogOccurrence::SkipFirst => { + if first_time { + // This is the first time that we're seeing this log, and we + // were told to skip the first one, so just exit right away. + return; + } + } + // Do nothing + $crate::LogOccurrence::All => (), } - else if !ignore_first_timeout { - // Timer has not expired (and this is not the first time through here) - allow_logging = false; + + // If we have a throttle interval then check if we're inside or outside + // of that interval. + let interval = params.get_interval(); + if interval > std::time::Duration::ZERO { + static LAST_LOG_TIME: LazyLock> = LazyLock::new(|| { + Mutex::new(std::time::SystemTime::now()) + }); + + if !first_time { + let now = std::time::SystemTime::now(); + let mut previous = LAST_LOG_TIME.lock().unwrap(); + if now >= *previous + interval { + *previous = now; + } else { + // We are still inside the throttle interval, so just exit + // here. + return; + } + } } - } - // At this point we've validated the skip/throttle operations, and we now check that any - // expression supplied also evaluates to true, e.g. if timer has expired but expression is - // false, we won't print - if (allow_logging && log_conditions.log_if_true) - { - $crate::log(&std::fmt::format(format_args!($($args)*)), $logger_name, file!(), line!(), $severity); - } - }; + // All filters have been checked, so go ahead and publish the message + $crate::log_unconditional!(severity, logger_name, $($args)*); + })(); + }}; } -/// Debug log message. +/// Debug log message. See [`log`] for usage. #[macro_export] macro_rules! log_debug { - ($logger_name: expr, $($args:tt)*) => {{ - $crate::log_with_conditions!($crate::LogSeverity::Debug, $logger_name, $($args)*); + ($as_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::AsLogParams::as_log_params($as_log_params); + $crate::log!(log_params.debug(), $($args)*); }} } -/// Info log message. +/// Info log message. See [`log`] for usage. #[macro_export] macro_rules! log_info { - ($logger_name: expr, $($args:tt)*) => {{ - $crate::log_with_conditions!($crate::LogSeverity::Info, $logger_name, $($args)*); + ($as_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::AsLogParams::as_log_params($as_log_params); + $crate::log!(log_params.info(), $($args)*); }} } -/// Warning log message. +/// Warning log message. See [`log`] for usage. #[macro_export] macro_rules! log_warn { - ($logger_name: expr, $($args:tt)*) => {{ - $crate::log_with_conditions!($crate::LogSeverity::Warn, $logger_name, $($args)*); + ($as_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::AsLogParams::as_log_params($as_log_params); + $crate::log!(log_params.warn(), $($args)*); }} } -/// Error log message. +/// Error log message. See [`log`] for usage. #[macro_export] macro_rules! log_error { - ($logger_name: expr, $($args:tt)*) => {{ - $crate::log_with_conditions!($crate::LogSeverity::Error, $logger_name, $($args)*); + ($as_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::AsLogParams::as_log_params($as_log_params); + $crate::log!(log_params.error(), $($args)*); }} } -/// Fatal log message. +/// Fatal log message. See [`log`] for usage. #[macro_export] macro_rules! log_fatal { - ($logger_name: expr, $($args:tt)*) => {{ - $crate::log_with_conditions!($crate::LogSeverity::Fatal, $logger_name, $($args)*); + ($as_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::AsLogParams::as_log_params($as_log_params); + $crate::log!(log_params.fatal(), $($args)*); + }} +} + +/// A logging mechanism that does not have any conditions: It will definitely +/// publish the log. This is only meant for internal use, but needs to be exported +/// in order for [`log`] to work. +#[doc(hidden)] +#[macro_export] +macro_rules! log_unconditional { + ($severity: expr, $logger_name: expr, $($args:tt)*) => {{ + use std::{ffi::CString, sync::LazyLock}; + + // Only allocate a CString for the function name once per call to this macro. + let function: LazyLock = LazyLock::new(|| { + CString::new($crate::function!()).unwrap_or( + CString::new("").unwrap() + ) + }); + + // Only allocate a CString for the file name once per call to this macro. + let file: LazyLock = LazyLock::new(|| { + CString::new(file!()).unwrap_or( + CString::new("").unwrap() + ) + }); + + // We have to allocate a CString for the message every time because the + // formatted data may have changed. We could consider having an alternative + // macro for string literals that only allocates once, but it not obvious + // how to guarantee that the user only passes in an unchanging string literal. + match CString::new(std::fmt::format(format_args!($($args)*))) { + Ok(message) => { + // SAFETY: impl_log is actually completely safe to call, we just + // mark it as unsafe to discourage downstream users from calling it + unsafe { $crate::impl_log($severity, $logger_name, &message, &function, &file, line!()) }; + } + Err(err) => { + let message = CString::new(format!( + "Unable to format log message into a valid c-string. Error: {}", err + )).unwrap(); + + unsafe { + $crate::impl_log( + $crate::LogSeverity::Error, + &$crate::LoggerName::Unvalidated("logger"), + &message, + &function, + &file, + line!(), + ); + } + } + } }} } + +/// Calls the underlying rclutils logging function +/// Don't call this directly, use the logging macros instead, i.e. [`log`]. +/// +/// SAFETY: This function is not actually unsafe, but it is not meant to be part of the public +/// API, so we mark it as unsafe to discourage users from trying to use it. They should use +/// one of the of log! macros instead. We can't make it private because it needs to be used +/// by exported macros. +#[doc(hidden)] +pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: &CString, function: &CString, file: &CString, line: u32) { + // We use a closure here because there are several different points in this + // function where we may need to run this same logic. + let send_log = |severity: LogSeverity, logger_name: &CString, message: &CString, function: &CString, file: &CString, line: u32| { + let location = rcutils_log_location_t { + function_name: function.as_ptr(), + file_name: file.as_ptr(), + line_number: line as usize, + }; + let format: LazyLock = LazyLock::new(|| { + CString::new("%s").unwrap() + }); + + let severity = severity.to_native(); + + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + // SAFETY: Global variables are protected via ENTITY_LIFECYCLE_MUTEX, no other preconditions are required + unsafe { + rcutils_log( + &location, + severity as i32, + logger_name.as_ptr(), + format.as_ptr(), + message.as_ptr(), + ); + } + }; + + match logger_name { + LoggerName::Validated(c_name) => { + // The logger name is pre-validated, so just go ahead and use it. + send_log(severity, c_name, message, function, file, line); + } + LoggerName::Unvalidated(str_name) => { + // The name was not validated before being passed in. + // + // We maintain a hashmap of previously validated loggers so + // we don't need to reallocate the CString on every log instance. + // This is done inside of the function impl_log instead of in a macro + // so that this map is global for the entire application. + let name_map: LazyLock>> = LazyLock::default(); + + { + // We need to keep name_map locked while we call send_log, but + // we also need to make sure it gets unlocked right afterward + // if the condition fails, otherwise this function would + // deadlock on itself when handling the error case of the logger + // name being invalid. So we keep name_map_guard in this extra + // scope to isolate its lifespan. + let name_map_guard = name_map.lock().unwrap(); + if let Some(c_name) = name_map_guard.get(*str_name) { + // The map name has been used before, so we just use the + // pre-existing CString + send_log(severity, c_name, message, function, file, line); + + // We return right away because the remainder of this + // function just allocates and validates a new CString for + // the logger name. + return; + } + } + + // The unvalidated logger name has not been used before, so we need + // to convert it and add it to the name_map now. + let c_name = match CString::new(str_name.to_string()) { + Ok(c_name) => c_name, + Err(_) => { + let invalid_msg: LazyLock = LazyLock::new(|| { + CString::new( + "Failed to convert logger name into a c-string. \ + Check for null terminators inside the string." + ).unwrap() + }); + let internal_logger_name: LazyLock = LazyLock::new(|| { + CString::new("logger").unwrap() + }); + send_log(severity, &internal_logger_name, &invalid_msg, function, file, line); + return; + } + }; + + name_map.lock().unwrap().insert(str_name.to_string(), c_name); + } + } +} + +/// Used internally by logging macros to get the name of the function that called the +/// logging macro. This is not meant for public use, but we need to export it so the +/// other exported macros can use it. We should remove it if an official function! macro +/// is ever offered. +#[macro_export] +#[doc(hidden)] +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + name.strip_suffix("::f").unwrap() + }} +} + +#[cfg(test)] +mod tests { + use crate::{*, test_helpers::*}; + + #[test] + fn test_logging_macros() -> Result<(), RclrsError> { + let graph = construct_test_graph("test_logging_macros")?; + let node = graph.node1; + + log!(&*node, "Logging with node dereference"); + + for _ in 0..10 { + log!(node.once(), "Logging once"); + } + + log!(node.logger(), "Logging with node logger"); + log!(node.debug(), "Debug from node"); + log!(node.info(), "Info from node"); + log!(node.warn(), "Warn from node"); + log!(node.error(), "Error from node"); + log!(node.fatal(), "Fatal from node"); + + log_debug!(node.logger(), "log_debug macro"); + log_info!(node.logger(), "log_info macro"); + log_warn!(node.logger(), "log_warn macro"); + log_error!(node.logger(), "log_error macro"); + log_fatal!(node.logger(), "log_fatal macro"); + + log!( + node.only_if(false), + "This should not be logged", + ); + log!( + node.only_if(true), + "This should be logged", + ); + + for i in 0..3 { + log!( + node.warn().skip_first(), + "Formatted warning #{}", i + ); + } + + node.logger().set_level(LogSeverity::Debug).unwrap(); + log_debug!(node.logger(), "This debug message appears"); + node.logger().set_level(LogSeverity::Info).unwrap(); + log_debug!(node.logger(), "This debug message does not"); + + Ok(()) + } +} diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs new file mode 100644 index 000000000..08bf19b5f --- /dev/null +++ b/rclrs/src/logging/log_params.rs @@ -0,0 +1,240 @@ +use std::{time::Duration, borrow::Borrow, ffi::CString}; +use crate::rcl_bindings::RCUTILS_LOG_SEVERITY; + +/// These parameters determine the behavior of an instance of logging. +#[derive(Debug, Clone, Copy)] +pub struct LogParams<'a> { + /// The name of the logger + logger_name: LoggerName<'a>, + /// The severity of the logging instance. + severity: LogSeverity, + /// Specify when a log message should be published (See[`LoggingOccurrence`] above) + occurs: LogOccurrence, + /// Specify the publication interval of the message. A value of ZERO (0) indicates that the + /// message should be published every time, otherwise, the message will only be published once + /// the specified interval has elapsed. + /// This field is typically used to limit the output from high-frequency messages, e.g. instead + /// of publishing a log message every 10 milliseconds, the `publish_interval` can be configured + /// such that the message is published every 10 seconds. + interval: Duration, + /// The log message will only published if the specified expression evaluates to true + only_if: bool, +} + +impl<'a> LogParams<'a> { + /// Create a set of default log parameters, given the name of a logger + pub fn new(logger_name: LoggerName<'a>) -> Self { + Self { + logger_name, + severity: Default::default(), + occurs: Default::default(), + interval: Duration::new(0, 0), + only_if: true, + } + } + + /// Get the logger name + pub fn get_logger_name(&self) -> &LoggerName { + &self.logger_name + } + + /// Get the severity of the log + pub fn get_severity(&self) -> LogSeverity { + self.severity + } + + /// Get the occurrence + pub fn get_occurence(&self) -> LogOccurrence { + self.occurs + } + + /// Get the interval + pub fn get_interval(&self) -> Duration { + self.interval + } + + /// Get the arbitrary filter set by the user + pub fn get_user_filter(&self) -> bool { + self.only_if + } +} + +/// Methods for defining the behavior of a logging instance. +/// +/// This trait is implemented by Logger, Node, and anything that a `&str` can be +/// [borrowed][1] from, such as string literals (`"my_str"`), [`String`], or +/// [`Cow`][2]. +/// +/// [1]: Borrow +/// [2]: std::borrow::Cow +pub trait AsLogParams<'a>: Sized { + /// Convert the object into LogParams with default settings + fn as_log_params(self) -> LogParams<'a>; + + /// The logging should only happen once + fn once(self) -> LogParams<'a> { + self.occurs(LogOccurrence::Once) + } + + /// The first time the logging happens, we should skip it + fn skip_first(self) -> LogParams<'a> { + self.occurs(LogOccurrence::SkipFirst) + } + + /// Set the occurrence behavior of the log instance + fn occurs(self, occurs: LogOccurrence) -> LogParams<'a> { + let mut params = self.as_log_params(); + params.occurs = occurs; + params + } + + /// Set an interval during which this log will not publish. A value of zero + /// will never block the message from being published, and this is the + /// default behavior. + /// + /// A negative duration is not valid, but will be treated as a zero duration. + fn interval(self, interval: Duration) -> LogParams<'a> { + let mut params = self.as_log_params(); + params.interval = interval; + params + } + + /// The log will not be published if a `false` expression is passed into + /// this function. + /// + /// Other factors may prevent the log from being published if a `true` is + /// passed in, such as `AsLogParams::interval` or `AsLogParams::once` + /// filtering the log. + fn only_if(self, only_if: bool) -> LogParams<'a> { + let mut params = self.as_log_params(); + params.only_if = only_if; + params + } + + /// Log as a debug message. + fn debug(self) -> LogParams<'a> { + self.severity(LogSeverity::Debug) + } + + /// Log as an informative message. This is the default, so you don't + /// generally need to use this. + fn info(self) -> LogParams<'a> { + self.severity(LogSeverity::Info) + } + + /// Log as a warning message. + fn warn(self) -> LogParams<'a> { + self.severity(LogSeverity::Warn) + } + + /// Log as an error message. + fn error(self) -> LogParams<'a> { + self.severity(LogSeverity::Error) + } + + /// Log as a fatal message. + fn fatal(self) -> LogParams<'a> { + self.severity(LogSeverity::Fatal) + } + + /// Set the severity for this instance of logging. The default value will be + /// [`LogSeverity::Info`]. + fn severity(self, severity: LogSeverity) -> LogParams<'a> { + let mut params = self.as_log_params(); + params.severity = severity; + params + } +} + +/// This is used to borrow a logger name which might be validated (e.g. came +/// from a [`Logger`][1] struct) or not (e.g. a user-defined `&str`). If an +/// unvalidated logger name is used with one of the logging macros, we will log +/// an error about it, and the original log message will be logged with the +/// default logger. +/// +/// [1]: crate::Logger +#[derive(Debug, Clone, Copy)] +pub enum LoggerName<'a> { + /// The logger name is already available as a valid CString + Validated(&'a CString), + /// The logger name has not been validated yet + Unvalidated(&'a str), +} + +/// Logging severity. +#[doc(hidden)] +#[derive(Debug, Clone, Copy)] +pub enum LogSeverity { + /// Use the severity level of the parent logger (or the root logger if the + /// current logger has no parent) + Unset, + /// For messages that are not needed outside of debugging. + Debug, + /// For messages that provide useful information about the state of the + /// application. + Info, + /// For messages that indicate something unusual or unintended might have happened. + Warn, + /// For messages that indicate an error has occurred which may cause the application + /// to misbehave. + Error, + /// For messages that indicate an error has occurred which is so severe that the + /// application should terminate because it cannot recover. + /// + /// Using this severity level will not automatically cause the application to + /// terminate, the application developer must decide how to do that on a + /// case-by-case basis. + Fatal, +} + +impl LogSeverity { + pub(super) fn to_native(&self) -> RCUTILS_LOG_SEVERITY { + use crate::rcl_bindings::rcl_log_severity_t::*; + match self { + LogSeverity::Unset => RCUTILS_LOG_SEVERITY_UNSET, + LogSeverity::Debug => RCUTILS_LOG_SEVERITY_DEBUG, + LogSeverity::Info => RCUTILS_LOG_SEVERITY_INFO, + LogSeverity::Warn => RCUTILS_LOG_SEVERITY_WARN, + LogSeverity::Error => RCUTILS_LOG_SEVERITY_ERROR, + LogSeverity::Fatal => RCUTILS_LOG_SEVERITY_FATAL, + } + } +} + +impl Default for LogSeverity { + fn default() -> Self { + Self::Info + } +} + +/// Specify when a log message should be published +#[derive(Debug, Clone, Copy)] +pub enum LogOccurrence { + /// Every message will be published if all other conditions are met + All, + /// The message will only be published on the first occurrence (Note: no other conditions apply) + Once, + /// The log message will not be published on the first occurrence, but will be published on + /// each subsequent occurrence (assuming all other conditions are met) + SkipFirst, +} + +impl Default for LogOccurrence { + fn default() -> Self { + Self::All + } +} + +// Anything that we can borrow a string from can be used as if it's a logger and +// turned into LogParams +impl<'a, T: Borrow> AsLogParams<'a> for &'a T { + fn as_log_params(self) -> LogParams<'a> { + LogParams::new(LoggerName::Unvalidated(self.borrow())) + } +} + +impl<'a> AsLogParams<'a> for LogParams<'a> { + fn as_log_params(self) -> LogParams<'a> { + self + } +} diff --git a/rclrs/src/logging/logger.rs b/rclrs/src/logging/logger.rs new file mode 100644 index 000000000..25f231948 --- /dev/null +++ b/rclrs/src/logging/logger.rs @@ -0,0 +1,108 @@ +use std::{ + ffi::CString, + borrow::Borrow, +}; + +use crate::{ + RclrsError, AsLogParams, LogParams, LoggerName, LogSeverity, ToResult, + ENTITY_LIFECYCLE_MUTEX, + rcl_bindings::{ + rcutils_logging_set_logger_level, + rcutils_logging_set_default_logger_level, + } +}; + +/// Logger can be passed in as the first argument into one of the [logging][1] +/// macros provided by rclrs. When passing it into one of the logging macros, +/// you can optionally apply any of the methods from [`AsLogParams`] to tweak +/// the logging behavior. +/// +/// You can obtain a Logger in the following ways: +/// - Calling [`Node::logger`][2] to get the logger of a Node +/// - Using [`Logger::create_child`] to create a child logger for an existing logger +/// - Using [`Logger::new`] to create a new logger with any name that you'd like +/// - Using [`Logger::default()`] to access the global default logger +/// +/// Note that if you are passing the Logger of a Node into one of the logging macros, +/// then you can choose to simply pass in `&node` instead of `node.logger()`. +/// +/// [1]: crate::log +/// [2]: crate::Node::logger +pub struct Logger { + name: Box, + c_name: CString, +} + +impl Logger { + /// Create a new logger with the given name. + pub fn new(name: impl Borrow) -> Result { + let name: Box = name.borrow().into(); + let c_name = match CString::new(name.clone().into_string()) { + Ok(c_name) => c_name, + Err(err) => { + return Err(RclrsError::StringContainsNul { s: name.into_string(), err }); + } + }; + + Ok(Self { name, c_name }) + } + + /// Create a new logger which will be a child of this logger. + /// + /// If the name of this logger is `parent_name`, then the name for the new + /// child will be '"parent_name.child_name"`. + /// + /// If this is the default logger (whose name is an empty string), then the + /// name for the new child will simply be the value in `child_name`. + pub fn create_child(&self, child_name: impl Borrow) -> Result { + if self.name.is_empty() { + Self::new(child_name) + } else { + Self::new(format!("{}.{}", &self.name, child_name.borrow())) + } + } + + /// Get the name of this logger + pub fn name(&self) -> &str { + &self.name + } + + /// Set the severity level of this logger + pub fn set_level(&self, severity: LogSeverity) -> Result<(), RclrsError> { + // SAFETY: The precondition are: + // - we are passing in a valid CString, which is already taken care of during construction of the Logger + // - the severity level is a valid value, which is guaranteed by the rigid enum definition + // - not thread-safe, so we lock the global mutex before calling this + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + unsafe { + rcutils_logging_set_logger_level( + self.c_name.as_ptr(), + severity.to_native() as i32, + ).ok() + } + } + + /// Set the severity level of the default logger which acts as the root ancestor + /// of all other loggers. + pub fn set_default_level(severity: LogSeverity) { + // SAFETY: The preconditions are: + // - the severity level is a valid value, which is guaranteed by the rigid enum definition + // - not thread-safe, so we lock the global mutex before calling this + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + unsafe { + rcutils_logging_set_default_logger_level(severity.to_native() as i32); + } + } +} + +impl<'a> AsLogParams<'a> for &'a Logger { + fn as_log_params(self) -> LogParams<'a> { + LogParams::new(LoggerName::Validated(&self.c_name)) + } +} + +impl Default for Logger { + fn default() -> Self { + Self::new("").unwrap() + } +} diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 0d141721c..9cac45b27 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -16,7 +16,7 @@ use crate::{ rcl_bindings::*, Client, ClientBase, Clock, Context, ContextHandle, GuardCondition, ParameterBuilder, ParameterInterface, ParameterVariant, Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase, Subscription, SubscriptionBase, SubscriptionCallback, - TimeSource, ENTITY_LIFECYCLE_MUTEX, + TimeSource, ENTITY_LIFECYCLE_MUTEX, Logger, AsLogParams, LogParams, }; // SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread @@ -66,6 +66,7 @@ pub struct Node { time_source: TimeSource, parameter: ParameterInterface, pub(crate) handle: Arc, + logger: Logger, } /// This struct manages the lifetime of an `rcl_node_t`, and accounts for its @@ -447,19 +448,15 @@ impl Node { NodeBuilder::new(context, node_name) } - /// Returns the logger name of the node. - pub fn logger_name(&self) -> &str { - let rcl_node = self.handle.rcl_node.lock().unwrap(); - // SAFETY: No pre-conditions for this function - let name_raw_ptr = unsafe { rcl_node_get_logger_name(&**rcl_node) }; - if name_raw_ptr.is_null() { - return ""; - } - // SAFETY: The returned CStr is immediately converted to a string slice, - // so the lifetime is no issue. The ptr is valid as per the documentation - // of rcl_node_get_logger_name. - let name_cstr = unsafe { CStr::from_ptr(name_raw_ptr) }; - name_cstr.to_str().unwrap_or("") + /// Get the logger associated with this Node. + pub fn logger(&self) -> &Logger { + &self.logger + } +} + +impl<'a> AsLogParams<'a> for &'a Node { + fn as_log_params(self) -> LogParams<'a> { + self.logger().as_log_params() } } @@ -542,11 +539,11 @@ mod tests { let graph = construct_test_graph("test_topics_graph")?; assert_eq!( - graph.node1.logger_name(), + graph.node1.logger().name(), "test_topics_graph.graph_test_node_1" ); assert_eq!( - graph.node2.logger_name(), + graph.node2.logger().name(), "test_topics_graph.graph_test_node_2" ); diff --git a/rclrs/src/node/builder.rs b/rclrs/src/node/builder.rs index 14d4dae27..01e96d2f0 100644 --- a/rclrs/src/node/builder.rs +++ b/rclrs/src/node/builder.rs @@ -1,11 +1,12 @@ use std::{ - ffi::CString, + ffi::{CString, CStr}, sync::{Arc, Mutex}, }; use crate::{ rcl_bindings::*, ClockType, Context, ContextHandle, Node, NodeHandle, ParameterInterface, QoSProfile, RclrsError, TimeSource, ToResult, ENTITY_LIFECYCLE_MUTEX, QOS_PROFILE_CLOCK, + Logger, }; /// A builder for creating a [`Node`][1]. @@ -308,6 +309,19 @@ impl NodeBuilder { &rcl_context.global_arguments, )? }; + + let logger_name = { + let rcl_node = handle.rcl_node.lock().unwrap(); + let logger_name_raw_ptr = unsafe { rcl_node_get_logger_name(&**rcl_node) }; + if logger_name_raw_ptr.is_null() { + "" + } else { + unsafe { CStr::from_ptr(logger_name_raw_ptr) } + .to_str() + .unwrap_or("") + } + }; + let node = Arc::new(Node { handle, clients_mtx: Mutex::new(vec![]), @@ -318,7 +332,9 @@ impl NodeBuilder { .clock_qos(self.clock_qos) .build(), parameter, + logger: Logger::new(logger_name)?, }); + node.time_source.attach_node(&node); if self.start_parameter_services { node.parameter.create_services(&node)?; From d89121be7615a44a9024dc0991e37acb7b8e2e51 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 4 Nov 2024 02:45:10 +0800 Subject: [PATCH 03/33] Add test for arbitrary string logger name Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 5a08e189d..9e39a65f0 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -256,7 +256,7 @@ macro_rules! log_unconditional { pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: &CString, function: &CString, file: &CString, line: u32) { // We use a closure here because there are several different points in this // function where we may need to run this same logic. - let send_log = |severity: LogSeverity, logger_name: &CString, message: &CString, function: &CString, file: &CString, line: u32| { + let send_log = |severity: LogSeverity, logger_name: &CString, message: &CString| { let location = rcutils_log_location_t { function_name: function.as_ptr(), file_name: file.as_ptr(), @@ -284,7 +284,7 @@ pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: match logger_name { LoggerName::Validated(c_name) => { // The logger name is pre-validated, so just go ahead and use it. - send_log(severity, c_name, message, function, file, line); + send_log(severity, c_name, message); } LoggerName::Unvalidated(str_name) => { // The name was not validated before being passed in. @@ -306,7 +306,7 @@ pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: if let Some(c_name) = name_map_guard.get(*str_name) { // The map name has been used before, so we just use the // pre-existing CString - send_log(severity, c_name, message, function, file, line); + send_log(severity, c_name, message); // We return right away because the remainder of this // function just allocates and validates a new CString for @@ -329,11 +329,12 @@ pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: let internal_logger_name: LazyLock = LazyLock::new(|| { CString::new("logger").unwrap() }); - send_log(severity, &internal_logger_name, &invalid_msg, function, file, line); + send_log(severity, &internal_logger_name, &invalid_msg); return; } }; + send_log(severity, &c_name, message); name_map.lock().unwrap().insert(str_name.to_string(), c_name); } } @@ -405,6 +406,8 @@ mod tests { node.logger().set_level(LogSeverity::Info).unwrap(); log_debug!(node.logger(), "This debug message does not"); + log!(&"custom logger name", "message for custom logger"); + Ok(()) } } From 69b43f04ed51e8ca662e18476be87a4380852724 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 4 Nov 2024 23:32:33 +0800 Subject: [PATCH 04/33] Fix merge Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 3 +++ rclrs/src/node/builder.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 9e39a65f0..1e25a35cc 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -10,6 +10,9 @@ use crate::{ ENTITY_LIFECYCLE_MUTEX, }; +mod logging_configuration; +pub(crate) use logging_configuration::*; + mod log_params; pub use log_params::*; diff --git a/rclrs/src/node/builder.rs b/rclrs/src/node/builder.rs index 933c9f926..0a67211e9 100644 --- a/rclrs/src/node/builder.rs +++ b/rclrs/src/node/builder.rs @@ -317,7 +317,7 @@ impl NodeBuilder { let logger_name = { let rcl_node = handle.rcl_node.lock().unwrap(); - let logger_name_raw_ptr = unsafe { rcl_node_get_logger_name(&**rcl_node) }; + let logger_name_raw_ptr = unsafe { rcl_node_get_logger_name(&*rcl_node) }; if logger_name_raw_ptr.is_null() { "" } else { From 27f5bc85eb37380772b56c5a487dddf0d73517a1 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 4 Nov 2024 23:40:25 +0800 Subject: [PATCH 05/33] Add safety comment Signed-off-by: Michael X. Grey --- rclrs/src/node/builder.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rclrs/src/node/builder.rs b/rclrs/src/node/builder.rs index 0a67211e9..f19ffb544 100644 --- a/rclrs/src/node/builder.rs +++ b/rclrs/src/node/builder.rs @@ -321,6 +321,13 @@ impl NodeBuilder { if logger_name_raw_ptr.is_null() { "" } else { + // SAFETY: rcl_node_get_logger_name will either return a nullptr + // if the provided node was invalid or provide a valid null-terminated + // const char* if the provided node was valid. We have already + // verified that it is not a nullptr. We are also preventing the + // pointed-to value from being modified while we view it by locking + // the mutex of rcl_node while we view it. This means all the + // safety conditions of CStr::from_ptr are met. unsafe { CStr::from_ptr(logger_name_raw_ptr) } .to_str() .unwrap_or("") From 57f879e243f1599054421fbfcc0e64cd5f761d43 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 4 Nov 2024 23:42:56 +0800 Subject: [PATCH 06/33] Fix formatting Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 58 ++++++++++++++++----------------- rclrs/src/logging/log_params.rs | 2 +- rclrs/src/logging/logger.rs | 23 +++++-------- rclrs/src/node.rs | 8 ++--- rclrs/src/node/builder.rs | 12 +++---- 5 files changed, 48 insertions(+), 55 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 1e25a35cc..a89e7439e 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -3,13 +3,14 @@ // Adapted from https://github.com/sequenceplanner/r2r/blob/89cec03d07a1496a225751159cbc7bfb529d9dd1/r2r/src/utils.rs // Further adapted from https://github.com/mvukov/rules_ros2/pull/371 -use std::{ffi::CString, sync::{LazyLock, Mutex}, collections::HashMap}; - -use crate::{ - rcl_bindings::*, - ENTITY_LIFECYCLE_MUTEX, +use std::{ + collections::HashMap, + ffi::CString, + sync::{LazyLock, Mutex}, }; +use crate::{rcl_bindings::*, ENTITY_LIFECYCLE_MUTEX}; + mod logging_configuration; pub(crate) use logging_configuration::*; @@ -256,7 +257,14 @@ macro_rules! log_unconditional { /// one of the of log! macros instead. We can't make it private because it needs to be used /// by exported macros. #[doc(hidden)] -pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: &CString, function: &CString, file: &CString, line: u32) { +pub unsafe fn impl_log( + severity: LogSeverity, + logger_name: &LoggerName, + message: &CString, + function: &CString, + file: &CString, + line: u32, +) { // We use a closure here because there are several different points in this // function where we may need to run this same logic. let send_log = |severity: LogSeverity, logger_name: &CString, message: &CString| { @@ -265,9 +273,7 @@ pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: file_name: file.as_ptr(), line_number: line as usize, }; - let format: LazyLock = LazyLock::new(|| { - CString::new("%s").unwrap() - }); + let format: LazyLock = LazyLock::new(|| CString::new("%s").unwrap()); let severity = severity.to_native(); @@ -326,19 +332,22 @@ pub unsafe fn impl_log(severity: LogSeverity, logger_name: &LoggerName, message: let invalid_msg: LazyLock = LazyLock::new(|| { CString::new( "Failed to convert logger name into a c-string. \ - Check for null terminators inside the string." - ).unwrap() - }); - let internal_logger_name: LazyLock = LazyLock::new(|| { - CString::new("logger").unwrap() + Check for null terminators inside the string.", + ) + .unwrap() }); + let internal_logger_name: LazyLock = + LazyLock::new(|| CString::new("logger").unwrap()); send_log(severity, &internal_logger_name, &invalid_msg); return; } }; send_log(severity, &c_name, message); - name_map.lock().unwrap().insert(str_name.to_string(), c_name); + name_map + .lock() + .unwrap() + .insert(str_name.to_string(), c_name); } } } @@ -357,12 +366,12 @@ macro_rules! function { } let name = type_name_of(f); name.strip_suffix("::f").unwrap() - }} + }}; } #[cfg(test)] mod tests { - use crate::{*, test_helpers::*}; + use crate::{test_helpers::*, *}; #[test] fn test_logging_macros() -> Result<(), RclrsError> { @@ -388,20 +397,11 @@ mod tests { log_error!(node.logger(), "log_error macro"); log_fatal!(node.logger(), "log_fatal macro"); - log!( - node.only_if(false), - "This should not be logged", - ); - log!( - node.only_if(true), - "This should be logged", - ); + log!(node.only_if(false), "This should not be logged",); + log!(node.only_if(true), "This should be logged",); for i in 0..3 { - log!( - node.warn().skip_first(), - "Formatted warning #{}", i - ); + log!(node.warn().skip_first(), "Formatted warning #{}", i); } node.logger().set_level(LogSeverity::Debug).unwrap(); diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index 08bf19b5f..8f1d4215e 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -1,5 +1,5 @@ -use std::{time::Duration, borrow::Borrow, ffi::CString}; use crate::rcl_bindings::RCUTILS_LOG_SEVERITY; +use std::{borrow::Borrow, ffi::CString, time::Duration}; /// These parameters determine the behavior of an instance of logging. #[derive(Debug, Clone, Copy)] diff --git a/rclrs/src/logging/logger.rs b/rclrs/src/logging/logger.rs index 25f231948..10bcce3fd 100644 --- a/rclrs/src/logging/logger.rs +++ b/rclrs/src/logging/logger.rs @@ -1,15 +1,8 @@ -use std::{ - ffi::CString, - borrow::Borrow, -}; +use std::{borrow::Borrow, ffi::CString}; use crate::{ - RclrsError, AsLogParams, LogParams, LoggerName, LogSeverity, ToResult, - ENTITY_LIFECYCLE_MUTEX, - rcl_bindings::{ - rcutils_logging_set_logger_level, - rcutils_logging_set_default_logger_level, - } + rcl_bindings::{rcutils_logging_set_default_logger_level, rcutils_logging_set_logger_level}, + AsLogParams, LogParams, LogSeverity, LoggerName, RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, }; /// Logger can be passed in as the first argument into one of the [logging][1] @@ -40,7 +33,10 @@ impl Logger { let c_name = match CString::new(name.clone().into_string()) { Ok(c_name) => c_name, Err(err) => { - return Err(RclrsError::StringContainsNul { s: name.into_string(), err }); + return Err(RclrsError::StringContainsNul { + s: name.into_string(), + err, + }); } }; @@ -75,10 +71,7 @@ impl Logger { // - not thread-safe, so we lock the global mutex before calling this let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { - rcutils_logging_set_logger_level( - self.c_name.as_ptr(), - severity.to_native() as i32, - ).ok() + rcutils_logging_set_logger_level(self.c_name.as_ptr(), severity.to_native() as i32).ok() } } diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 74a8a267d..89e6d4bd0 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -13,10 +13,10 @@ use rosidl_runtime_rs::Message; pub use self::{builder::*, graph::*}; use crate::{ - rcl_bindings::*, Client, ClientBase, Clock, Context, ContextHandle, GuardCondition, - ParameterBuilder, ParameterInterface, ParameterVariant, Parameters, Publisher, QoSProfile, - RclrsError, Service, ServiceBase, Subscription, SubscriptionBase, SubscriptionCallback, - TimeSource, ENTITY_LIFECYCLE_MUTEX, Logger, AsLogParams, LogParams, + rcl_bindings::*, AsLogParams, Client, ClientBase, Clock, Context, ContextHandle, + GuardCondition, LogParams, Logger, ParameterBuilder, ParameterInterface, ParameterVariant, + Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase, Subscription, + SubscriptionBase, SubscriptionCallback, TimeSource, ENTITY_LIFECYCLE_MUTEX, }; // SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread diff --git a/rclrs/src/node/builder.rs b/rclrs/src/node/builder.rs index f19ffb544..1e7a9fc63 100644 --- a/rclrs/src/node/builder.rs +++ b/rclrs/src/node/builder.rs @@ -1,12 +1,12 @@ use std::{ - ffi::{CString, CStr}, + ffi::{CStr, CString}, sync::{atomic::AtomicBool, Arc, Mutex}, }; use crate::{ - rcl_bindings::*, ClockType, Context, ContextHandle, Node, NodeHandle, ParameterInterface, - QoSProfile, RclrsError, TimeSource, ToResult, ENTITY_LIFECYCLE_MUTEX, QOS_PROFILE_CLOCK, - Logger, + rcl_bindings::*, ClockType, Context, ContextHandle, Logger, Node, NodeHandle, + ParameterInterface, QoSProfile, RclrsError, TimeSource, ToResult, ENTITY_LIFECYCLE_MUTEX, + QOS_PROFILE_CLOCK, }; /// A builder for creating a [`Node`][1]. @@ -329,8 +329,8 @@ impl NodeBuilder { // the mutex of rcl_node while we view it. This means all the // safety conditions of CStr::from_ptr are met. unsafe { CStr::from_ptr(logger_name_raw_ptr) } - .to_str() - .unwrap_or("") + .to_str() + .unwrap_or("") } }; From ef0d0a0429f6a47f938649d3d4507a58aa556198 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 00:12:40 +0800 Subject: [PATCH 07/33] Allow &str to implement AsLogParams instead of &&str Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 12 +++++++++++- rclrs/src/logging/log_params.rs | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index a89e7439e..4bd685f55 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -409,7 +409,17 @@ mod tests { node.logger().set_level(LogSeverity::Info).unwrap(); log_debug!(node.logger(), "This debug message does not"); - log!(&"custom logger name", "message for custom logger"); + log!("custom logger name", "message for custom logger"); + for _ in 0..3 { + log!("custom logger name".once(), "one-time message for custom logger"); + } + + for _ in 0..3 { + log!( + "custom logger name".error().skip_first(), + "error for custom logger", + ); + } Ok(()) } diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index 8f1d4215e..d3e0af2a8 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -233,6 +233,12 @@ impl<'a, T: Borrow> AsLogParams<'a> for &'a T { } } +impl<'a> AsLogParams<'a> for &'a str { + fn as_log_params(self) -> LogParams<'a> { + LogParams::new(LoggerName::Unvalidated(self)) + } +} + impl<'a> AsLogParams<'a> for LogParams<'a> { fn as_log_params(self) -> LogParams<'a> { self From 8451518d083330d1c965a3112ed18cc1e407edb2 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 00:21:39 +0800 Subject: [PATCH 08/33] Add safety comment Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 4bd685f55..a01586bad 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -234,6 +234,8 @@ macro_rules! log_unconditional { "Unable to format log message into a valid c-string. Error: {}", err )).unwrap(); + // SAFETY: impl_log is actually completely safe to call, we just + // mark it as unsafe to discourage downstream users from calling it unsafe { $crate::impl_log( $crate::LogSeverity::Error, From 2d169855eeedb8c39fd8041d1c3c75bd5829eba8 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 00:26:46 +0800 Subject: [PATCH 09/33] Add test for function!() macro Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index a01586bad..b724f5548 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -425,4 +425,9 @@ mod tests { Ok(()) } + + #[test] + fn test_function_macro() { + assert_eq!(function!(), "rclrs::logging::tests::test_function_macro"); + } } From d27b4079e88e4044da57b2427d3b1aede881c50d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 13:30:47 +0800 Subject: [PATCH 10/33] Replace all uses of LazyLock with OnceLock to be compatible with 1.75 Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 40 +++++++++++++--------- rclrs/src/logging/logging_configuration.rs | 12 +++---- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index b724f5548..f9d231a06 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -6,7 +6,7 @@ use std::{ collections::HashMap, ffi::CString, - sync::{LazyLock, Mutex}, + sync::{OnceLock, Mutex}, }; use crate::{rcl_bindings::*, ENTITY_LIFECYCLE_MUTEX}; @@ -80,7 +80,7 @@ macro_rules! log { // https://github.com/intellij-rust/intellij-rust/issues/9853 // Note: that issue appears to be specific to jetbrains intellisense however, // observed same/similar behaviour with rust-analyzer/rustc - use std::sync::{Once, LazyLock, Mutex}; + use std::sync::{Once, OnceLock, Mutex}; use std::time::SystemTime; // We wrap the functional body of the macro inside of a closure which @@ -128,13 +128,14 @@ macro_rules! log { // of that interval. let interval = params.get_interval(); if interval > std::time::Duration::ZERO { - static LAST_LOG_TIME: LazyLock> = LazyLock::new(|| { + static LAST_LOG_TIME: OnceLock> = OnceLock::new(); + let last_log_time = LAST_LOG_TIME.get_or_init(|| { Mutex::new(std::time::SystemTime::now()) }); if !first_time { let now = std::time::SystemTime::now(); - let mut previous = LAST_LOG_TIME.lock().unwrap(); + let mut previous = last_log_time.lock().unwrap(); if now >= *previous + interval { *previous = now; } else { @@ -203,17 +204,19 @@ macro_rules! log_fatal { #[macro_export] macro_rules! log_unconditional { ($severity: expr, $logger_name: expr, $($args:tt)*) => {{ - use std::{ffi::CString, sync::LazyLock}; + use std::{ffi::CString, sync::OnceLock}; // Only allocate a CString for the function name once per call to this macro. - let function: LazyLock = LazyLock::new(|| { + static FUNCTION_NAME: OnceLock = OnceLock::new(); + let function_name = FUNCTION_NAME.get_or_init(|| { CString::new($crate::function!()).unwrap_or( CString::new("").unwrap() ) }); // Only allocate a CString for the file name once per call to this macro. - let file: LazyLock = LazyLock::new(|| { + static FILE_NAME: OnceLock = OnceLock::new(); + let file_name = FILE_NAME.get_or_init(|| { CString::new(file!()).unwrap_or( CString::new("").unwrap() ) @@ -227,7 +230,7 @@ macro_rules! log_unconditional { Ok(message) => { // SAFETY: impl_log is actually completely safe to call, we just // mark it as unsafe to discourage downstream users from calling it - unsafe { $crate::impl_log($severity, $logger_name, &message, &function, &file, line!()) }; + unsafe { $crate::impl_log($severity, $logger_name, &message, &function_name, &file_name, line!()) }; } Err(err) => { let message = CString::new(format!( @@ -241,8 +244,8 @@ macro_rules! log_unconditional { $crate::LogSeverity::Error, &$crate::LoggerName::Unvalidated("logger"), &message, - &function, - &file, + &function_name, + &file_name, line!(), ); } @@ -275,7 +278,8 @@ pub unsafe fn impl_log( file_name: file.as_ptr(), line_number: line as usize, }; - let format: LazyLock = LazyLock::new(|| CString::new("%s").unwrap()); + static FORMAT_CSTR: OnceLock = OnceLock::new(); + let format_cstr = FORMAT_CSTR.get_or_init(|| CString::new("%s").unwrap()); let severity = severity.to_native(); @@ -286,7 +290,7 @@ pub unsafe fn impl_log( &location, severity as i32, logger_name.as_ptr(), - format.as_ptr(), + format_cstr.as_ptr(), message.as_ptr(), ); } @@ -304,7 +308,8 @@ pub unsafe fn impl_log( // we don't need to reallocate the CString on every log instance. // This is done inside of the function impl_log instead of in a macro // so that this map is global for the entire application. - let name_map: LazyLock>> = LazyLock::default(); + static NAME_MAP: OnceLock>> = OnceLock::new(); + let name_map = NAME_MAP.get_or_init(|| Default::default()); { // We need to keep name_map locked while we call send_log, but @@ -331,15 +336,18 @@ pub unsafe fn impl_log( let c_name = match CString::new(str_name.to_string()) { Ok(c_name) => c_name, Err(_) => { - let invalid_msg: LazyLock = LazyLock::new(|| { + static INVALID_MSG: OnceLock = OnceLock::new(); + let invalid_msg = INVALID_MSG.get_or_init(|| { CString::new( "Failed to convert logger name into a c-string. \ Check for null terminators inside the string.", ) .unwrap() }); - let internal_logger_name: LazyLock = - LazyLock::new(|| CString::new("logger").unwrap()); + static INTERNAL_LOGGER_NAME: OnceLock = OnceLock::new(); + let internal_logger_name = INTERNAL_LOGGER_NAME.get_or_init( + || CString::new("logger").unwrap() + ); send_log(severity, &internal_logger_name, &invalid_msg); return; } diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 21f13987a..84519ac56 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, LazyLock, Mutex, Weak}; +use std::sync::{Arc, OnceLock, Mutex, Weak}; use crate::{ rcl_bindings::{ @@ -32,12 +32,12 @@ impl LoggingLifecycle { pub(crate) unsafe fn configure( context: &rcl_context_t, ) -> Result, RclrsError> { - static CONFIGURATION: LazyLock = - LazyLock::new(|| LoggingConfiguration { - lifecycle: Mutex::new(Weak::new()), - }); + static CONFIGURATION: OnceLock = OnceLock::new(); + let configuration = CONFIGURATION.get_or_init(|| LoggingConfiguration { + lifecycle: Mutex::new(Weak::new()), + }); - let mut lifecycle = CONFIGURATION.lifecycle.lock().unwrap(); + let mut lifecycle = configuration.lifecycle.lock().unwrap(); if let Some(arc_lifecycle) = lifecycle.upgrade() { return Ok(arc_lifecycle); } From 188a33103210a4e76cc76bd3cb992f90353fffa6 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 18:13:41 +0800 Subject: [PATCH 11/33] Fix formatting Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 12 +++++++----- rclrs/src/logging/logging_configuration.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index f9d231a06..43096841b 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -6,7 +6,7 @@ use std::{ collections::HashMap, ffi::CString, - sync::{OnceLock, Mutex}, + sync::{Mutex, OnceLock}, }; use crate::{rcl_bindings::*, ENTITY_LIFECYCLE_MUTEX}; @@ -345,9 +345,8 @@ pub unsafe fn impl_log( .unwrap() }); static INTERNAL_LOGGER_NAME: OnceLock = OnceLock::new(); - let internal_logger_name = INTERNAL_LOGGER_NAME.get_or_init( - || CString::new("logger").unwrap() - ); + let internal_logger_name = + INTERNAL_LOGGER_NAME.get_or_init(|| CString::new("logger").unwrap()); send_log(severity, &internal_logger_name, &invalid_msg); return; } @@ -421,7 +420,10 @@ mod tests { log!("custom logger name", "message for custom logger"); for _ in 0..3 { - log!("custom logger name".once(), "one-time message for custom logger"); + log!( + "custom logger name".once(), + "one-time message for custom logger" + ); } for _ in 0..3 { diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 84519ac56..2f7014273 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, OnceLock, Mutex, Weak}; +use std::sync::{Arc, Mutex, OnceLock, Weak}; use crate::{ rcl_bindings::{ From 75766f62c27d218c6b8388e3ae158b0a6004336b Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 19:44:03 +0800 Subject: [PATCH 12/33] Investigating how to set a custom logging handler Signed-off-by: Michael X. Grey --- rclrs/src/logging/logging_configuration.rs | 55 +++++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 84519ac56..fc49a8bb8 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -1,18 +1,21 @@ -use std::sync::{Arc, OnceLock, Mutex, Weak}; +use std::{ + sync::{Arc, OnceLock, Mutex, Weak}, + time::Instant, +}; use crate::{ - rcl_bindings::{ - rcl_arguments_t, rcl_context_t, rcl_logging_configure, rcl_logging_fini, - rcutils_get_default_allocator, - }, - RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, + rcl_bindings::*, + RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, LogSeverity, }; struct LoggingConfiguration { lifecycle: Mutex>, } -pub(crate) struct LoggingLifecycle; +pub(crate) struct LoggingLifecycle { + // Keep the pointer + handler: Option, +} impl LoggingLifecycle { fn new(args: &rcl_arguments_t) -> Result { @@ -55,3 +58,41 @@ impl Drop for LoggingLifecycle { } } } + +pub struct LogLocation<'a> { + pub function_name: &'a str, + pub file_name: &'a str, + pub line_number: usize, +} + +pub type RawLogHandler = Box; + +static LOGGING_OUTPUT_HANDLER: OnceLock = OnceLock::new(); + +pub fn set_raw_logging_output_handler( + +) -> Result<(), RawLogHandler> { + Ok(()) +} + +pub type LogHandler = Box; + +pub fn set_logging_output_handler( + +) -> Result<(), { + +} From ec91f3dec1cb676d06ff87fb9647f33b4f17040e Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 19:55:31 +0800 Subject: [PATCH 13/33] Comply with Rust naming conventions Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 28 ++++++++++++++-------------- rclrs/src/logging/log_params.rs | 28 ++++++++++++++-------------- rclrs/src/logging/logger.rs | 12 ++++++------ rclrs/src/node.rs | 8 ++++---- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 43096841b..4cda8c6e3 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -25,7 +25,7 @@ pub use logger::*; /// # Examples /// /// ``` -/// use rclrs::{log, AsLogParams}; +/// use rclrs::{log, ToLogParams}; /// use std::sync::Mutex; /// use std::time::Duration; /// use std::env; @@ -75,7 +75,7 @@ macro_rules! log { // ``` // log_error!(, "Log with no params"); // OR // log_error!(, "Log with useful info {}", error_reason); - ($as_log_params: expr, $($args:tt)*) => {{ + ($to_log_params: expr, $($args:tt)*) => {{ // Adding these use statements here due an issue like this one: // https://github.com/intellij-rust/intellij-rust/issues/9853 // Note: that issue appears to be specific to jetbrains intellisense however, @@ -88,7 +88,7 @@ macro_rules! log { // macro early without causing the calling function to also try to // return early. (|| { - let params = $crate::AsLogParams::as_log_params($as_log_params); + let params = $crate::ToLogParams::to_log_params($to_log_params); if !params.get_user_filter() { // The user filter is telling us not to log this time, so exit @@ -155,8 +155,8 @@ macro_rules! log { /// Debug log message. See [`log`] for usage. #[macro_export] macro_rules! log_debug { - ($as_log_params: expr, $($args:tt)*) => {{ - let log_params = $crate::AsLogParams::as_log_params($as_log_params); + ($to_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::ToLogParams::to_log_params($to_log_params); $crate::log!(log_params.debug(), $($args)*); }} } @@ -164,8 +164,8 @@ macro_rules! log_debug { /// Info log message. See [`log`] for usage. #[macro_export] macro_rules! log_info { - ($as_log_params: expr, $($args:tt)*) => {{ - let log_params = $crate::AsLogParams::as_log_params($as_log_params); + ($to_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::ToLogParams::to_log_params($to_log_params); $crate::log!(log_params.info(), $($args)*); }} } @@ -173,8 +173,8 @@ macro_rules! log_info { /// Warning log message. See [`log`] for usage. #[macro_export] macro_rules! log_warn { - ($as_log_params: expr, $($args:tt)*) => {{ - let log_params = $crate::AsLogParams::as_log_params($as_log_params); + ($to_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::ToLogParams::to_log_params($to_log_params); $crate::log!(log_params.warn(), $($args)*); }} } @@ -182,8 +182,8 @@ macro_rules! log_warn { /// Error log message. See [`log`] for usage. #[macro_export] macro_rules! log_error { - ($as_log_params: expr, $($args:tt)*) => {{ - let log_params = $crate::AsLogParams::as_log_params($as_log_params); + ($to_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::ToLogParams::to_log_params($to_log_params); $crate::log!(log_params.error(), $($args)*); }} } @@ -191,8 +191,8 @@ macro_rules! log_error { /// Fatal log message. See [`log`] for usage. #[macro_export] macro_rules! log_fatal { - ($as_log_params: expr, $($args:tt)*) => {{ - let log_params = $crate::AsLogParams::as_log_params($as_log_params); + ($to_log_params: expr, $($args:tt)*) => {{ + let log_params = $crate::ToLogParams::to_log_params($to_log_params); $crate::log!(log_params.fatal(), $($args)*); }} } @@ -281,7 +281,7 @@ pub unsafe fn impl_log( static FORMAT_CSTR: OnceLock = OnceLock::new(); let format_cstr = FORMAT_CSTR.get_or_init(|| CString::new("%s").unwrap()); - let severity = severity.to_native(); + let severity = severity.as_native(); let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); // SAFETY: Global variables are protected via ENTITY_LIFECYCLE_MUTEX, no other preconditions are required diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index d3e0af2a8..d8cbc8995 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -67,9 +67,9 @@ impl<'a> LogParams<'a> { /// /// [1]: Borrow /// [2]: std::borrow::Cow -pub trait AsLogParams<'a>: Sized { +pub trait ToLogParams<'a>: Sized { /// Convert the object into LogParams with default settings - fn as_log_params(self) -> LogParams<'a>; + fn to_log_params(self) -> LogParams<'a>; /// The logging should only happen once fn once(self) -> LogParams<'a> { @@ -83,7 +83,7 @@ pub trait AsLogParams<'a>: Sized { /// Set the occurrence behavior of the log instance fn occurs(self, occurs: LogOccurrence) -> LogParams<'a> { - let mut params = self.as_log_params(); + let mut params = self.to_log_params(); params.occurs = occurs; params } @@ -94,7 +94,7 @@ pub trait AsLogParams<'a>: Sized { /// /// A negative duration is not valid, but will be treated as a zero duration. fn interval(self, interval: Duration) -> LogParams<'a> { - let mut params = self.as_log_params(); + let mut params = self.to_log_params(); params.interval = interval; params } @@ -103,10 +103,10 @@ pub trait AsLogParams<'a>: Sized { /// this function. /// /// Other factors may prevent the log from being published if a `true` is - /// passed in, such as `AsLogParams::interval` or `AsLogParams::once` + /// passed in, such as `ToLogParams::interval` or `ToLogParams::once` /// filtering the log. fn only_if(self, only_if: bool) -> LogParams<'a> { - let mut params = self.as_log_params(); + let mut params = self.to_log_params(); params.only_if = only_if; params } @@ -140,7 +140,7 @@ pub trait AsLogParams<'a>: Sized { /// Set the severity for this instance of logging. The default value will be /// [`LogSeverity::Info`]. fn severity(self, severity: LogSeverity) -> LogParams<'a> { - let mut params = self.as_log_params(); + let mut params = self.to_log_params(); params.severity = severity; params } @@ -188,7 +188,7 @@ pub enum LogSeverity { } impl LogSeverity { - pub(super) fn to_native(&self) -> RCUTILS_LOG_SEVERITY { + pub(super) fn as_native(&self) -> RCUTILS_LOG_SEVERITY { use crate::rcl_bindings::rcl_log_severity_t::*; match self { LogSeverity::Unset => RCUTILS_LOG_SEVERITY_UNSET, @@ -227,20 +227,20 @@ impl Default for LogOccurrence { // Anything that we can borrow a string from can be used as if it's a logger and // turned into LogParams -impl<'a, T: Borrow> AsLogParams<'a> for &'a T { - fn as_log_params(self) -> LogParams<'a> { +impl<'a, T: Borrow> ToLogParams<'a> for &'a T { + fn to_log_params(self) -> LogParams<'a> { LogParams::new(LoggerName::Unvalidated(self.borrow())) } } -impl<'a> AsLogParams<'a> for &'a str { - fn as_log_params(self) -> LogParams<'a> { +impl<'a> ToLogParams<'a> for &'a str { + fn to_log_params(self) -> LogParams<'a> { LogParams::new(LoggerName::Unvalidated(self)) } } -impl<'a> AsLogParams<'a> for LogParams<'a> { - fn as_log_params(self) -> LogParams<'a> { +impl<'a> ToLogParams<'a> for LogParams<'a> { + fn to_log_params(self) -> LogParams<'a> { self } } diff --git a/rclrs/src/logging/logger.rs b/rclrs/src/logging/logger.rs index 10bcce3fd..64f917c81 100644 --- a/rclrs/src/logging/logger.rs +++ b/rclrs/src/logging/logger.rs @@ -2,12 +2,12 @@ use std::{borrow::Borrow, ffi::CString}; use crate::{ rcl_bindings::{rcutils_logging_set_default_logger_level, rcutils_logging_set_logger_level}, - AsLogParams, LogParams, LogSeverity, LoggerName, RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, + ToLogParams, LogParams, LogSeverity, LoggerName, RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, }; /// Logger can be passed in as the first argument into one of the [logging][1] /// macros provided by rclrs. When passing it into one of the logging macros, -/// you can optionally apply any of the methods from [`AsLogParams`] to tweak +/// you can optionally apply any of the methods from [`ToLogParams`] to tweak /// the logging behavior. /// /// You can obtain a Logger in the following ways: @@ -71,7 +71,7 @@ impl Logger { // - not thread-safe, so we lock the global mutex before calling this let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { - rcutils_logging_set_logger_level(self.c_name.as_ptr(), severity.to_native() as i32).ok() + rcutils_logging_set_logger_level(self.c_name.as_ptr(), severity.as_native() as i32).ok() } } @@ -83,13 +83,13 @@ impl Logger { // - not thread-safe, so we lock the global mutex before calling this let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { - rcutils_logging_set_default_logger_level(severity.to_native() as i32); + rcutils_logging_set_default_logger_level(severity.as_native() as i32); } } } -impl<'a> AsLogParams<'a> for &'a Logger { - fn as_log_params(self) -> LogParams<'a> { +impl<'a> ToLogParams<'a> for &'a Logger { + fn to_log_params(self) -> LogParams<'a> { LogParams::new(LoggerName::Validated(&self.c_name)) } } diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 89e6d4bd0..5d50b114c 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -13,7 +13,7 @@ use rosidl_runtime_rs::Message; pub use self::{builder::*, graph::*}; use crate::{ - rcl_bindings::*, AsLogParams, Client, ClientBase, Clock, Context, ContextHandle, + rcl_bindings::*, ToLogParams, Client, ClientBase, Clock, Context, ContextHandle, GuardCondition, LogParams, Logger, ParameterBuilder, ParameterInterface, ParameterVariant, Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase, Subscription, SubscriptionBase, SubscriptionCallback, TimeSource, ENTITY_LIFECYCLE_MUTEX, @@ -472,9 +472,9 @@ impl Node { } } -impl<'a> AsLogParams<'a> for &'a Node { - fn as_log_params(self) -> LogParams<'a> { - self.logger().as_log_params() +impl<'a> ToLogParams<'a> for &'a Node { + fn to_log_params(self) -> LogParams<'a> { + self.logger().to_log_params() } } From 474e4d2d85edbfc8837c838f1ce01ff881b8529d Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 19:59:36 +0800 Subject: [PATCH 14/33] Comply with clippy suggestions Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 4 ++-- rclrs/src/subscription.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index 4cda8c6e3..ede331d7d 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -309,7 +309,7 @@ pub unsafe fn impl_log( // This is done inside of the function impl_log instead of in a macro // so that this map is global for the entire application. static NAME_MAP: OnceLock>> = OnceLock::new(); - let name_map = NAME_MAP.get_or_init(|| Default::default()); + let name_map = NAME_MAP.get_or_init(Default::default); { // We need to keep name_map locked while we call send_log, but @@ -347,7 +347,7 @@ pub unsafe fn impl_log( static INTERNAL_LOGGER_NAME: OnceLock = OnceLock::new(); let internal_logger_name = INTERNAL_LOGGER_NAME.get_or_init(|| CString::new("logger").unwrap()); - send_log(severity, &internal_logger_name, &invalid_msg); + send_log(severity, internal_logger_name, invalid_msg); return; } }; diff --git a/rclrs/src/subscription.rs b/rclrs/src/subscription.rs index 05b01beb5..36c241d19 100644 --- a/rclrs/src/subscription.rs +++ b/rclrs/src/subscription.rs @@ -381,7 +381,7 @@ mod tests { // Test get_subscriptions_info_by_topic() let expected_subscriptions_info = vec![TopicEndpointInfo { - node_name: format!("graph_test_node_2"), + node_name: String::from("graph_test_node_2"), node_namespace: String::from(namespace), topic_type: String::from("test_msgs/msg/Empty"), }]; From e9ef5dbc24d4580bf7890381c541e73022b7cb7f Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 20:20:27 +0800 Subject: [PATCH 15/33] Putting log handling into its own module Signed-off-by: Michael X. Grey --- rclrs/src/logging/logging_configuration.rs | 119 +++++++++++++-------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 43778e564..407fd6a05 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -1,21 +1,15 @@ -use std::{ - sync::{Arc, Mutex, OnceLock, Weak}, - time::Instant, -}; +use std::sync::{Arc, Mutex, OnceLock, Weak}; use crate::{ rcl_bindings::*, - RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, LogSeverity, + RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, }; struct LoggingConfiguration { lifecycle: Mutex>, } -pub(crate) struct LoggingLifecycle { - // Keep the pointer - handler: Option, -} +pub(crate) struct LoggingLifecycle; impl LoggingLifecycle { fn new(args: &rcl_arguments_t) -> Result { @@ -59,40 +53,79 @@ impl Drop for LoggingLifecycle { } } -pub struct LogLocation<'a> { - pub function_name: &'a str, - pub file_name: &'a str, - pub line_number: usize, -} - -pub type RawLogHandler = Box; - -static LOGGING_OUTPUT_HANDLER: OnceLock = OnceLock::new(); - -pub fn set_raw_logging_output_handler( - -) -> Result<(), RawLogHandler> { - Ok(()) -} - -pub type LogHandler = Box; - -pub fn set_logging_output_handler( +pub(crate) mod log_handler { + //! This module provides a way to customize how log output is handled. For + //! now we are not making this a public API and are only using it for tests + //! in rclrs. We can consider making it public in the future, but we should + //! put more consideration into the API before doing so. + + use std::{ + sync::OnceLock, + time::Instant, + }; + + use crate::{ + rcl_bindings::*, LogSeverity, + }; + + /// Global variable that allows a custom log handler to be set. This log + /// handler will be applied throughout the entire application and cannot be + /// undone. If you want to be able to change the log handler over the + /// lifetime of your application, you should design your own custom handler + /// with an Arc> inside that allows its behavior to be modified. + static LOGGING_OUTPUT_HANDLER: OnceLock = OnceLock::new(); + + /// Alias for an arbitrary log handler that is compatible with raw rcl types + pub(crate) type RawLogHandler = Box; + + /// Alias for an abitrary idiomatic log handler + pub(crate) type LogHandler = Box; + + /// Rust-idiomatic representation of the location of a log + pub(crate) struct LogLocation<'a> { + pub function_name: &'a str, + pub file_name: &'a str, + pub line_number: usize, + } -) -> Result<(), { + /// Set an idiomatic log hander + pub(crate) fn set_logging_output_handler( + handler: LogHandler, + ) -> Result<(), RawLogHandler> { + let raw_handler = Box::new( + | + location: *const rcutils_log_location_t, + severity: std::os::raw::c_int, + logger_name: *const std::os::raw::c_char, + timestamp: rcutils_time_point_value_t, + message: *const std::os::raw::c_char, + logging_output: *mut rcutils_char_array_t, + | { + + } + ); + + set_raw_logging_output_handler(raw_handler) + } + /// Set the raw value for the logging output handler + pub(crate) fn set_raw_logging_output_handler( + handler: RawLogHandler, + ) -> Result<(), RawLogHandler> { + LOGGING_OUTPUT_HANDLER.set(handler) + } } From 0416551d237ec624631f1c498129f174d6392959 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 20:21:36 +0800 Subject: [PATCH 16/33] Fix formatting with ToLogParams Signed-off-by: Michael X. Grey --- rclrs/src/logging/logger.rs | 2 +- rclrs/src/node.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rclrs/src/logging/logger.rs b/rclrs/src/logging/logger.rs index 64f917c81..2d55f2d7a 100644 --- a/rclrs/src/logging/logger.rs +++ b/rclrs/src/logging/logger.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, ffi::CString}; use crate::{ rcl_bindings::{rcutils_logging_set_default_logger_level, rcutils_logging_set_logger_level}, - ToLogParams, LogParams, LogSeverity, LoggerName, RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, + LogParams, LogSeverity, LoggerName, RclrsError, ToLogParams, ToResult, ENTITY_LIFECYCLE_MUTEX, }; /// Logger can be passed in as the first argument into one of the [logging][1] diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 5d50b114c..5afa1fe03 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -13,10 +13,10 @@ use rosidl_runtime_rs::Message; pub use self::{builder::*, graph::*}; use crate::{ - rcl_bindings::*, ToLogParams, Client, ClientBase, Clock, Context, ContextHandle, - GuardCondition, LogParams, Logger, ParameterBuilder, ParameterInterface, ParameterVariant, - Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase, Subscription, - SubscriptionBase, SubscriptionCallback, TimeSource, ENTITY_LIFECYCLE_MUTEX, + rcl_bindings::*, Client, ClientBase, Clock, Context, ContextHandle, GuardCondition, LogParams, + Logger, ParameterBuilder, ParameterInterface, ParameterVariant, Parameters, Publisher, + QoSProfile, RclrsError, Service, ServiceBase, Subscription, SubscriptionBase, + SubscriptionCallback, TimeSource, ToLogParams, ENTITY_LIFECYCLE_MUTEX, }; // SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread From 5b769873ac1c5f9621a6868dd69a400820211a24 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 22:37:53 +0800 Subject: [PATCH 17/33] Able to override the log handling Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 26 +++- rclrs/src/logging/log_params.rs | 18 +++ rclrs/src/logging/logging_configuration.rs | 133 ++++++++++++++++----- 3 files changed, 146 insertions(+), 31 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index ede331d7d..22b6deaef 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -278,8 +278,6 @@ pub unsafe fn impl_log( file_name: file.as_ptr(), line_number: line as usize, }; - static FORMAT_CSTR: OnceLock = OnceLock::new(); - let format_cstr = FORMAT_CSTR.get_or_init(|| CString::new("%s").unwrap()); let severity = severity.as_native(); @@ -290,7 +288,6 @@ pub unsafe fn impl_log( &location, severity as i32, logger_name.as_ptr(), - format_cstr.as_ptr(), message.as_ptr(), ); } @@ -380,14 +377,33 @@ macro_rules! function { #[cfg(test)] mod tests { - use crate::{test_helpers::*, *}; + use std::sync::Mutex; + use crate::{test_helpers::*, *, log_handler::*}; #[test] fn test_logging_macros() -> Result<(), RclrsError> { let graph = construct_test_graph("test_logging_macros")?; + + let log_collection: Arc>>> = Arc::new(Mutex::new(Vec::new())); + let inner_log_collection = log_collection.clone(); + + log_handler::set_logging_output_handler( + move |log_entry: log_handler::LogEntry| { + inner_log_collection.lock().unwrap().push(log_entry.into_owned()); + } + ).unwrap(); + + let last_logger_name_is = |logger_name: &str| { + assert_eq!( + &log_collection.lock().unwrap().last().unwrap().logger_name, + logger_name + ); + }; + let node = graph.node1; log!(&*node, "Logging with node dereference"); + last_logger_name_is(node.logger().name()); for _ in 0..10 { log!(node.once(), "Logging once"); @@ -433,6 +449,8 @@ mod tests { ); } + reset_logging_output_handler(); + Ok(()) } diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index d8cbc8995..88d5f059f 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -162,6 +162,11 @@ pub enum LoggerName<'a> { } /// Logging severity. +// +// TODO(@mxgrey): Consider whether this is redundant with RCUTILS_LOG_SEVERITY. +// Perhaps we can customize the output of bindgen to automatically change the name +// of RCUTILS_LOG_SEVERITY to just LogSeverity so it's more idiomatic and then +// export it from the rclrs module. #[doc(hidden)] #[derive(Debug, Clone, Copy)] pub enum LogSeverity { @@ -199,6 +204,19 @@ impl LogSeverity { LogSeverity::Fatal => RCUTILS_LOG_SEVERITY_FATAL, } } + + pub(crate) fn from_native(native: i32) -> Self { + use crate::rcl_bindings::rcl_log_severity_t::*; + match native { + _ if native == RCUTILS_LOG_SEVERITY_UNSET as i32 => LogSeverity::Unset, + _ if native == RCUTILS_LOG_SEVERITY_DEBUG as i32 => LogSeverity::Debug, + _ if native == RCUTILS_LOG_SEVERITY_INFO as i32 => LogSeverity::Info, + _ if native == RCUTILS_LOG_SEVERITY_WARN as i32 => LogSeverity::Warn, + _ if native == RCUTILS_LOG_SEVERITY_ERROR as i32 => LogSeverity::Error, + _ if native == RCUTILS_LOG_SEVERITY_FATAL as i32 => LogSeverity::Fatal, + _ => panic!("Invalid native severity received: {}", native), + } + } } impl Default for LogSeverity { diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 407fd6a05..f4f6fd932 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -53,6 +53,7 @@ impl Drop for LoggingLifecycle { } } +#[cfg(test)] pub(crate) mod log_handler { //! This module provides a way to customize how log output is handled. For //! now we are not making this a public API and are only using it for tests @@ -61,11 +62,12 @@ pub(crate) mod log_handler { use std::{ sync::OnceLock, - time::Instant, + ffi::CStr, + borrow::Cow, }; use crate::{ - rcl_bindings::*, LogSeverity, + rcl_bindings::*, LogSeverity, ENTITY_LIFECYCLE_MUTEX, }; /// Global variable that allows a custom log handler to be set. This log @@ -81,51 +83,128 @@ pub(crate) mod log_handler { std::os::raw::c_int, // severity *const std::os::raw::c_char, // logger name rcutils_time_point_value_t, // timestamp - *const std::os::raw::c_char, // message - *mut rcutils_char_array_t, // logging_output + *const std::os::raw::c_char, // format + *mut va_list, // formatting arguments ) + 'static + Send + Sync>; - /// Alias for an abitrary idiomatic log handler - pub(crate) type LogHandler = Box { + pub(crate) location: LogLocation<'a>, + pub(crate) severity: LogSeverity, + pub(crate) logger_name: Cow<'a, str>, + pub(crate) timestamp: i64, + pub(crate) message: Cow<'a, str>, + } - ) + 'static + Send + Sync>; + impl<'a> LogEntry<'a> { + /// Change the entry from something borrowed into something owned + pub(crate) fn into_owned(self) -> LogEntry<'static> { + LogEntry { + location: self.location.into_owned(), + severity: self.severity, + logger_name: Cow::Owned(self.logger_name.into_owned()), + timestamp: self.timestamp, + message: Cow::Owned(self.message.into_owned()), + } + } + } /// Rust-idiomatic representation of the location of a log + #[derive(Debug)] pub(crate) struct LogLocation<'a> { - pub function_name: &'a str, - pub file_name: &'a str, + pub function_name: Cow<'a, str>, + pub file_name: Cow<'a, str>, pub line_number: usize, } + impl<'a> LogLocation<'a> { + pub(crate) fn into_owned(self) -> LogLocation<'static> { + LogLocation { + function_name: Cow::Owned(self.function_name.into_owned()), + file_name: Cow::Owned(self.file_name.into_owned()), + line_number: self.line_number, + } + } + } + + #[derive(Debug)] + pub(crate) struct OutputHandlerAlreadySet; + /// Set an idiomatic log hander pub(crate) fn set_logging_output_handler( - handler: LogHandler, - ) -> Result<(), RawLogHandler> { + handler: impl Fn(LogEntry) + 'static + Send + Sync, + ) -> Result<(), OutputHandlerAlreadySet> { let raw_handler = Box::new( - | - location: *const rcutils_log_location_t, - severity: std::os::raw::c_int, - logger_name: *const std::os::raw::c_char, - timestamp: rcutils_time_point_value_t, - message: *const std::os::raw::c_char, - logging_output: *mut rcutils_char_array_t, + move | + raw_location: *const rcutils_log_location_t, + raw_severity: std::os::raw::c_int, + raw_logger_name: *const std::os::raw::c_char, + raw_timestamp: rcutils_time_point_value_t, + raw_format: *const std::os::raw::c_char, + // NOTE: In rclrs we are choosing to always format the full + // message in advance, so the format field always contains + // the finished formatted message. Therefore we can just ignore + // the raw formatting arguments. + _raw_formatting_arguments: *mut va_list, | { - + unsafe { + // NOTE: We use .unwrap() extensively inside this function because + // it only gets used during tests. We should reconsider this if + // we ever make this public. + let location = LogLocation { + function_name: Cow::Borrowed(CStr::from_ptr((*raw_location).function_name).to_str().unwrap()), + file_name: Cow::Borrowed(CStr::from_ptr((*raw_location).file_name).to_str().unwrap()), + line_number: (*raw_location).line_number, + }; + let severity = LogSeverity::from_native(raw_severity); + let logger_name = Cow::Borrowed(CStr::from_ptr(raw_logger_name).to_str().unwrap()); + let timestamp: i64 = raw_timestamp; + let message = Cow::Borrowed(CStr::from_ptr(raw_format).to_str().unwrap()); + handler(LogEntry { location, severity, logger_name, timestamp, message }); + } } ); set_raw_logging_output_handler(raw_handler) } - /// Set the raw value for the logging output handler + /// Set the logging output handler directly pub(crate) fn set_raw_logging_output_handler( handler: RawLogHandler, - ) -> Result<(), RawLogHandler> { - LOGGING_OUTPUT_HANDLER.set(handler) + ) -> Result<(), OutputHandlerAlreadySet> { + LOGGING_OUTPUT_HANDLER.set(handler).map_err(|_| OutputHandlerAlreadySet)?; + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + unsafe { + // SAFETY: + // - We have locked the global mutex + rcutils_logging_set_output_handler(Some(rclrs_logging_output_handler)); + let rcl_handler = rcutils_logging_get_output_handler(); + } + + Ok(()) + } + + /// This function exists so that we can give a raw function pointer to + /// rcutils_logging_set_output_handler, which is needed by its API. + extern fn rclrs_logging_output_handler( + location: *const rcutils_log_location_t, + severity: std::os::raw::c_int, + logger_name: *const std::os::raw::c_char, + timestamp: rcutils_time_point_value_t, + message: *const std::os::raw::c_char, + logging_output: *mut va_list, + ) { + let handler = LOGGING_OUTPUT_HANDLER.get().unwrap(); + (*handler)(location, severity, logger_name, timestamp, message, logging_output); + } + + /// Reset the logging output handler to the default one + pub(crate) fn reset_logging_output_handler() { + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + unsafe { + rcutils_logging_set_output_handler( + Some(rcl_logging_multiple_output_handler) + ); + } } } From 3934c3262a45709b15aa8306a8cb8f21bb46f498 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 22:41:46 +0800 Subject: [PATCH 18/33] Try to help cargo docs resolve macro links Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index ede331d7d..d008e5d66 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -58,11 +58,11 @@ pub use logger::*; /// /// All of the above examples will also work with the severity-specific log macros, /// but any severity that gets passed in will be overridden: -/// - [`log_debug`] -/// - [`log_info`] -/// - [`log_warn`] -/// - [`log_error`] -/// - [`log_fatal`] +/// - [`log_debug`][crate::log_debug] +/// - [`log_info`][crate::log_info] +/// - [`log_warn`][crate::log_warn] +/// - [`log_error`][crate::log_error] +/// - [`log_fatal`][crate::log_fatal] /// /// # Panics /// From 3db4183b4e547cba7a405baad1734eaa24d7a670 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 23:00:47 +0800 Subject: [PATCH 19/33] Cleaner log testing Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 97 ++++++++++++++++---- rclrs/src/logging/log_params.rs | 2 +- rclrs/src/logging/logging_configuration.rs | 100 ++++++++++++--------- 3 files changed, 139 insertions(+), 60 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index e805c4b80..bb9cb97b3 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -377,8 +377,8 @@ macro_rules! function { #[cfg(test)] mod tests { + use crate::{log_handler::*, test_helpers::*, *}; use std::sync::Mutex; - use crate::{test_helpers::*, *, log_handler::*}; #[test] fn test_logging_macros() -> Result<(), RclrsError> { @@ -387,34 +387,83 @@ mod tests { let log_collection: Arc>>> = Arc::new(Mutex::new(Vec::new())); let inner_log_collection = log_collection.clone(); - log_handler::set_logging_output_handler( - move |log_entry: log_handler::LogEntry| { - inner_log_collection.lock().unwrap().push(log_entry.into_owned()); - } - ).unwrap(); + log_handler::set_logging_output_handler(move |log_entry: log_handler::LogEntry| { + inner_log_collection + .lock() + .unwrap() + .push(log_entry.into_owned()); + }) + .unwrap(); - let last_logger_name_is = |logger_name: &str| { - assert_eq!( - &log_collection.lock().unwrap().last().unwrap().logger_name, - logger_name - ); + let last_logger_name = || { + log_collection + .lock() + .unwrap() + .last() + .unwrap() + .logger_name + .clone() + }; + + let last_message = || { + log_collection + .lock() + .unwrap() + .last() + .unwrap() + .message + .clone() + }; + + let last_severity = || log_collection.lock().unwrap().last().unwrap().severity; + + let count_message = |message: &str| { + let mut count = 0; + for log in log_collection.lock().unwrap().iter() { + if log.message == message { + count += 1; + } + } + count }; let node = graph.node1; log!(&*node, "Logging with node dereference"); - last_logger_name_is(node.logger().name()); + assert_eq!(last_logger_name(), node.logger().name()); + assert_eq!(last_message(), "Logging with node dereference"); + assert_eq!(last_severity(), LogSeverity::Info); for _ in 0..10 { log!(node.once(), "Logging once"); } + assert_eq!(count_message("Logging once"), 1); + assert_eq!(last_severity(), LogSeverity::Info); log!(node.logger(), "Logging with node logger"); + assert_eq!(last_message(), "Logging with node logger"); + assert_eq!(last_severity(), LogSeverity::Info); + log!(node.debug(), "Debug from node"); + // The default severity level is Info so we should not see the last message + assert_ne!(last_message(), "Debug from node"); + assert_ne!(last_severity(), LogSeverity::Debug); + log!(node.info(), "Info from node"); + assert_eq!(last_message(), "Info from node"); + assert_eq!(last_severity(), LogSeverity::Info); + log!(node.warn(), "Warn from node"); + assert_eq!(last_message(), "Warn from node"); + assert_eq!(last_severity(), LogSeverity::Warn); + log!(node.error(), "Error from node"); + assert_eq!(last_message(), "Error from node"); + assert_eq!(last_severity(), LogSeverity::Error); + log!(node.fatal(), "Fatal from node"); + assert_eq!(last_message(), "Fatal from node"); + assert_eq!(last_severity(), LogSeverity::Fatal); log_debug!(node.logger(), "log_debug macro"); log_info!(node.logger(), "log_info macro"); @@ -428,26 +477,42 @@ mod tests { for i in 0..3 { log!(node.warn().skip_first(), "Formatted warning #{}", i); } + assert_eq!(count_message("Formatted warning #0"), 0); + assert_eq!(count_message("Formatted warning #1"), 1); + assert_eq!(count_message("Formatted warning #2"), 1); node.logger().set_level(LogSeverity::Debug).unwrap(); log_debug!(node.logger(), "This debug message appears"); + assert_eq!(last_message(), "This debug message appears"); + assert_eq!(last_severity(), LogSeverity::Debug); + node.logger().set_level(LogSeverity::Info).unwrap(); - log_debug!(node.logger(), "This debug message does not"); + log_debug!(node.logger(), "This debug message does not appear"); + assert_ne!(last_message(), "This debug message does not appear"); log!("custom logger name", "message for custom logger"); + assert_eq!(last_logger_name(), "custom logger name"); + assert_eq!(last_message(), "message for custom logger"); + for _ in 0..3 { log!( - "custom logger name".once(), - "one-time message for custom logger" + "custom logger name once".once(), + "one-time message for custom logger", ); } + assert_eq!(last_logger_name(), "custom logger name once"); + assert_eq!(last_severity(), LogSeverity::Info); + assert_eq!(count_message("one-time message for custom logger"), 1); for _ in 0..3 { log!( - "custom logger name".error().skip_first(), + "custom logger name skip".error().skip_first(), "error for custom logger", ); } + assert_eq!(last_logger_name(), "custom logger name skip"); + assert_eq!(last_severity(), LogSeverity::Error); + assert_eq!(count_message("error for custom logger"), 2); reset_logging_output_handler(); diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index 88d5f059f..dd8086cb4 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -168,7 +168,7 @@ pub enum LoggerName<'a> { // of RCUTILS_LOG_SEVERITY to just LogSeverity so it's more idiomatic and then // export it from the rclrs module. #[doc(hidden)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum LogSeverity { /// Use the severity level of the parent logger (or the root logger if the /// current logger has no parent) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index f4f6fd932..b9a08f1d8 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -1,9 +1,6 @@ use std::sync::{Arc, Mutex, OnceLock, Weak}; -use crate::{ - rcl_bindings::*, - RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX, -}; +use crate::{rcl_bindings::*, RclrsError, ToResult, ENTITY_LIFECYCLE_MUTEX}; struct LoggingConfiguration { lifecycle: Mutex>, @@ -60,15 +57,9 @@ pub(crate) mod log_handler { //! in rclrs. We can consider making it public in the future, but we should //! put more consideration into the API before doing so. - use std::{ - sync::OnceLock, - ffi::CStr, - borrow::Cow, - }; + use std::{borrow::Cow, ffi::CStr, sync::OnceLock}; - use crate::{ - rcl_bindings::*, LogSeverity, ENTITY_LIFECYCLE_MUTEX, - }; + use crate::{rcl_bindings::*, LogSeverity, ENTITY_LIFECYCLE_MUTEX}; /// Global variable that allows a custom log handler to be set. This log /// handler will be applied throughout the entire application and cannot be @@ -78,14 +69,19 @@ pub(crate) mod log_handler { static LOGGING_OUTPUT_HANDLER: OnceLock = OnceLock::new(); /// Alias for an arbitrary log handler that is compatible with raw rcl types - pub(crate) type RawLogHandler = Box; + pub(crate) type RawLogHandler = Box< + dyn Fn( + *const rcutils_log_location_t, // location + std::os::raw::c_int, // severity + *const std::os::raw::c_char, // logger name + rcutils_time_point_value_t, // timestamp + *const std::os::raw::c_char, // format + *mut va_list, // formatting arguments + ) + + 'static + + Send + + Sync, + >; /// This is an idiomatic representation of all the information for a log entry pub(crate) struct LogEntry<'a> { @@ -135,34 +131,45 @@ pub(crate) mod log_handler { handler: impl Fn(LogEntry) + 'static + Send + Sync, ) -> Result<(), OutputHandlerAlreadySet> { let raw_handler = Box::new( - move | - raw_location: *const rcutils_log_location_t, - raw_severity: std::os::raw::c_int, - raw_logger_name: *const std::os::raw::c_char, - raw_timestamp: rcutils_time_point_value_t, - raw_format: *const std::os::raw::c_char, - // NOTE: In rclrs we are choosing to always format the full - // message in advance, so the format field always contains - // the finished formatted message. Therefore we can just ignore - // the raw formatting arguments. - _raw_formatting_arguments: *mut va_list, - | { + move |raw_location: *const rcutils_log_location_t, + raw_severity: std::os::raw::c_int, + raw_logger_name: *const std::os::raw::c_char, + raw_timestamp: rcutils_time_point_value_t, + raw_format: *const std::os::raw::c_char, + // NOTE: In rclrs we are choosing to always format the full + // message in advance, so the format field always contains + // the finished formatted message. Therefore we can just ignore + // the raw formatting arguments. + _raw_formatting_arguments: *mut va_list| { unsafe { // NOTE: We use .unwrap() extensively inside this function because // it only gets used during tests. We should reconsider this if // we ever make this public. let location = LogLocation { - function_name: Cow::Borrowed(CStr::from_ptr((*raw_location).function_name).to_str().unwrap()), - file_name: Cow::Borrowed(CStr::from_ptr((*raw_location).file_name).to_str().unwrap()), + function_name: Cow::Borrowed( + CStr::from_ptr((*raw_location).function_name) + .to_str() + .unwrap(), + ), + file_name: Cow::Borrowed( + CStr::from_ptr((*raw_location).file_name).to_str().unwrap(), + ), line_number: (*raw_location).line_number, }; let severity = LogSeverity::from_native(raw_severity); - let logger_name = Cow::Borrowed(CStr::from_ptr(raw_logger_name).to_str().unwrap()); + let logger_name = + Cow::Borrowed(CStr::from_ptr(raw_logger_name).to_str().unwrap()); let timestamp: i64 = raw_timestamp; let message = Cow::Borrowed(CStr::from_ptr(raw_format).to_str().unwrap()); - handler(LogEntry { location, severity, logger_name, timestamp, message }); + handler(LogEntry { + location, + severity, + logger_name, + timestamp, + message, + }); } - } + }, ); set_raw_logging_output_handler(raw_handler) @@ -172,7 +179,9 @@ pub(crate) mod log_handler { pub(crate) fn set_raw_logging_output_handler( handler: RawLogHandler, ) -> Result<(), OutputHandlerAlreadySet> { - LOGGING_OUTPUT_HANDLER.set(handler).map_err(|_| OutputHandlerAlreadySet)?; + LOGGING_OUTPUT_HANDLER + .set(handler) + .map_err(|_| OutputHandlerAlreadySet)?; let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { // SAFETY: @@ -186,7 +195,7 @@ pub(crate) mod log_handler { /// This function exists so that we can give a raw function pointer to /// rcutils_logging_set_output_handler, which is needed by its API. - extern fn rclrs_logging_output_handler( + extern "C" fn rclrs_logging_output_handler( location: *const rcutils_log_location_t, severity: std::os::raw::c_int, logger_name: *const std::os::raw::c_char, @@ -195,16 +204,21 @@ pub(crate) mod log_handler { logging_output: *mut va_list, ) { let handler = LOGGING_OUTPUT_HANDLER.get().unwrap(); - (*handler)(location, severity, logger_name, timestamp, message, logging_output); + (*handler)( + location, + severity, + logger_name, + timestamp, + message, + logging_output, + ); } /// Reset the logging output handler to the default one pub(crate) fn reset_logging_output_handler() { let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { - rcutils_logging_set_output_handler( - Some(rcl_logging_multiple_output_handler) - ); + rcutils_logging_set_output_handler(Some(rcl_logging_multiple_output_handler)); } } } From a9f5b3f4816589dc270ed96e9c476e7836f548c0 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 5 Nov 2024 23:50:44 +0800 Subject: [PATCH 20/33] Fork expectations based on distro version Signed-off-by: Michael X. Grey --- rclrs/build.rs | 1 + rclrs/src/node/graph.rs | 35 +++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/rclrs/build.rs b/rclrs/build.rs index 3ac6dc414..0d5f84a87 100644 --- a/rclrs/build.rs +++ b/rclrs/build.rs @@ -35,6 +35,7 @@ fn main() { } }; + println!("cargo:rustc-check-cfg=cfg(ros_distro, values(\"humble\", \"iron\", \"jazzy\"))"); println!("cargo:rustc-cfg=ros_distro=\"{ros_distro}\""); let mut builder = bindgen::Builder::default() diff --git a/rclrs/src/node/graph.rs b/rclrs/src/node/graph.rs index 6f9e6d866..0f8a9b3fa 100644 --- a/rclrs/src/node/graph.rs +++ b/rclrs/src/node/graph.rs @@ -487,16 +487,29 @@ mod tests { .unwrap(); let node_name = "test_publisher_names_and_types"; let node = Node::new(&context, node_name).unwrap(); - // Test that the graph has no publishers besides /rosout + + let check_rosout = |topics: HashMap>| { + // rosout shows up in humble and iron, even if the graph is empty + #[cfg(any(ros_distro = "humble", ros_distro = "iron"))] + { + assert_eq!(topics.len(), 1); + assert_eq!( + topics.get("/rosout").unwrap().first().unwrap(), + "rcl_interfaces/msg/Log" + ); + } + + // rosout does not automatically show up in jazzy when the graph is empty + #[cfg(ros_distro = "jazzy")] + { + assert_eq!(topics.len(), 0); + } + }; + let names_and_topics = node .get_publisher_names_and_types_by_node(node_name, "") .unwrap(); - - assert_eq!(names_and_topics.len(), 1); - assert_eq!( - names_and_topics.get("/rosout").unwrap().first().unwrap(), - "rcl_interfaces/msg/Log" - ); + check_rosout(names_and_topics); let num_publishers = node.count_publishers("/test").unwrap(); @@ -539,14 +552,8 @@ mod tests { assert_eq!(names_and_topics.len(), 0); - // Test that the graph has no topics besides /rosout let names_and_topics = node.get_topic_names_and_types().unwrap(); - - assert_eq!(names_and_topics.len(), 1); - assert_eq!( - names_and_topics.get("/rosout").unwrap().first().unwrap(), - "rcl_interfaces/msg/Log" - ); + check_rosout(names_and_topics); } #[test] From 049a829965f0428ed0cfb5f489735e29e7ada0d1 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 00:32:26 +0800 Subject: [PATCH 21/33] Account for rolling distro Signed-off-by: Michael X. Grey --- rclrs/build.rs | 2 +- rclrs/src/node/graph.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rclrs/build.rs b/rclrs/build.rs index 0d5f84a87..91d0b190f 100644 --- a/rclrs/build.rs +++ b/rclrs/build.rs @@ -35,7 +35,7 @@ fn main() { } }; - println!("cargo:rustc-check-cfg=cfg(ros_distro, values(\"humble\", \"iron\", \"jazzy\"))"); + println!("cargo:rustc-check-cfg=cfg(ros_distro, values(\"humble\", \"iron\", \"jazzy\", \"rolling\"))"); println!("cargo:rustc-cfg=ros_distro=\"{ros_distro}\""); let mut builder = bindgen::Builder::default() diff --git a/rclrs/src/node/graph.rs b/rclrs/src/node/graph.rs index 0f8a9b3fa..639a38e38 100644 --- a/rclrs/src/node/graph.rs +++ b/rclrs/src/node/graph.rs @@ -500,7 +500,7 @@ mod tests { } // rosout does not automatically show up in jazzy when the graph is empty - #[cfg(ros_distro = "jazzy")] + #[cfg(any(ros_distro = "jazzy", ros_distro = "rolling"))] { assert_eq!(topics.len(), 0); } From 974f84183ef612d74dbf079b8df88c6d002cb8f0 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 00:48:48 +0800 Subject: [PATCH 22/33] Fix clippy warnings Signed-off-by: Michael X. Grey --- rclrs/src/logging/log_params.rs | 3 +++ rclrs/src/logging/logging_configuration.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index dd8086cb4..4b5b961e9 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -205,6 +205,9 @@ impl LogSeverity { } } + /// This is only used by the log output handler during testing, so it will + /// not be compiled when testing is not configured + #[allow(dead_code)] pub(crate) fn from_native(native: i32) -> Self { use crate::rcl_bindings::rcl_log_severity_t::*; match native { diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index b9a08f1d8..c1e501259 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -187,7 +187,6 @@ pub(crate) mod log_handler { // SAFETY: // - We have locked the global mutex rcutils_logging_set_output_handler(Some(rclrs_logging_output_handler)); - let rcl_handler = rcutils_logging_get_output_handler(); } Ok(()) From 4699fea07fe1cf34f71a2cb8f7fbe3072369a200 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 02:06:46 +0000 Subject: [PATCH 23/33] Fix function names generated by logger Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 26 +++++++++++++++++++++- rclrs/src/logging/logging_configuration.rs | 3 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index bb9cb97b3..cb65e4202 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -209,7 +209,17 @@ macro_rules! log_unconditional { // Only allocate a CString for the function name once per call to this macro. static FUNCTION_NAME: OnceLock = OnceLock::new(); let function_name = FUNCTION_NAME.get_or_init(|| { - CString::new($crate::function!()).unwrap_or( + // This call to function! is nested within two layers of closures, + // so we need to strip away those suffixes or else users will be + // misled. If we ever restructure these macros or if Rust changes + // the way it names closures, this implementation detail may need to + // change. + let function_name = $crate::function!() + .strip_suffix("::{{closure}}::{{closure}}") + .unwrap() + ; + + CString::new(function_name).unwrap_or( CString::new("").unwrap() ) }); @@ -415,6 +425,16 @@ mod tests { .clone() }; + let last_location = || { + log_collection + .lock() + .unwrap() + .last() + .unwrap() + .location + .clone() + }; + let last_severity = || log_collection.lock().unwrap().last().unwrap().severity; let count_message = |message: &str| { @@ -433,6 +453,10 @@ mod tests { assert_eq!(last_logger_name(), node.logger().name()); assert_eq!(last_message(), "Logging with node dereference"); assert_eq!(last_severity(), LogSeverity::Info); + assert_eq!( + last_location().function_name, + "rclrs::logging::tests::test_logging_macros", + ); for _ in 0..10 { log!(node.once(), "Logging once"); diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index c1e501259..4e68eeef0 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -84,6 +84,7 @@ pub(crate) mod log_handler { >; /// This is an idiomatic representation of all the information for a log entry + #[derive(Clone)] pub(crate) struct LogEntry<'a> { pub(crate) location: LogLocation<'a>, pub(crate) severity: LogSeverity, @@ -106,7 +107,7 @@ pub(crate) mod log_handler { } /// Rust-idiomatic representation of the location of a log - #[derive(Debug)] + #[derive(Debug, Clone)] pub(crate) struct LogLocation<'a> { pub function_name: Cow<'a, str>, pub file_name: Cow<'a, str>, From 89809d15980140c66ff6910db542a615dcb74e9f Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 03:00:51 +0000 Subject: [PATCH 24/33] Fix format string memory vulnerability while still being able to run logging tests Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 81 +++++++++++++++++++--- rclrs/src/logging/logging_configuration.rs | 23 ++++-- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index cb65e4202..bbc79af05 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -216,8 +216,7 @@ macro_rules! log_unconditional { // change. let function_name = $crate::function!() .strip_suffix("::{{closure}}::{{closure}}") - .unwrap() - ; + .unwrap(); CString::new(function_name).unwrap_or( CString::new("").unwrap() @@ -289,17 +288,65 @@ pub unsafe fn impl_log( line_number: line as usize, }; + static FORMAT_STRING: OnceLock = OnceLock::new(); + let format_string = FORMAT_STRING.get_or_init(|| { + CString::new("%s").unwrap() + }); + let severity = severity.as_native(); let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); - // SAFETY: Global variables are protected via ENTITY_LIFECYCLE_MUTEX, no other preconditions are required - unsafe { - rcutils_log( - &location, - severity as i32, - logger_name.as_ptr(), - message.as_ptr(), - ); + + #[cfg(test)] + { + // If we are compiling for testing purposes, when the default log + // output handler is being used we need to use the format_string, + // but when our custom log output handler is being used we need to + // pass the raw message string so that it can be viewed by the + // custom log output handler, allowing us to use it for test assertions. + let using_custom_handler = unsafe { + // SAFETY: The global mutex is locked as _lifecycle + log_handler::is_using_custom_handler() + }; + if using_custom_handler { + // We are using the custom log handler that is only used during + // logging tests, so pass the raw message as the format string. + unsafe { + // SAFETY: The global mutex is locked as _lifecycle + rcutils_log( + &location, + severity as i32, + logger_name.as_ptr(), + message.as_ptr(), + ); + } + } else { + // We are using the normal log handler so call rcutils_log the normal way. + unsafe { + // SAFETY: The global mutex is locked as _lifecycle + rcutils_log( + &location, + severity as i32, + logger_name.as_ptr(), + format_string.as_ptr(), + message.as_ptr(), + ); + } + } + } + + #[cfg(not(test))] + { + unsafe { + // SAFETY: The global mutex is locked as _lifecycle + rcutils_log( + &location, + severity as i32, + logger_name.as_ptr(), + format_string.as_ptr(), + message.as_ptr(), + ); + } } }; @@ -392,6 +439,20 @@ mod tests { #[test] fn test_logging_macros() -> Result<(), RclrsError> { + // This test ensures that strings which are being sent to the logger are + // being sanitized correctly. Rust generally and our logging macro in + // particular do not use C-style formatting strings, but rcutils expects + // to receive C-style formatting strings alongside variadic arguments + // that describe how to fill in the formatting. + // + // If we pass the final string into rcutils as the format with no + // variadic arguments, then it may trigger a crash or undefined behavior + // if the message happens to contain any % symbols. In particular %n + // will trigger a crash when no variadic arguments are given because it + // attempts to write to a buffer. If no buffer is given, a seg fault + // happens. + log!("please do not crash", "%n"); + let graph = construct_test_graph("test_logging_macros")?; let log_collection: Arc>>> = Arc::new(Mutex::new(Vec::new())); diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 4e68eeef0..c1933d776 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -55,7 +55,9 @@ pub(crate) mod log_handler { //! This module provides a way to customize how log output is handled. For //! now we are not making this a public API and are only using it for tests //! in rclrs. We can consider making it public in the future, but we should - //! put more consideration into the API before doing so. + //! put more consideration into the API before doing so, and more crucially + //! we need to figure out a way to process C-style formatting strings with + //! a [`va_list`] from inside of Rust, which seems to be very messy. use std::{borrow::Cow, ffi::CStr, sync::OnceLock}; @@ -137,10 +139,11 @@ pub(crate) mod log_handler { raw_logger_name: *const std::os::raw::c_char, raw_timestamp: rcutils_time_point_value_t, raw_format: *const std::os::raw::c_char, - // NOTE: In rclrs we are choosing to always format the full - // message in advance, so the format field always contains - // the finished formatted message. Therefore we can just ignore - // the raw formatting arguments. + // NOTE: In the rclrs logging test we are choosing to format + // the full message in advance when using the custom handler, + // so the format field always contains the finished formatted + // message. Therefore we can just ignore the raw formatting + // arguments. _raw_formatting_arguments: *mut va_list| { unsafe { // NOTE: We use .unwrap() extensively inside this function because @@ -193,6 +196,16 @@ pub(crate) mod log_handler { Ok(()) } + /// SAFETY: The global mutex needs to be locked while calling this + pub(crate) unsafe fn is_using_custom_handler() -> bool { + let current_handler = unsafe { + // SAFETY: The global mutex is by the caller + rcutils_logging_get_output_handler() + }; + + current_handler.is_some_and(|h| h == rclrs_logging_output_handler) + } + /// This function exists so that we can give a raw function pointer to /// rcutils_logging_set_output_handler, which is needed by its API. extern "C" fn rclrs_logging_output_handler( From 66302065588f6e2b27ac3a8db1c78e64d12f6ee7 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 06:46:24 +0000 Subject: [PATCH 25/33] Fix formatting Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index bbc79af05..b197e0870 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -289,9 +289,7 @@ pub unsafe fn impl_log( }; static FORMAT_STRING: OnceLock = OnceLock::new(); - let format_string = FORMAT_STRING.get_or_init(|| { - CString::new("%s").unwrap() - }); + let format_string = FORMAT_STRING.get_or_init(|| CString::new("%s").unwrap()); let severity = severity.as_native(); From 22021e2b2ca39f32b81d7ec4bfbad7af0d36a9cf Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 07:17:00 +0000 Subject: [PATCH 26/33] More robust detection of using custom log handler Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 6 +---- rclrs/src/logging/logging_configuration.rs | 26 ++++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index b197e0870..a76648202 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -302,11 +302,7 @@ pub unsafe fn impl_log( // but when our custom log output handler is being used we need to // pass the raw message string so that it can be viewed by the // custom log output handler, allowing us to use it for test assertions. - let using_custom_handler = unsafe { - // SAFETY: The global mutex is locked as _lifecycle - log_handler::is_using_custom_handler() - }; - if using_custom_handler { + if log_handler::is_using_custom_handler() { // We are using the custom log handler that is only used during // logging tests, so pass the raw message as the format string. unsafe { diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index c1933d776..6af0de93f 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -59,7 +59,14 @@ pub(crate) mod log_handler { //! we need to figure out a way to process C-style formatting strings with //! a [`va_list`] from inside of Rust, which seems to be very messy. - use std::{borrow::Cow, ffi::CStr, sync::OnceLock}; + use std::{ + borrow::Cow, + ffi::CStr, + sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock, + }, + }; use crate::{rcl_bindings::*, LogSeverity, ENTITY_LIFECYCLE_MUTEX}; @@ -129,6 +136,8 @@ pub(crate) mod log_handler { #[derive(Debug)] pub(crate) struct OutputHandlerAlreadySet; + static USING_CUSTOM_HANDLER: OnceLock = OnceLock::new(); + /// Set an idiomatic log hander pub(crate) fn set_logging_output_handler( handler: impl Fn(LogEntry) + 'static + Send + Sync, @@ -193,17 +202,16 @@ pub(crate) mod log_handler { rcutils_logging_set_output_handler(Some(rclrs_logging_output_handler)); } + USING_CUSTOM_HANDLER + .get_or_init(|| AtomicBool::new(false)) + .store(true, Ordering::Release); Ok(()) } - /// SAFETY: The global mutex needs to be locked while calling this - pub(crate) unsafe fn is_using_custom_handler() -> bool { - let current_handler = unsafe { - // SAFETY: The global mutex is by the caller - rcutils_logging_get_output_handler() - }; - - current_handler.is_some_and(|h| h == rclrs_logging_output_handler) + pub(crate) fn is_using_custom_handler() -> bool { + USING_CUSTOM_HANDLER + .get_or_init(|| AtomicBool::new(false)) + .load(Ordering::Acquire) } /// This function exists so that we can give a raw function pointer to From 88b7a69aff57e96bc99fb2fa17076a1f52b320c4 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Wed, 6 Nov 2024 08:21:48 +0000 Subject: [PATCH 27/33] Remember to reset USING_CUSTOM_HANDLER when the logger gets reset Signed-off-by: Michael X. Grey --- rclrs/src/logging/logging_configuration.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 6af0de93f..aaaabb21b 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -241,5 +241,8 @@ pub(crate) mod log_handler { unsafe { rcutils_logging_set_output_handler(Some(rcl_logging_multiple_output_handler)); } + USING_CUSTOM_HANDLER + .get_or_init(|| AtomicBool::new(false)) + .store(false, Ordering::Release); } } From 295379fcbeeea496315b4d83a1bc3f317fca6706 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 11 Nov 2024 20:48:55 +0800 Subject: [PATCH 28/33] Use cfg instead of suppressing warning Signed-off-by: Michael X. Grey --- rclrs/src/logging/log_params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index 4b5b961e9..1a5806ce4 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -207,7 +207,7 @@ impl LogSeverity { /// This is only used by the log output handler during testing, so it will /// not be compiled when testing is not configured - #[allow(dead_code)] + #[cfg(test)] pub(crate) fn from_native(native: i32) -> Self { use crate::rcl_bindings::rcl_log_severity_t::*; match native { From 0793954802ebdb9e29595c04697c99f37e148888 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 11 Nov 2024 20:49:52 +0800 Subject: [PATCH 29/33] Fix errors in documentation Signed-off-by: Michael X. Grey --- rclrs/src/logging/logging_configuration.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index aaaabb21b..3df00ed47 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -53,7 +53,7 @@ impl Drop for LoggingLifecycle { #[cfg(test)] pub(crate) mod log_handler { //! This module provides a way to customize how log output is handled. For - //! now we are not making this a public API and are only using it for tests + //! now we are not making this a private API and are only using it for tests //! in rclrs. We can consider making it public in the future, but we should //! put more consideration into the API before doing so, and more crucially //! we need to figure out a way to process C-style formatting strings with @@ -72,9 +72,10 @@ pub(crate) mod log_handler { /// Global variable that allows a custom log handler to be set. This log /// handler will be applied throughout the entire application and cannot be - /// undone. If you want to be able to change the log handler over the - /// lifetime of your application, you should design your own custom handler - /// with an Arc> inside that allows its behavior to be modified. + /// replaced with a different custom log handler. If you want to be able to + /// change the log handler over the lifetime of your application, you should + /// design your own custom handler with an Arc> inside that allows + /// its own behavior to be modified. static LOGGING_OUTPUT_HANDLER: OnceLock = OnceLock::new(); /// Alias for an arbitrary log handler that is compatible with raw rcl types From 8c294d1bce7efa35c779232d278c344a023fe52e Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Tue, 12 Nov 2024 11:10:53 +0800 Subject: [PATCH 30/33] Add safety comment Signed-off-by: Michael X. Grey --- rclrs/src/logging/logging_configuration.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rclrs/src/logging/logging_configuration.rs b/rclrs/src/logging/logging_configuration.rs index 3df00ed47..1012608ec 100644 --- a/rclrs/src/logging/logging_configuration.rs +++ b/rclrs/src/logging/logging_configuration.rs @@ -240,6 +240,8 @@ pub(crate) mod log_handler { pub(crate) fn reset_logging_output_handler() { let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); unsafe { + // SAFETY: The global mutex is locked. No other precondition is + // required. rcutils_logging_set_output_handler(Some(rcl_logging_multiple_output_handler)); } USING_CUSTOM_HANDLER From b4e975cdb712d81a4ba9f8f2595a10c159660ac6 Mon Sep 17 00:00:00 2001 From: Grey Date: Fri, 15 Nov 2024 18:42:48 +0800 Subject: [PATCH 31/33] Use latest stable Rust CI + Fix Test Errors (#420) * Use latest stable Rust CI Signed-off-by: Michael X. Grey * Fix quotation in doc Signed-off-by: Michael X. Grey * Fix serde feature for vendored messages Signed-off-by: Michael X. Grey * Fix formatting of lists in docs Signed-off-by: Michael X. Grey * Update minimum supported Rust version based on the currently used language features Signed-off-by: Michael X. Grey * Conform to clippy style guide Signed-off-by: Michael X. Grey * Add rust toolchain as a matrix dimension Signed-off-by: Michael X. Grey * Bump declaration of minimum supported rust version Signed-off-by: Michael X. Grey * Limit the scheduled runs to once a week Signed-off-by: Michael X. Grey * Define separate stable and minimal workflows because matrix does not work with reusable actions Signed-off-by: Michael X. Grey * Reduce minimum version to 1.75 to target Noble Signed-off-by: Michael X. Grey --------- Signed-off-by: Michael X. Grey Signed-off-by: Michael X. Grey --- .../workflows/{rust.yml => rust-minimal.yml} | 9 +- .github/workflows/rust-stable.yml | 130 ++++++++++++++++++ rclrs/Cargo.toml | 7 +- rclrs/src/parameter.rs | 18 +-- rclrs/src/parameter/range.rs | 2 +- rclrs/src/subscription.rs | 10 +- rclrs/src/subscription/message_info.rs | 36 ++--- rclrs/src/wait.rs | 2 +- 8 files changed, 178 insertions(+), 36 deletions(-) rename .github/workflows/{rust.yml => rust-minimal.yml} (94%) create mode 100644 .github/workflows/rust-stable.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust-minimal.yml similarity index 94% rename from .github/workflows/rust.yml rename to .github/workflows/rust-minimal.yml index 3fd48dba0..9b1a5bb49 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust-minimal.yml @@ -1,4 +1,4 @@ -name: Rust +name: Rust Minimal on: push: @@ -6,7 +6,10 @@ on: pull_request: branches: [ main ] schedule: - - cron: '0 0 * * *' + # Run the CI at 02:22 UTC every Tuesday + # We pick an arbitrary time outside of most of the world's work hours + # to minimize the likelihood of running alongside a heavy workload. + - cron: '22 2 * * 2' env: CARGO_TERM_COLOR: always @@ -51,7 +54,7 @@ jobs: use-ros2-testing: ${{ matrix.ros_distribution == 'rolling' }} - name: Setup Rust - uses: dtolnay/rust-toolchain@1.74.0 + uses: dtolnay/rust-toolchain@1.75 with: components: clippy, rustfmt diff --git a/.github/workflows/rust-stable.yml b/.github/workflows/rust-stable.yml new file mode 100644 index 000000000..6dc395496 --- /dev/null +++ b/.github/workflows/rust-stable.yml @@ -0,0 +1,130 @@ +name: Rust Stable + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run the CI at 02:22 UTC every Tuesday + # We pick an arbitrary time outside of most of the world's work hours + # to minimize the likelihood of running alongside a heavy workload. + - cron: '22 2 * * 2' + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + ros_distribution: + - humble + - iron + - rolling + include: + # Humble Hawksbill (May 2022 - May 2027) + - docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-humble-ros-base-latest + ros_distribution: humble + ros_version: 2 + # Iron Irwini (May 2023 - November 2024) + - docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-iron-ros-base-latest + ros_distribution: iron + ros_version: 2 + # Rolling Ridley (June 2020 - Present) + - docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-rolling-ros-base-latest + ros_distribution: rolling + ros_version: 2 + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.ros_distribution == 'rolling' }} + container: + image: ${{ matrix.docker_image }} + steps: + - uses: actions/checkout@v4 + + - name: Search packages in this repository + id: list_packages + run: | + echo ::set-output name=package_list::$(colcon list --names-only) + + - name: Setup ROS environment + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: ${{ matrix.ros_distribution }} + use-ros2-testing: ${{ matrix.ros_distribution == 'rolling' }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Install colcon-cargo and colcon-ros-cargo + run: | + sudo pip3 install git+https://github.com/colcon/colcon-cargo.git + sudo pip3 install git+https://github.com/colcon/colcon-ros-cargo.git + + - name: Check formatting of Rust packages + run: | + for path in $(colcon list | awk '$3 == "(ament_cargo)" { print $2 }'); do + cd $path + rustup toolchain install nightly + cargo +nightly fmt -- --check + cd - + done + + - name: Install cargo-ament-build + run: | + cargo install --debug cargo-ament-build + + - name: Build and test + id: build + uses: ros-tooling/action-ros-ci@v0.3 + with: + package-name: ${{ steps.list_packages.outputs.package_list }} + target-ros2-distro: ${{ matrix.ros_distribution }} + vcs-repo-file-url: ros2_rust_${{ matrix.ros_distribution }}.repos + + - name: Run clippy on Rust packages + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" { print $2 }'); do + cd $path + echo "Running clippy in $path" + # Run clippy for all features except generate_docs (needed for docs.rs) + if [ "$(basename $path)" = "rclrs" ]; then + cargo clippy --all-targets -F default,dyn_msg -- -D warnings + else + cargo clippy --all-targets --all-features -- -D warnings + fi + cd - + done + + - name: Run cargo test on Rust packages + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . install/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" && $1 != "rust_pubsub" { print $2 }'); do + cd $path + echo "Running cargo test in $path" + # Run cargo test for all features except generate_docs (needed for docs.rs) + if [ "$(basename $path)" = "rclrs" ]; then + cargo test -F default,dyn_msg + elif [ "$(basename $path)" = "rosidl_runtime_rs" ]; then + cargo test -F default + else + cargo test --all-features + fi + cd - + done + + - name: Rustdoc check + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" && $1 != "rust_pubsub" { print $2 }'); do + cd $path + echo "Running rustdoc check in $path" + cargo rustdoc -- -D warnings + cd - + done diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index f489ea3f0..fe17cc990 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Esteve Fernandez ", "Nikolai Morin Parameters<'a> { /// Returns: /// * `Ok(())` if setting was successful. /// * [`Err(DeclarationError::TypeMismatch)`] if the type of the requested value is different - /// from the parameter's type. + /// from the parameter's type. /// * [`Err(DeclarationError::OutOfRange)`] if the requested value is out of the parameter's - /// range. + /// range. /// * [`Err(DeclarationError::ReadOnly)`] if the parameter is read only. pub fn set( &self, diff --git a/rclrs/src/parameter/range.rs b/rclrs/src/parameter/range.rs index 96f66d6e3..6a46d2ff0 100644 --- a/rclrs/src/parameter/range.rs +++ b/rclrs/src/parameter/range.rs @@ -32,7 +32,7 @@ impl From<()> for ParameterRanges { /// Usually only one of these ranges will be applied, but all have to be stored since: /// /// * A dynamic parameter can change its type at runtime, in which case a different range could be -/// applied. +/// applied. /// * Introspection through service calls requires all the ranges to be reported to the user. #[derive(Clone, Debug, Default)] pub struct ParameterRanges { diff --git a/rclrs/src/subscription.rs b/rclrs/src/subscription.rs index 36c241d19..fbd518c21 100644 --- a/rclrs/src/subscription.rs +++ b/rclrs/src/subscription.rs @@ -272,9 +272,7 @@ where } fn execute(&self) -> Result<(), RclrsError> { - // Immediately evaluated closure, to handle SubscriptionTakeFailed - // outside this match - match (|| { + let evaluate = || { match &mut *self.callback.lock().unwrap() { AnySubscriptionCallback::Regular(cb) => { let (msg, _) = self.take()?; @@ -302,7 +300,11 @@ where } } Ok(()) - })() { + }; + + // Immediately evaluated closure, to handle SubscriptionTakeFailed + // outside this match + match evaluate() { Err(RclrsError::RclError { code: RclReturnCode::SubscriptionTakeFailed, .. diff --git a/rclrs/src/subscription/message_info.rs b/rclrs/src/subscription/message_info.rs index 010bf28ec..1adecd3ec 100644 --- a/rclrs/src/subscription/message_info.rs +++ b/rclrs/src/subscription/message_info.rs @@ -4,26 +4,28 @@ use crate::rcl_bindings::*; /// An identifier for a publisher in the local context. /// -/// To quote the `rmw` documentation: +/// To quote the [`rmw` documentation][1]: /// /// > The identifier uniquely identifies the publisher for the local context, but -/// it will not necessarily be the same identifier given in other contexts or processes -/// for the same publisher. -/// Therefore the identifier will uniquely identify the publisher within your application -/// but may disagree about the identifier for that publisher when compared to another -/// application. -/// Even with this limitation, when combined with the publisher sequence number it can -/// uniquely identify a message within your local context. -/// Publisher GIDs generated by the RMW implementation could collide at some point, in which -/// case it is not possible to distinguish which publisher sent the message. -/// The details of how GIDs are generated are RMW implementation dependent. -/// +/// > it will not necessarily be the same identifier given in other contexts or processes +/// > for the same publisher. +/// > Therefore the identifier will uniquely identify the publisher within your application +/// > but may disagree about the identifier for that publisher when compared to another +/// > application. +/// > Even with this limitation, when combined with the publisher sequence number it can +/// > uniquely identify a message within your local context. +/// > Publisher GIDs generated by the RMW implementation could collide at some point, in which +/// > case it is not possible to distinguish which publisher sent the message. +/// > The details of how GIDs are generated are RMW implementation dependent. +/// > /// > It is possible the the RMW implementation needs to reuse a publisher GID, -/// due to running out of unique identifiers or some other constraint, in which case -/// the RMW implementation may document what happens in that case, but that -/// behavior is not defined here. -/// However, this should be avoided, if at all possible, by the RMW implementation, -/// and should be unlikely to happen in practice. +/// > due to running out of unique identifiers or some other constraint, in which case +/// > the RMW implementation may document what happens in that case, but that +/// > behavior is not defined here. +/// > However, this should be avoided, if at all possible, by the RMW implementation, +/// > and should be unlikely to happen in practice. +/// +/// [1]: https://docs.ros.org/en/rolling/p/rmw/generated/structrmw__message__info__s.html#_CPPv4N18rmw_message_info_s13publisher_gidE #[derive(Clone, Debug, PartialEq, Eq)] pub struct PublisherGid { /// Bytes identifying a publisher in the RMW implementation. diff --git a/rclrs/src/wait.rs b/rclrs/src/wait.rs index 2ef99c026..243c9d857 100644 --- a/rclrs/src/wait.rs +++ b/rclrs/src/wait.rs @@ -329,7 +329,7 @@ impl WaitSet { /// /// - Passing a wait set with no wait-able items in it will return an error. /// - The timeout must not be so large so as to overflow an `i64` with its nanosecond - /// representation, or an error will occur. + /// representation, or an error will occur. /// /// This list is not comprehensive, since further errors may occur in the `rmw` or `rcl` layers. /// From 066dd7c6e70d515268945ab6190793ef605fd887 Mon Sep 17 00:00:00 2001 From: Nathan Wiebe Neufeldt Date: Fri, 15 Nov 2024 12:07:56 -0500 Subject: [PATCH 32/33] Update vendored interface packages (#423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update rclrs vendor_interfaces.py script This updates the vendor_interfaces.py script to also vendor in the action_msgs and unique_identifier_msgs packages. The script is modified to always use a list of package names rather than hard-coding the package names everywhere. * Silence certain clippy lints on generated code In case a user enforces clippy linting on these generated packages, silence expected warnings. This is already the case in rclrs, but should be applied directly to the generated packages for the sake of downstream users. The `clippy::derive_partial_eq_without_eq` lint was already being disabled for the packages vendored by rclrs, but is now moved to the rosidl_generator_rs template instead. This is necessary since we always derive the PartialEq trait, but can't necessary derive Eq, and so don't. The `clippy::upper_case_acronyms` is new and was added to account for message type names being upper-case acrynyms, like unique_identifier_msgs::msg::UUID. * Update vendored message packages This updates the message packages vendored under the rclrs `vendor` module. The vendor_interfaces.py script was used for reproducibility. The existing packages – rcl_interfaces, rosgraph_msgs, and builtin_interfaces – are only modified in that they now use the std::ffi::c_void naming for c_void instead of the std::os::raw::c_void alias and disable certain clippy lints. The action_msgs and unique_identifier_msgs packages are newly added to enable adding action support to rclrs. action_msgs is needed for interaction with action goal info and statuses, and has a dependency on unique_identifier_msgs. --- rclrs/src/vendor/action_msgs/mod.rs | 7 + rclrs/src/vendor/action_msgs/msg.rs | 457 ++++++++++++++++++ rclrs/src/vendor/action_msgs/srv.rs | 375 ++++++++++++++ rclrs/src/vendor/builtin_interfaces/mod.rs | 2 + rclrs/src/vendor/builtin_interfaces/msg.rs | 8 +- rclrs/src/vendor/mod.rs | 3 +- rclrs/src/vendor/rcl_interfaces/mod.rs | 2 + rclrs/src/vendor/rcl_interfaces/msg.rs | 44 +- rclrs/src/vendor/rcl_interfaces/srv.rs | 97 ++-- rclrs/src/vendor/rosgraph_msgs/mod.rs | 2 + rclrs/src/vendor/rosgraph_msgs/msg.rs | 4 +- .../src/vendor/unique_identifier_msgs/mod.rs | 5 + .../src/vendor/unique_identifier_msgs/msg.rs | 125 +++++ rclrs/vendor_interfaces.py | 61 +-- rosidl_generator_rs/resource/lib.rs.em | 2 + 15 files changed, 1089 insertions(+), 105 deletions(-) create mode 100644 rclrs/src/vendor/action_msgs/mod.rs create mode 100644 rclrs/src/vendor/action_msgs/msg.rs create mode 100644 rclrs/src/vendor/action_msgs/srv.rs create mode 100644 rclrs/src/vendor/unique_identifier_msgs/mod.rs create mode 100644 rclrs/src/vendor/unique_identifier_msgs/msg.rs mode change 100644 => 100755 rclrs/vendor_interfaces.py diff --git a/rclrs/src/vendor/action_msgs/mod.rs b/rclrs/src/vendor/action_msgs/mod.rs new file mode 100644 index 000000000..f43deda46 --- /dev/null +++ b/rclrs/src/vendor/action_msgs/mod.rs @@ -0,0 +1,7 @@ +#![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] + +pub mod msg; + +pub mod srv; diff --git a/rclrs/src/vendor/action_msgs/msg.rs b/rclrs/src/vendor/action_msgs/msg.rs new file mode 100644 index 000000000..32c7b7089 --- /dev/null +++ b/rclrs/src/vendor/action_msgs/msg.rs @@ -0,0 +1,457 @@ +pub mod rmw { + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalInfo( + ) -> *const std::ffi::c_void; + } + + #[link(name = "action_msgs__rosidl_generator_c")] + extern "C" { + fn action_msgs__msg__GoalInfo__init(msg: *mut GoalInfo) -> bool; + fn action_msgs__msg__GoalInfo__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn action_msgs__msg__GoalInfo__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn action_msgs__msg__GoalInfo__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to action_msgs__msg__GoalInfo + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct GoalInfo { + pub goal_id: crate::vendor::unique_identifier_msgs::msg::rmw::UUID, + pub stamp: crate::vendor::builtin_interfaces::msg::rmw::Time, + } + + impl Default for GoalInfo { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !action_msgs__msg__GoalInfo__init(&mut msg as *mut _) { + panic!("Call to action_msgs__msg__GoalInfo__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for GoalInfo { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalInfo__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalInfo__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalInfo__Sequence__copy(in_seq, out_seq as *mut _) } + } + } + + impl rosidl_runtime_rs::Message for GoalInfo { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for GoalInfo + where + Self: Sized, + { + const TYPE_NAME: &'static str = "action_msgs/msg/GoalInfo"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalInfo() + } + } + } + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalStatus( + ) -> *const std::ffi::c_void; + } + + #[link(name = "action_msgs__rosidl_generator_c")] + extern "C" { + fn action_msgs__msg__GoalStatus__init(msg: *mut GoalStatus) -> bool; + fn action_msgs__msg__GoalStatus__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn action_msgs__msg__GoalStatus__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn action_msgs__msg__GoalStatus__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to action_msgs__msg__GoalStatus + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct GoalStatus { + pub goal_info: crate::vendor::action_msgs::msg::rmw::GoalInfo, + pub status: i8, + } + + impl GoalStatus { + /// Indicates status has not been properly set. + pub const STATUS_UNKNOWN: i8 = 0; + /// The goal has been accepted and is awaiting execution. + pub const STATUS_ACCEPTED: i8 = 1; + /// The goal is currently being executed by the action server. + pub const STATUS_EXECUTING: i8 = 2; + /// The client has requested that the goal be canceled and the action server has + /// accepted the cancel request. + pub const STATUS_CANCELING: i8 = 3; + /// The goal was achieved successfully by the action server. + pub const STATUS_SUCCEEDED: i8 = 4; + /// The goal was canceled after an external request from an action client. + pub const STATUS_CANCELED: i8 = 5; + /// The goal was terminated by the action server without an external request. + pub const STATUS_ABORTED: i8 = 6; + } + + impl Default for GoalStatus { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !action_msgs__msg__GoalStatus__init(&mut msg as *mut _) { + panic!("Call to action_msgs__msg__GoalStatus__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for GoalStatus { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatus__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatus__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatus__Sequence__copy(in_seq, out_seq as *mut _) } + } + } + + impl rosidl_runtime_rs::Message for GoalStatus { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for GoalStatus + where + Self: Sized, + { + const TYPE_NAME: &'static str = "action_msgs/msg/GoalStatus"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalStatus( + ) + } + } + } + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalStatusArray( + ) -> *const std::ffi::c_void; + } + + #[link(name = "action_msgs__rosidl_generator_c")] + extern "C" { + fn action_msgs__msg__GoalStatusArray__init(msg: *mut GoalStatusArray) -> bool; + fn action_msgs__msg__GoalStatusArray__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn action_msgs__msg__GoalStatusArray__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn action_msgs__msg__GoalStatusArray__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to action_msgs__msg__GoalStatusArray + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct GoalStatusArray { + pub status_list: + rosidl_runtime_rs::Sequence, + } + + impl Default for GoalStatusArray { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !action_msgs__msg__GoalStatusArray__init(&mut msg as *mut _) { + panic!("Call to action_msgs__msg__GoalStatusArray__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for GoalStatusArray { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatusArray__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatusArray__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__msg__GoalStatusArray__Sequence__copy(in_seq, out_seq as *mut _) } + } + } + + impl rosidl_runtime_rs::Message for GoalStatusArray { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for GoalStatusArray + where + Self: Sized, + { + const TYPE_NAME: &'static str = "action_msgs/msg/GoalStatusArray"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__action_msgs__msg__GoalStatusArray() + } + } + } +} // mod rmw + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct GoalInfo { + pub goal_id: crate::vendor::unique_identifier_msgs::msg::UUID, + pub stamp: crate::vendor::builtin_interfaces::msg::Time, +} + +impl Default for GoalInfo { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::action_msgs::msg::rmw::GoalInfo::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for GoalInfo { + type RmwMsg = crate::vendor::action_msgs::msg::rmw::GoalInfo; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_id: crate::vendor::unique_identifier_msgs::msg::UUID::into_rmw_message( + std::borrow::Cow::Owned(msg.goal_id), + ) + .into_owned(), + stamp: crate::vendor::builtin_interfaces::msg::Time::into_rmw_message( + std::borrow::Cow::Owned(msg.stamp), + ) + .into_owned(), + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_id: crate::vendor::unique_identifier_msgs::msg::UUID::into_rmw_message( + std::borrow::Cow::Borrowed(&msg.goal_id), + ) + .into_owned(), + stamp: crate::vendor::builtin_interfaces::msg::Time::into_rmw_message( + std::borrow::Cow::Borrowed(&msg.stamp), + ) + .into_owned(), + }), + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { + goal_id: crate::vendor::unique_identifier_msgs::msg::UUID::from_rmw_message( + msg.goal_id, + ), + stamp: crate::vendor::builtin_interfaces::msg::Time::from_rmw_message(msg.stamp), + } + } +} + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct GoalStatus { + pub goal_info: crate::vendor::action_msgs::msg::GoalInfo, + pub status: i8, +} + +impl GoalStatus { + /// Indicates status has not been properly set. + pub const STATUS_UNKNOWN: i8 = 0; + /// The goal has been accepted and is awaiting execution. + pub const STATUS_ACCEPTED: i8 = 1; + /// The goal is currently being executed by the action server. + pub const STATUS_EXECUTING: i8 = 2; + /// The client has requested that the goal be canceled and the action server has + /// accepted the cancel request. + pub const STATUS_CANCELING: i8 = 3; + /// The goal was achieved successfully by the action server. + pub const STATUS_SUCCEEDED: i8 = 4; + /// The goal was canceled after an external request from an action client. + pub const STATUS_CANCELED: i8 = 5; + /// The goal was terminated by the action server without an external request. + pub const STATUS_ABORTED: i8 = 6; +} + +impl Default for GoalStatus { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::action_msgs::msg::rmw::GoalStatus::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for GoalStatus { + type RmwMsg = crate::vendor::action_msgs::msg::rmw::GoalStatus; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Owned(msg.goal_info), + ) + .into_owned(), + status: msg.status, + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Borrowed(&msg.goal_info), + ) + .into_owned(), + status: msg.status, + }), + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::from_rmw_message(msg.goal_info), + status: msg.status, + } + } +} + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct GoalStatusArray { + pub status_list: Vec, +} + +impl Default for GoalStatusArray { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::action_msgs::msg::rmw::GoalStatusArray::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for GoalStatusArray { + type RmwMsg = crate::vendor::action_msgs::msg::rmw::GoalStatusArray; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + status_list: msg + .status_list + .into_iter() + .map(|elem| { + crate::vendor::action_msgs::msg::GoalStatus::into_rmw_message( + std::borrow::Cow::Owned(elem), + ) + .into_owned() + }) + .collect(), + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + status_list: msg + .status_list + .iter() + .map(|elem| { + crate::vendor::action_msgs::msg::GoalStatus::into_rmw_message( + std::borrow::Cow::Borrowed(elem), + ) + .into_owned() + }) + .collect(), + }), + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { + status_list: msg + .status_list + .into_iter() + .map(crate::vendor::action_msgs::msg::GoalStatus::from_rmw_message) + .collect(), + } + } +} diff --git a/rclrs/src/vendor/action_msgs/srv.rs b/rclrs/src/vendor/action_msgs/srv.rs new file mode 100644 index 000000000..f4cf6fe53 --- /dev/null +++ b/rclrs/src/vendor/action_msgs/srv.rs @@ -0,0 +1,375 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct CancelGoal_Request { + pub goal_info: crate::vendor::action_msgs::msg::GoalInfo, +} + +impl Default for CancelGoal_Request { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::action_msgs::srv::rmw::CancelGoal_Request::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for CancelGoal_Request { + type RmwMsg = crate::vendor::action_msgs::srv::rmw::CancelGoal_Request; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Owned(msg.goal_info), + ) + .into_owned(), + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Borrowed(&msg.goal_info), + ) + .into_owned(), + }), + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { + goal_info: crate::vendor::action_msgs::msg::GoalInfo::from_rmw_message(msg.goal_info), + } + } +} + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct CancelGoal_Response { + pub return_code: i8, + pub goals_canceling: Vec, +} + +impl CancelGoal_Response { + /// Indicates the request was accepted without any errors. + /// + /// One or more goals have transitioned to the CANCELING state. The + /// goals_canceling list is not empty. + pub const ERROR_NONE: i8 = 0; + /// Indicates the request was rejected. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_REJECTED: i8 = 1; + /// Indicates the requested goal ID does not exist. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_UNKNOWN_GOAL_ID: i8 = 2; + /// Indicates the goal is not cancelable because it is already in a terminal state. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_GOAL_TERMINATED: i8 = 3; +} + +impl Default for CancelGoal_Response { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::action_msgs::srv::rmw::CancelGoal_Response::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for CancelGoal_Response { + type RmwMsg = crate::vendor::action_msgs::srv::rmw::CancelGoal_Response; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + return_code: msg.return_code, + goals_canceling: msg + .goals_canceling + .into_iter() + .map(|elem| { + crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Owned(elem), + ) + .into_owned() + }) + .collect(), + }), + std::borrow::Cow::Borrowed(msg) => std::borrow::Cow::Owned(Self::RmwMsg { + return_code: msg.return_code, + goals_canceling: msg + .goals_canceling + .iter() + .map(|elem| { + crate::vendor::action_msgs::msg::GoalInfo::into_rmw_message( + std::borrow::Cow::Borrowed(elem), + ) + .into_owned() + }) + .collect(), + }), + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { + return_code: msg.return_code, + goals_canceling: msg + .goals_canceling + .into_iter() + .map(crate::vendor::action_msgs::msg::GoalInfo::from_rmw_message) + .collect(), + } + } +} + +#[link(name = "action_msgs__rosidl_typesupport_c")] +extern "C" { + fn rosidl_typesupport_c__get_service_type_support_handle__action_msgs__srv__CancelGoal( + ) -> *const std::ffi::c_void; +} + +// Corresponds to action_msgs__srv__CancelGoal +pub struct CancelGoal; + +impl rosidl_runtime_rs::Service for CancelGoal { + type Request = crate::vendor::action_msgs::srv::CancelGoal_Request; + type Response = crate::vendor::action_msgs::srv::CancelGoal_Response; + + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_service_type_support_handle__action_msgs__srv__CancelGoal() + } + } +} + +pub mod rmw { + + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__action_msgs__srv__CancelGoal_Request( + ) -> *const std::ffi::c_void; + } + + #[link(name = "action_msgs__rosidl_generator_c")] + extern "C" { + fn action_msgs__srv__CancelGoal_Request__init(msg: *mut CancelGoal_Request) -> bool; + fn action_msgs__srv__CancelGoal_Request__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn action_msgs__srv__CancelGoal_Request__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn action_msgs__srv__CancelGoal_Request__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to action_msgs__srv__CancelGoal_Request + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct CancelGoal_Request { + pub goal_info: crate::vendor::action_msgs::msg::rmw::GoalInfo, + } + + impl Default for CancelGoal_Request { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !action_msgs__srv__CancelGoal_Request__init(&mut msg as *mut _) { + panic!("Call to action_msgs__srv__CancelGoal_Request__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for CancelGoal_Request { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__srv__CancelGoal_Request__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__srv__CancelGoal_Request__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { + action_msgs__srv__CancelGoal_Request__Sequence__copy(in_seq, out_seq as *mut _) + } + } + } + + impl rosidl_runtime_rs::Message for CancelGoal_Request { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for CancelGoal_Request + where + Self: Sized, + { + const TYPE_NAME: &'static str = "action_msgs/srv/CancelGoal_Request"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__action_msgs__srv__CancelGoal_Request() + } + } + } + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__action_msgs__srv__CancelGoal_Response( + ) -> *const std::ffi::c_void; + } + + #[link(name = "action_msgs__rosidl_generator_c")] + extern "C" { + fn action_msgs__srv__CancelGoal_Response__init(msg: *mut CancelGoal_Response) -> bool; + fn action_msgs__srv__CancelGoal_Response__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn action_msgs__srv__CancelGoal_Response__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn action_msgs__srv__CancelGoal_Response__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to action_msgs__srv__CancelGoal_Response + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct CancelGoal_Response { + pub return_code: i8, + pub goals_canceling: + rosidl_runtime_rs::Sequence, + } + + impl CancelGoal_Response { + /// Indicates the request was accepted without any errors. + /// + /// One or more goals have transitioned to the CANCELING state. The + /// goals_canceling list is not empty. + pub const ERROR_NONE: i8 = 0; + /// Indicates the request was rejected. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_REJECTED: i8 = 1; + /// Indicates the requested goal ID does not exist. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_UNKNOWN_GOAL_ID: i8 = 2; + /// Indicates the goal is not cancelable because it is already in a terminal state. + /// + /// No goals have transitioned to the CANCELING state. The goals_canceling list is + /// empty. + pub const ERROR_GOAL_TERMINATED: i8 = 3; + } + + impl Default for CancelGoal_Response { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !action_msgs__srv__CancelGoal_Response__init(&mut msg as *mut _) { + panic!("Call to action_msgs__srv__CancelGoal_Response__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for CancelGoal_Response { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__srv__CancelGoal_Response__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { action_msgs__srv__CancelGoal_Response__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { + action_msgs__srv__CancelGoal_Response__Sequence__copy(in_seq, out_seq as *mut _) + } + } + } + + impl rosidl_runtime_rs::Message for CancelGoal_Response { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for CancelGoal_Response + where + Self: Sized, + { + const TYPE_NAME: &'static str = "action_msgs/srv/CancelGoal_Response"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__action_msgs__srv__CancelGoal_Response() + } + } + } + + #[link(name = "action_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_service_type_support_handle__action_msgs__srv__CancelGoal( + ) -> *const std::ffi::c_void; + } + + // Corresponds to action_msgs__srv__CancelGoal + pub struct CancelGoal; + + impl rosidl_runtime_rs::Service for CancelGoal { + type Request = crate::vendor::action_msgs::srv::rmw::CancelGoal_Request; + type Response = crate::vendor::action_msgs::srv::rmw::CancelGoal_Response; + + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_service_type_support_handle__action_msgs__srv__CancelGoal( + ) + } + } + } +} // mod rmw diff --git a/rclrs/src/vendor/builtin_interfaces/mod.rs b/rclrs/src/vendor/builtin_interfaces/mod.rs index a6365b3b8..4c61d56a1 100644 --- a/rclrs/src/vendor/builtin_interfaces/mod.rs +++ b/rclrs/src/vendor/builtin_interfaces/mod.rs @@ -1,3 +1,5 @@ #![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] pub mod msg; diff --git a/rclrs/src/vendor/builtin_interfaces/msg.rs b/rclrs/src/vendor/builtin_interfaces/msg.rs index 371803171..fd2ec47cd 100644 --- a/rclrs/src/vendor/builtin_interfaces/msg.rs +++ b/rclrs/src/vendor/builtin_interfaces/msg.rs @@ -5,7 +5,7 @@ pub mod rmw { #[link(name = "builtin_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__builtin_interfaces__msg__Duration( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "builtin_interfaces__rosidl_generator_c")] @@ -80,7 +80,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "builtin_interfaces/msg/Duration"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__builtin_interfaces__msg__Duration() @@ -91,7 +91,7 @@ pub mod rmw { #[link(name = "builtin_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__builtin_interfaces__msg__Time( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "builtin_interfaces__rosidl_generator_c")] @@ -166,7 +166,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "builtin_interfaces/msg/Time"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__builtin_interfaces__msg__Time( diff --git a/rclrs/src/vendor/mod.rs b/rclrs/src/vendor/mod.rs index fe87087b1..df638ee7f 100644 --- a/rclrs/src/vendor/mod.rs +++ b/rclrs/src/vendor/mod.rs @@ -1,7 +1,8 @@ //! Created by vendor_interfaces.py #![allow(dead_code)] -#![allow(clippy::derive_partial_eq_without_eq)] +pub mod action_msgs; pub mod builtin_interfaces; pub mod rcl_interfaces; pub mod rosgraph_msgs; +pub mod unique_identifier_msgs; diff --git a/rclrs/src/vendor/rcl_interfaces/mod.rs b/rclrs/src/vendor/rcl_interfaces/mod.rs index ff96b59a9..f43deda46 100644 --- a/rclrs/src/vendor/rcl_interfaces/mod.rs +++ b/rclrs/src/vendor/rcl_interfaces/mod.rs @@ -1,4 +1,6 @@ #![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] pub mod msg; diff --git a/rclrs/src/vendor/rcl_interfaces/msg.rs b/rclrs/src/vendor/rcl_interfaces/msg.rs index 39c029ae1..7f6dbcc58 100644 --- a/rclrs/src/vendor/rcl_interfaces/msg.rs +++ b/rclrs/src/vendor/rcl_interfaces/msg.rs @@ -5,7 +5,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__FloatingPointRange( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -83,7 +83,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/FloatingPointRange"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__FloatingPointRange() @@ -94,7 +94,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__IntegerRange( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -170,7 +170,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/IntegerRange"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__IntegerRange() @@ -181,7 +181,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ListParametersResult( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -260,7 +260,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ListParametersResult"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ListParametersResult() @@ -271,7 +271,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__Log( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -364,7 +364,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/Log"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__Log() @@ -375,7 +375,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterDescriptor( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -464,7 +464,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ParameterDescriptor"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterDescriptor() @@ -475,7 +475,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterEventDescriptors( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -566,7 +566,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ParameterEventDescriptors"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterEventDescriptors() @@ -577,7 +577,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterEvent( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -660,7 +660,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ParameterEvent"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterEvent() @@ -671,7 +671,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__Parameter( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -746,7 +746,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/Parameter"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__Parameter() @@ -757,7 +757,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterType( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -845,7 +845,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ParameterType"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterType() @@ -856,7 +856,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterValue( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -941,7 +941,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/ParameterValue"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__ParameterValue() @@ -952,7 +952,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__SetParametersResult( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1029,7 +1029,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/msg/SetParametersResult"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__msg__SetParametersResult() diff --git a/rclrs/src/vendor/rcl_interfaces/srv.rs b/rclrs/src/vendor/rcl_interfaces/srv.rs index 221d7eac1..3f43398a1 100644 --- a/rclrs/src/vendor/rcl_interfaces/srv.rs +++ b/rclrs/src/vendor/rcl_interfaces/srv.rs @@ -582,7 +582,7 @@ impl rosidl_runtime_rs::Message for SetParameters_Response { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__DescribeParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__DescribeParameters @@ -592,7 +592,7 @@ impl rosidl_runtime_rs::Service for DescribeParameters { type Request = crate::vendor::rcl_interfaces::srv::DescribeParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::DescribeParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__DescribeParameters() @@ -603,7 +603,7 @@ impl rosidl_runtime_rs::Service for DescribeParameters { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__GetParameters @@ -613,7 +613,7 @@ impl rosidl_runtime_rs::Service for GetParameters { type Request = crate::vendor::rcl_interfaces::srv::GetParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::GetParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameters() @@ -624,7 +624,7 @@ impl rosidl_runtime_rs::Service for GetParameters { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameterTypes( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__GetParameterTypes @@ -634,7 +634,7 @@ impl rosidl_runtime_rs::Service for GetParameterTypes { type Request = crate::vendor::rcl_interfaces::srv::GetParameterTypes_Request; type Response = crate::vendor::rcl_interfaces::srv::GetParameterTypes_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameterTypes() @@ -645,7 +645,7 @@ impl rosidl_runtime_rs::Service for GetParameterTypes { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__ListParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__ListParameters @@ -655,7 +655,7 @@ impl rosidl_runtime_rs::Service for ListParameters { type Request = crate::vendor::rcl_interfaces::srv::ListParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::ListParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__ListParameters() @@ -666,7 +666,7 @@ impl rosidl_runtime_rs::Service for ListParameters { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParametersAtomically( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__SetParametersAtomically @@ -676,7 +676,7 @@ impl rosidl_runtime_rs::Service for SetParametersAtomically { type Request = crate::vendor::rcl_interfaces::srv::SetParametersAtomically_Request; type Response = crate::vendor::rcl_interfaces::srv::SetParametersAtomically_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParametersAtomically() @@ -687,7 +687,7 @@ impl rosidl_runtime_rs::Service for SetParametersAtomically { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__SetParameters @@ -697,7 +697,7 @@ impl rosidl_runtime_rs::Service for SetParameters { type Request = crate::vendor::rcl_interfaces::srv::SetParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::SetParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParameters() @@ -706,13 +706,14 @@ impl rosidl_runtime_rs::Service for SetParameters { } pub mod rmw { + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__DescribeParameters_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -799,7 +800,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/DescribeParameters_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__DescribeParameters_Request() @@ -810,7 +811,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__DescribeParameters_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -902,7 +903,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/DescribeParameters_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__DescribeParameters_Response() @@ -913,7 +914,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameters_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -996,7 +997,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/GetParameters_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameters_Request() @@ -1007,7 +1008,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameters_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1091,7 +1092,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/GetParameters_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameters_Response() @@ -1102,7 +1103,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameterTypes_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1185,7 +1186,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/GetParameterTypes_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameterTypes_Request() @@ -1196,7 +1197,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameterTypes_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1283,7 +1284,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/GetParameterTypes_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__GetParameterTypes_Response() @@ -1294,7 +1295,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__ListParameters_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1382,7 +1383,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/ListParameters_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__ListParameters_Request() @@ -1393,7 +1394,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__ListParameters_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1476,7 +1477,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/ListParameters_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__ListParameters_Response() @@ -1487,7 +1488,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParametersAtomically_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1576,7 +1577,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/SetParametersAtomically_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParametersAtomically_Request() @@ -1587,7 +1588,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParametersAtomically_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1676,7 +1677,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/SetParametersAtomically_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParametersAtomically_Response() @@ -1687,7 +1688,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParameters_Request( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1771,7 +1772,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/SetParameters_Request"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParameters_Request() @@ -1782,7 +1783,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParameters_Response( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rcl_interfaces__rosidl_generator_c")] @@ -1867,7 +1868,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rcl_interfaces/srv/SetParameters_Response"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rcl_interfaces__srv__SetParameters_Response() @@ -1878,7 +1879,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__DescribeParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__DescribeParameters @@ -1888,7 +1889,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::DescribeParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::DescribeParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__DescribeParameters() @@ -1899,7 +1900,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__GetParameters @@ -1909,7 +1910,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::GetParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::GetParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameters() @@ -1920,7 +1921,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameterTypes( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__GetParameterTypes @@ -1930,7 +1931,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::GetParameterTypes_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::GetParameterTypes_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__GetParameterTypes() @@ -1941,7 +1942,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__ListParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__ListParameters @@ -1951,7 +1952,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::ListParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::ListParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__ListParameters() @@ -1962,7 +1963,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParametersAtomically( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__SetParametersAtomically @@ -1972,7 +1973,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::SetParametersAtomically_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::SetParametersAtomically_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParametersAtomically() @@ -1983,7 +1984,7 @@ pub mod rmw { #[link(name = "rcl_interfaces__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParameters( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } // Corresponds to rcl_interfaces__srv__SetParameters @@ -1993,7 +1994,7 @@ pub mod rmw { type Request = crate::vendor::rcl_interfaces::srv::rmw::SetParameters_Request; type Response = crate::vendor::rcl_interfaces::srv::rmw::SetParameters_Response; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_service_type_support_handle__rcl_interfaces__srv__SetParameters() diff --git a/rclrs/src/vendor/rosgraph_msgs/mod.rs b/rclrs/src/vendor/rosgraph_msgs/mod.rs index a6365b3b8..4c61d56a1 100644 --- a/rclrs/src/vendor/rosgraph_msgs/mod.rs +++ b/rclrs/src/vendor/rosgraph_msgs/mod.rs @@ -1,3 +1,5 @@ #![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] pub mod msg; diff --git a/rclrs/src/vendor/rosgraph_msgs/msg.rs b/rclrs/src/vendor/rosgraph_msgs/msg.rs index 1f4af8b70..6190bfe62 100644 --- a/rclrs/src/vendor/rosgraph_msgs/msg.rs +++ b/rclrs/src/vendor/rosgraph_msgs/msg.rs @@ -5,7 +5,7 @@ pub mod rmw { #[link(name = "rosgraph_msgs__rosidl_typesupport_c")] extern "C" { fn rosidl_typesupport_c__get_message_type_support_handle__rosgraph_msgs__msg__Clock( - ) -> *const std::os::raw::c_void; + ) -> *const std::ffi::c_void; } #[link(name = "rosgraph_msgs__rosidl_generator_c")] @@ -77,7 +77,7 @@ pub mod rmw { Self: Sized, { const TYPE_NAME: &'static str = "rosgraph_msgs/msg/Clock"; - fn get_type_support() -> *const std::os::raw::c_void { + fn get_type_support() -> *const std::ffi::c_void { // SAFETY: No preconditions for this function. unsafe { rosidl_typesupport_c__get_message_type_support_handle__rosgraph_msgs__msg__Clock() diff --git a/rclrs/src/vendor/unique_identifier_msgs/mod.rs b/rclrs/src/vendor/unique_identifier_msgs/mod.rs new file mode 100644 index 000000000..4c61d56a1 --- /dev/null +++ b/rclrs/src/vendor/unique_identifier_msgs/mod.rs @@ -0,0 +1,5 @@ +#![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] + +pub mod msg; diff --git a/rclrs/src/vendor/unique_identifier_msgs/msg.rs b/rclrs/src/vendor/unique_identifier_msgs/msg.rs new file mode 100644 index 000000000..7d6085fb1 --- /dev/null +++ b/rclrs/src/vendor/unique_identifier_msgs/msg.rs @@ -0,0 +1,125 @@ +pub mod rmw { + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; + + #[link(name = "unique_identifier_msgs__rosidl_typesupport_c")] + extern "C" { + fn rosidl_typesupport_c__get_message_type_support_handle__unique_identifier_msgs__msg__UUID( + ) -> *const std::ffi::c_void; + } + + #[link(name = "unique_identifier_msgs__rosidl_generator_c")] + extern "C" { + fn unique_identifier_msgs__msg__UUID__init(msg: *mut UUID) -> bool; + fn unique_identifier_msgs__msg__UUID__Sequence__init( + seq: *mut rosidl_runtime_rs::Sequence, + size: usize, + ) -> bool; + fn unique_identifier_msgs__msg__UUID__Sequence__fini( + seq: *mut rosidl_runtime_rs::Sequence, + ); + fn unique_identifier_msgs__msg__UUID__Sequence__copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: *mut rosidl_runtime_rs::Sequence, + ) -> bool; + } + + // Corresponds to unique_identifier_msgs__msg__UUID + #[repr(C)] + #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct UUID { + pub uuid: [u8; 16], + } + + impl Default for UUID { + fn default() -> Self { + unsafe { + let mut msg = std::mem::zeroed(); + if !unique_identifier_msgs__msg__UUID__init(&mut msg as *mut _) { + panic!("Call to unique_identifier_msgs__msg__UUID__init() failed"); + } + msg + } + } + } + + impl rosidl_runtime_rs::SequenceAlloc for UUID { + fn sequence_init(seq: &mut rosidl_runtime_rs::Sequence, size: usize) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { unique_identifier_msgs__msg__UUID__Sequence__init(seq as *mut _, size) } + } + fn sequence_fini(seq: &mut rosidl_runtime_rs::Sequence) { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { unique_identifier_msgs__msg__UUID__Sequence__fini(seq as *mut _) } + } + fn sequence_copy( + in_seq: &rosidl_runtime_rs::Sequence, + out_seq: &mut rosidl_runtime_rs::Sequence, + ) -> bool { + // SAFETY: This is safe since the pointer is guaranteed to be valid/initialized. + unsafe { unique_identifier_msgs__msg__UUID__Sequence__copy(in_seq, out_seq as *mut _) } + } + } + + impl rosidl_runtime_rs::Message for UUID { + type RmwMsg = Self; + fn into_rmw_message( + msg_cow: std::borrow::Cow<'_, Self>, + ) -> std::borrow::Cow<'_, Self::RmwMsg> { + msg_cow + } + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + msg + } + } + + impl rosidl_runtime_rs::RmwMessage for UUID + where + Self: Sized, + { + const TYPE_NAME: &'static str = "unique_identifier_msgs/msg/UUID"; + fn get_type_support() -> *const std::ffi::c_void { + // SAFETY: No preconditions for this function. + unsafe { + rosidl_typesupport_c__get_message_type_support_handle__unique_identifier_msgs__msg__UUID() + } + } + } +} // mod rmw + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct UUID { + pub uuid: [u8; 16], +} + +impl Default for UUID { + fn default() -> Self { + ::from_rmw_message( + crate::vendor::unique_identifier_msgs::msg::rmw::UUID::default(), + ) + } +} + +impl rosidl_runtime_rs::Message for UUID { + type RmwMsg = crate::vendor::unique_identifier_msgs::msg::rmw::UUID; + + fn into_rmw_message(msg_cow: std::borrow::Cow<'_, Self>) -> std::borrow::Cow<'_, Self::RmwMsg> { + match msg_cow { + std::borrow::Cow::Owned(msg) => { + std::borrow::Cow::Owned(Self::RmwMsg { uuid: msg.uuid }) + } + std::borrow::Cow::Borrowed(msg) => { + std::borrow::Cow::Owned(Self::RmwMsg { uuid: msg.uuid }) + } + } + } + + fn from_rmw_message(msg: Self::RmwMsg) -> Self { + Self { uuid: msg.uuid } + } +} diff --git a/rclrs/vendor_interfaces.py b/rclrs/vendor_interfaces.py old mode 100644 new mode 100755 index 6a0066869..8ffb4e4e0 --- a/rclrs/vendor_interfaces.py +++ b/rclrs/vendor_interfaces.py @@ -1,65 +1,70 @@ -# This script produces the `vendor` module inside `rclrs` by copying -# the generated code for the `rosgraph_msgs` and `rcl_interfaces` packages and -# its dependency `builtin_interfaces` and adjusting the submodule paths in the -# code. +#!/usr/bin/env python3 +# This script produces the `vendor` module inside `rclrs` by copying the +# generated code for the `rosgraph_msgs`, `rcl_interfaces`, and `action_msgs` +# packages and their dependencies `builtin_interfaces` and +# `unique_identifier_msgs` and adjusting the submodule paths in the code. # If these packages, or the `rosidl_generator_rs`, get changed, you can # update the `vendor` module by running this script. -# The purpose is to avoid an external dependency on `rcl_interfaces`, which -# is not published on crates.io. +# The purpose is to avoid an external dependency on these message packages, +# which are not published on crates.io. import argparse from pathlib import Path import shutil import subprocess +vendored_packages = [ + "action_msgs", + "builtin_interfaces", + "rcl_interfaces", + "rosgraph_msgs", + "unique_identifier_msgs", +] + def get_args(): - parser = argparse.ArgumentParser(description='Vendor the rcl_interfaces, builtin_interfaces and rosgraph_msgs packages into rclrs') + parser = argparse.ArgumentParser(description='Vendor interface packages into rclrs') parser.add_argument('install_base', metavar='install_base', type=Path, help='the install base (must have non-merged layout)') return parser.parse_args() -def adjust(pkg, text): - text = text.replace('builtin_interfaces::', 'crate::vendor::builtin_interfaces::') - text = text.replace('rcl_interfaces::', 'crate::vendor::rcl_interfaces::') - text = text.replace('rosgraph_msgs::', 'crate::vendor::rosgraph_msgs::') - text = text.replace('crate::msg', f'crate::vendor::{pkg}::msg') - text = text.replace('crate::srv', f'crate::vendor::{pkg}::srv') +def adjust(current_package, text): + for pkg in vendored_packages: + text = text.replace(f'{pkg}::', f'crate::vendor::{pkg}::') + text = text.replace('crate::msg', f'crate::vendor::{current_package}::msg') + text = text.replace('crate::srv', f'crate::vendor::{current_package}::srv') + text = text.replace('crate::action', f'crate::vendor::{current_package}::action') return text def copy_adjusted(pkg, src, dst): dst.write_text(adjust(pkg, src.read_text())) subprocess.check_call(['rustfmt', str(dst)]) -mod_contents = """//! Created by {} -#![allow(dead_code)] -#![allow(clippy::derive_partial_eq_without_eq)] - -pub mod builtin_interfaces; -pub mod rcl_interfaces; -pub mod rosgraph_msgs; -""".format(Path(__file__).name) - def main(): args = get_args() assert args.install_base.is_dir(), "Install base does not exist" - assert (args.install_base / 'builtin_interfaces').is_dir(), "Install base does not contain builtin_interfaces" - assert (args.install_base / 'rcl_interfaces').is_dir(), "Install base does not contain rcl_interfaces" - assert (args.install_base / 'rosgraph_msgs').is_dir(), "Install base does not contain rosgraph_msgs" + for pkg in vendored_packages: + assert (args.install_base / pkg).is_dir(), f"Install base does not contain {pkg}" rclrs_root = Path(__file__).parent vendor_dir = rclrs_root / 'src' / 'vendor' if vendor_dir.exists(): shutil.rmtree(vendor_dir) - for pkg in ['builtin_interfaces', 'rcl_interfaces', 'rosgraph_msgs']: + for pkg in vendored_packages: src = args.install_base / pkg / 'share' / pkg / 'rust' / 'src' dst = vendor_dir / pkg dst.mkdir(parents=True) copy_adjusted(pkg, src / 'msg.rs', dst / 'msg.rs') if (src / 'srv.rs').is_file(): copy_adjusted(pkg, src / 'srv.rs', dst / 'srv.rs') + if (src / 'action.rs').is_file(): + copy_adjusted(pkg, src / 'action.rs', dst / 'action.rs') copy_adjusted(pkg, src / 'lib.rs', dst / 'mod.rs') # Rename lib.rs to mod.rs - (vendor_dir / 'mod.rs').write_text(mod_contents) - + mod_contents = "//! Created by {}\n".format(Path(__file__).name) + mod_contents += "#![allow(dead_code)]\n" + mod_contents += "\n" + for pkg in vendored_packages: + mod_contents += f"pub mod {pkg};\n" + (vendor_dir / 'mod.rs').write_text(mod_contents) if __name__ == '__main__': main() diff --git a/rosidl_generator_rs/resource/lib.rs.em b/rosidl_generator_rs/resource/lib.rs.em index 79a0e1def..1ef77924c 100644 --- a/rosidl_generator_rs/resource/lib.rs.em +++ b/rosidl_generator_rs/resource/lib.rs.em @@ -1,4 +1,6 @@ #![allow(non_camel_case_types)] +#![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::upper_case_acronyms)] @[if len(msg_specs) > 0]@ pub mod msg; From c287fb25db873be3c34a9e76eb11a389ccb13cff Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Mon, 18 Nov 2024 12:33:58 +0800 Subject: [PATCH 33/33] Change API to use throttle Signed-off-by: Michael X. Grey --- rclrs/src/logging.rs | 15 +++++++-------- rclrs/src/logging/log_params.rs | 31 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/rclrs/src/logging.rs b/rclrs/src/logging.rs index a76648202..7bbfb423e 100644 --- a/rclrs/src/logging.rs +++ b/rclrs/src/logging.rs @@ -42,7 +42,7 @@ pub use logger::*; /// node /// .error() /// .skip_first() -/// .interval(Duration::from_millis(1000)), +/// .throttle(Duration::from_millis(1000)), /// "Noisy error that we expect the first time" /// ); /// @@ -50,7 +50,7 @@ pub use logger::*; /// log!( /// node /// .info() -/// .interval(Duration::from_millis(1000)) +/// .throttle(Duration::from_millis(1000)) /// .only_if(count % 10 == 0), /// "Manually constructed LogConditions", /// ); @@ -124,10 +124,10 @@ macro_rules! log { $crate::LogOccurrence::All => (), } - // If we have a throttle interval then check if we're inside or outside + // If we have a throttle duration then check if we're inside or outside // of that interval. - let interval = params.get_interval(); - if interval > std::time::Duration::ZERO { + let throttle = params.get_throttle(); + if throttle > std::time::Duration::ZERO { static LAST_LOG_TIME: OnceLock> = OnceLock::new(); let last_log_time = LAST_LOG_TIME.get_or_init(|| { Mutex::new(std::time::SystemTime::now()) @@ -136,11 +136,10 @@ macro_rules! log { if !first_time { let now = std::time::SystemTime::now(); let mut previous = last_log_time.lock().unwrap(); - if now >= *previous + interval { + if now >= *previous + throttle { *previous = now; } else { - // We are still inside the throttle interval, so just exit - // here. + // We are still inside the throttle interval, so just exit here. return; } } diff --git a/rclrs/src/logging/log_params.rs b/rclrs/src/logging/log_params.rs index 1a5806ce4..79197cd98 100644 --- a/rclrs/src/logging/log_params.rs +++ b/rclrs/src/logging/log_params.rs @@ -10,13 +10,12 @@ pub struct LogParams<'a> { severity: LogSeverity, /// Specify when a log message should be published (See[`LoggingOccurrence`] above) occurs: LogOccurrence, - /// Specify the publication interval of the message. A value of ZERO (0) indicates that the - /// message should be published every time, otherwise, the message will only be published once - /// the specified interval has elapsed. - /// This field is typically used to limit the output from high-frequency messages, e.g. instead - /// of publishing a log message every 10 milliseconds, the `publish_interval` can be configured - /// such that the message is published every 10 seconds. - interval: Duration, + /// Specify a publication throttling interval for the message. A value of ZERO (0) indicates that the + /// message should not be throttled. Otherwise, the message will only be published once the specified + /// interval has elapsed. This field is typically used to limit the output from high-frequency messages, + /// e.g. if `log!(logger.throttle(Duration::from_secs(1)), "message");` is called every 10ms, it will + /// nevertheless only be published once per second. + throttle: Duration, /// The log message will only published if the specified expression evaluates to true only_if: bool, } @@ -28,7 +27,7 @@ impl<'a> LogParams<'a> { logger_name, severity: Default::default(), occurs: Default::default(), - interval: Duration::new(0, 0), + throttle: Duration::new(0, 0), only_if: true, } } @@ -48,9 +47,9 @@ impl<'a> LogParams<'a> { self.occurs } - /// Get the interval - pub fn get_interval(&self) -> Duration { - self.interval + /// Get the throttle interval duration + pub fn get_throttle(&self) -> Duration { + self.throttle } /// Get the arbitrary filter set by the user @@ -88,14 +87,14 @@ pub trait ToLogParams<'a>: Sized { params } - /// Set an interval during which this log will not publish. A value of zero - /// will never block the message from being published, and this is the + /// Set a throttling interval during which this log will not publish. A value + /// of zero will never block the message from being published, and this is the /// default behavior. /// /// A negative duration is not valid, but will be treated as a zero duration. - fn interval(self, interval: Duration) -> LogParams<'a> { + fn throttle(self, throttle: Duration) -> LogParams<'a> { let mut params = self.to_log_params(); - params.interval = interval; + params.throttle = throttle; params } @@ -103,7 +102,7 @@ pub trait ToLogParams<'a>: Sized { /// this function. /// /// Other factors may prevent the log from being published if a `true` is - /// passed in, such as `ToLogParams::interval` or `ToLogParams::once` + /// passed in, such as `ToLogParams::throttle` or `ToLogParams::once` /// filtering the log. fn only_if(self, only_if: bool) -> LogParams<'a> { let mut params = self.to_log_params();