From 23827935de3dccb0f818657d879655862676a7cb Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Mon, 13 Apr 2026 17:13:23 +0200 Subject: [PATCH 1/2] build(vendor): add test-only gating metadata for vendored messages Signed-off-by: Esteve Fernandez --- .github/workflows/rust-minimal.yml | 8 +++- .github/workflows/rust-stable.yml | 8 +++- rclrs/Cargo.toml | 1 + rclrs/src/action.rs | 2 +- rclrs/src/action/action_client/goal_client.rs | 3 ++ rclrs/src/client.rs | 11 ++++- .../src/dynamic_message/dynamic_publisher.rs | 2 +- .../src/dynamic_message/message_structure.rs | 2 +- rclrs/src/lib.rs | 20 +++++++-- rclrs/src/node.rs | 44 ++++++++++++++++++- rclrs/src/publisher.rs | 2 +- rclrs/src/publisher/loaned_message.rs | 2 +- rclrs/src/service.rs | 2 +- rclrs/src/subscription.rs | 2 +- .../into_async_subscription_callback.rs | 2 +- .../into_node_subscription_callback.rs | 2 +- .../subscription/readonly_loaned_message.rs | 2 +- rclrs/src/vendor/mod.rs | 2 + rclrs/src/worker.rs | 8 +++- rclrs/vendor_interfaces.py | 27 +++++++----- 20 files changed, 118 insertions(+), 34 deletions(-) diff --git a/.github/workflows/rust-minimal.yml b/.github/workflows/rust-minimal.yml index 1a9e8175d..8fe03e61b 100644 --- a/.github/workflows/rust-minimal.yml +++ b/.github/workflows/rust-minimal.yml @@ -117,7 +117,7 @@ jobs: echo "Running cargo test in $path" # Run cargo test for all features except use_ros_shim (needed for docs.rs) if [ "$(basename $path)" = "rclrs" ]; then - cargo test -F default,serde + cargo test -F default,serde,vendored_test_interfaces else cargo test --all-features fi @@ -131,6 +131,10 @@ jobs: 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 + if [ "$(basename $path)" = "rclrs" ]; then + cargo rustdoc -F vendored_test_interfaces -- -D warnings + else + cargo rustdoc -- -D warnings + fi cd - done diff --git a/.github/workflows/rust-stable.yml b/.github/workflows/rust-stable.yml index 707e2b7e4..a115ebdea 100644 --- a/.github/workflows/rust-stable.yml +++ b/.github/workflows/rust-stable.yml @@ -117,7 +117,7 @@ jobs: echo "Running cargo test in $path" # Run cargo test for all features except use_ros_shim (needed for docs.rs) if [ "$(basename $path)" = "rclrs" ]; then - cargo test -F default,serde + cargo test -F default,serde,vendored_test_interfaces else cargo test --all-features fi @@ -131,6 +131,10 @@ jobs: 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 + if [ "$(basename $path)" = "rclrs" ]; then + cargo rustdoc -F vendored_test_interfaces -- -D warnings + else + cargo rustdoc -- -D warnings + fi cd - done diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index 8692e19ab..b7347ffdd 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -68,6 +68,7 @@ rustflags = "0.1" [features] default = [] serde = ["dep:serde", "dep:serde-big-array", "rosidl_runtime_rs/serde"] +vendored_test_interfaces = [] # This feature is solely for the purpose of being able to generate documetation without a ROS installation # The only intended usage of this feature is for docs.rs builders to work, and is not intended to be used by end users use_ros_shim = ["rosidl_runtime_rs/use_ros_shim"] diff --git a/rclrs/src/action.rs b/rclrs/src/action.rs index e2359914c..21318a30e 100644 --- a/rclrs/src/action.rs +++ b/rclrs/src/action.rs @@ -253,7 +253,7 @@ fn empty_goal_status_array() -> DropGuard { ) } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use crate::{ vendor::example_interfaces::action::{ diff --git a/rclrs/src/action/action_client/goal_client.rs b/rclrs/src/action/action_client/goal_client.rs index 61d2773ff..016b983a3 100644 --- a/rclrs/src/action/action_client/goal_client.rs +++ b/rclrs/src/action/action_client/goal_client.rs @@ -95,6 +95,8 @@ impl GoalClient { /// # Example /// /// ``` +/// # #[cfg(feature = "vendored_test_interfaces")] +/// # { /// use rclrs::*; /// use crate::rclrs::vendor::example_interfaces::action::Fibonacci; /// use futures::StreamExt; @@ -118,6 +120,7 @@ impl GoalClient { /// } /// } /// } +/// # } /// ``` pub struct GoalClientStream { stream_map: StreamMap> + Send>>>, diff --git a/rclrs/src/client.rs b/rclrs/src/client.rs index e5fb10875..521f3e32f 100644 --- a/rclrs/src/client.rs +++ b/rclrs/src/client.rs @@ -162,6 +162,8 @@ where /// Define an `async fn` whose arguments are compatible with one of the above /// signatures and which returns a `()` (a.k.a. nothing). /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let node = Context::default() @@ -176,6 +178,7 @@ where /// let request = test_msgs::srv::Empty_Request::default(); /// let promise = client.call_then_async(&request, print_hello)?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// ## 2. Function that returns an `async { ... }` @@ -190,6 +193,8 @@ where /// ### `fn` /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # use std::future::Future; @@ -210,6 +215,7 @@ where /// &request, /// print_greeting)?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// ### Closure @@ -219,6 +225,8 @@ where /// is also the most powerful option. /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let node = Context::default() @@ -236,6 +244,7 @@ where /// } /// })?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` pub fn call_then_async<'a, Req, Args>( &self, @@ -606,7 +615,7 @@ impl Drop for ClientHandle { // they are running in. Therefore, this type can be safely sent to another thread. unsafe impl Send for rcl_client_t {} -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::{test_helpers::*, vendor::test_msgs}; diff --git a/rclrs/src/dynamic_message/dynamic_publisher.rs b/rclrs/src/dynamic_message/dynamic_publisher.rs index 22cbf3268..54bdbd6b5 100644 --- a/rclrs/src/dynamic_message/dynamic_publisher.rs +++ b/rclrs/src/dynamic_message/dynamic_publisher.rs @@ -134,7 +134,7 @@ impl DynamicPublisherState { } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/dynamic_message/message_structure.rs b/rclrs/src/dynamic_message/message_structure.rs index a28b38ab9..136bb1d8f 100644 --- a/rclrs/src/dynamic_message/message_structure.rs +++ b/rclrs/src/dynamic_message/message_structure.rs @@ -295,7 +295,7 @@ impl MessageStructure { } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::dynamic_message::*; diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 9db4767f9..7d1254f81 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -30,7 +30,9 @@ //! [`Executor`], and then a [`Node`]. Create whatever primitives you need, and //! then tell the [`Executor`] to spin: //! -//! ```no_run +//! ``` +//! # #[cfg(feature = "vendored_test_interfaces")] +//! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; //! @@ -47,13 +49,16 @@ //! //! executor.spin(SpinOptions::default()).first_error()?; //! # Ok::<(), RclrsError>(()) +//! # } //! ``` //! //! If your callback needs to interact with some state data, consider using a //! [`Worker`], especially if that state data needs to be shared with other //! callbacks: //! -//! ```no_run +//! ``` +//! # #[cfg(feature = "vendored_test_interfaces")] +//! # { //! # use rclrs::*; //! # //! # let context = Context::default_from_env()?; @@ -84,6 +89,7 @@ //! //! # executor.spin(SpinOptions::default()).first_error()?; //! # Ok::<(), RclrsError>(()) +//! # } //! ``` //! //! # Parameters @@ -97,7 +103,9 @@ //! - Read-only parameters do not allow you to modify them after they have been declared. //! //! The following is a simple example of using a mandatory parameter: -//! ```no_run +//! ``` +//! # #[cfg(feature = "vendored_test_interfaces")] +//! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; //! use std::sync::Arc; @@ -119,6 +127,7 @@ //! //! executor.spin(SpinOptions::default()).first_error()?; //! # Ok::<(), RclrsError>(()) +//! # } //! ``` //! //! # Logging @@ -127,7 +136,9 @@ //! ergonomic Rust API. [`ToLogParams`] can be used to dictate how logging is //! performed. //! -//! ```no_run +//! ``` +//! # #[cfg(feature = "vendored_test_interfaces")] +//! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; //! use std::time::Duration; @@ -176,6 +187,7 @@ //! ); //! executor.spin(SpinOptions::default()).first_error()?; //! # Ok::<(), RclrsError>(()) +//! # } //! ``` mod action; diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index a48ad42fd..770417fd8 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -273,6 +273,8 @@ impl NodeState { /// /// In some cases the payload type can be inferred by Rust: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// let executor = Context::default().create_basic_executor(); @@ -300,7 +302,6 @@ impl NodeState { /// /// ``` /// # use rclrs::*; - /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); /// # let node = executor.create_node("my_node").unwrap(); /// let worker = node.create_worker::(String::new()); @@ -308,6 +309,8 @@ impl NodeState { /// /// The data given to the worker can be any custom data type: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -342,6 +345,8 @@ impl NodeState { /// /// Pass in only the service name for the `options` argument to use all default client options: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); /// # let node = executor.create_node("my_node").unwrap(); @@ -356,6 +361,8 @@ impl NodeState { /// client options: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); /// # let node = executor.create_node("my_node").unwrap(); @@ -423,6 +430,8 @@ impl NodeState { /// /// Pass in only the topic name for the `options` argument to use all default publisher options: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); /// # let node = executor.create_node("my_node").unwrap(); @@ -437,6 +446,8 @@ impl NodeState { /// publisher options: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let executor = Context::default().create_basic_executor(); @@ -453,6 +464,7 @@ impl NodeState { /// .reliable() /// ) /// .unwrap(); + /// # } /// ``` /// pub fn create_publisher<'a, T>( @@ -483,6 +495,7 @@ impl NodeState { /// .keep_last(100) /// ) /// .unwrap(); + /// ``` pub fn create_dynamic_publisher<'a>( self: &Arc, topic_type: MessageTypeName, @@ -510,6 +523,8 @@ impl NodeState { /// /// Pass in only the service name for the `options` argument to use all default service options: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let executor = Context::default().create_basic_executor(); @@ -527,6 +542,8 @@ impl NodeState { /// service options: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let executor = Context::default().create_basic_executor(); @@ -566,6 +583,8 @@ impl NodeState { /// multiple simultaneous runs of the callback. For example: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -586,12 +605,15 @@ impl NodeState { /// } /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// To share the internal state outside of the callback you will need to /// wrap it in [`Arc`] or `Arc>` and then clone the [`Arc`] before /// capturing it in the closure: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -622,6 +644,7 @@ impl NodeState { /// } /// }); /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// In general, when you need to manage some state within a blocking service, @@ -686,6 +709,8 @@ impl NodeState { /// This allows one async service to share state data across multiple workers. /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -721,6 +746,7 @@ impl NodeState { /// } /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` pub fn create_async_service<'a, T, Args>( self: &Arc, @@ -758,6 +784,8 @@ impl NodeState { /// /// Pass in only the topic name for the `options` argument to use all default subscription options: /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let executor = Context::default().create_basic_executor(); @@ -774,6 +802,8 @@ impl NodeState { /// subscription options: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; /// # let executor = Context::default().create_basic_executor(); @@ -794,6 +824,7 @@ impl NodeState { /// println!("Received message!"); /// }, /// ); + /// # } /// ``` /// /// # Subscription Callbacks @@ -817,6 +848,8 @@ impl NodeState { /// simultaneous runs of the callback. For example: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -833,6 +866,7 @@ impl NodeState { /// }, /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// To share the internal state outside of the callback you will need to @@ -840,6 +874,8 @@ impl NodeState { /// the [`Arc`] before capturing it in the closure: /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -865,6 +901,7 @@ impl NodeState { /// } /// }); /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// You can change the subscription at any time by calling @@ -1071,6 +1108,8 @@ impl NodeState { /// multiple workers. /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -1105,6 +1144,7 @@ impl NodeState { /// } /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// You can change the subscription at any time by calling @@ -1542,7 +1582,7 @@ pub(crate) unsafe fn call_string_getter_with_rcl_node( // they are running in. Therefore, this type can be safely sent to another thread. unsafe impl Send for rcl_node_t {} -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use crate::{test_helpers::*, *}; diff --git a/rclrs/src/publisher.rs b/rclrs/src/publisher.rs index 6a5717356..fcd708272 100644 --- a/rclrs/src/publisher.rs +++ b/rclrs/src/publisher.rs @@ -342,7 +342,7 @@ impl<'a, T: Message> MessageCow<'a, T> for &'a T { } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/publisher/loaned_message.rs b/rclrs/src/publisher/loaned_message.rs index 4a39d7001..24ed28da8 100644 --- a/rclrs/src/publisher/loaned_message.rs +++ b/rclrs/src/publisher/loaned_message.rs @@ -92,7 +92,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; diff --git a/rclrs/src/service.rs b/rclrs/src/service.rs index f5e43baef..11d87cfd0 100644 --- a/rclrs/src/service.rs +++ b/rclrs/src/service.rs @@ -480,7 +480,7 @@ impl From for rcl_service_introspection_state_e { } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/subscription.rs b/rclrs/src/subscription.rs index be195b8f2..5b71c5ccf 100644 --- a/rclrs/src/subscription.rs +++ b/rclrs/src/subscription.rs @@ -438,7 +438,7 @@ impl Drop for SubscriptionHandle { } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; use crate::{test_helpers::*, vendor::test_msgs::msg}; diff --git a/rclrs/src/subscription/into_async_subscription_callback.rs b/rclrs/src/subscription/into_async_subscription_callback.rs index 334fc0a9a..5e6bff605 100644 --- a/rclrs/src/subscription/into_async_subscription_callback.rs +++ b/rclrs/src/subscription/into_async_subscription_callback.rs @@ -99,7 +99,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; diff --git a/rclrs/src/subscription/into_node_subscription_callback.rs b/rclrs/src/subscription/into_node_subscription_callback.rs index 007ee137e..58ba2c81a 100644 --- a/rclrs/src/subscription/into_node_subscription_callback.rs +++ b/rclrs/src/subscription/into_node_subscription_callback.rs @@ -127,7 +127,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; diff --git a/rclrs/src/subscription/readonly_loaned_message.rs b/rclrs/src/subscription/readonly_loaned_message.rs index db891176e..b0c1de9fc 100644 --- a/rclrs/src/subscription/readonly_loaned_message.rs +++ b/rclrs/src/subscription/readonly_loaned_message.rs @@ -52,7 +52,7 @@ unsafe impl Send for ReadOnlyLoanedMessage where T: Message {} // SAFETY: This type has no interior mutability, in fact it has no mutability at all. unsafe impl Sync for ReadOnlyLoanedMessage where T: Message {} -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use super::*; diff --git a/rclrs/src/vendor/mod.rs b/rclrs/src/vendor/mod.rs index 28d3bfd56..fcae0d784 100644 --- a/rclrs/src/vendor/mod.rs +++ b/rclrs/src/vendor/mod.rs @@ -4,8 +4,10 @@ pub mod action_msgs; pub mod builtin_interfaces; +#[cfg(feature = "vendored_test_interfaces")] pub mod example_interfaces; pub mod rcl_interfaces; pub mod rosgraph_msgs; +#[cfg(feature = "vendored_test_interfaces")] pub mod test_msgs; pub mod unique_identifier_msgs; diff --git a/rclrs/src/worker.rs b/rclrs/src/worker.rs index 6e3901298..b029c4f6b 100644 --- a/rclrs/src/worker.rs +++ b/rclrs/src/worker.rs @@ -233,6 +233,8 @@ impl WorkerState { /// payload by setting the first argument of the callback to `&mut Payload`. /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -254,6 +256,7 @@ impl WorkerState { /// }, /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` pub fn create_subscription<'a, T, Args>( &self, @@ -383,6 +386,8 @@ impl WorkerState { /// payload by setting the first argument of the callback to `&mut Payload`. /// /// ``` + /// # #[cfg(feature = "vendored_test_interfaces")] + /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; /// # let executor = Context::default().create_basic_executor(); @@ -416,6 +421,7 @@ impl WorkerState { /// } /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` pub fn create_service<'a, T, Args>( &self, @@ -756,7 +762,7 @@ impl WorkScope for Worker { type Payload = Payload; } -#[cfg(test)] +#[cfg(all(test, feature = "vendored_test_interfaces"))] mod tests { use crate::{ vendor::test_msgs::{ diff --git a/rclrs/vendor_interfaces.py b/rclrs/vendor_interfaces.py index 29b4becc8..c92383b70 100755 --- a/rclrs/vendor_interfaces.py +++ b/rclrs/vendor_interfaces.py @@ -14,13 +14,13 @@ import subprocess vendored_packages = [ - "action_msgs", - "builtin_interfaces", - "example_interfaces", - "rcl_interfaces", - "rosgraph_msgs", - "test_msgs", - "unique_identifier_msgs", + ("action_msgs", False), + ("builtin_interfaces", False), + ("example_interfaces", True), + ("rcl_interfaces", False), + ("rosgraph_msgs", False), + ("test_msgs", True), + ("unique_identifier_msgs", False), ] def get_args(): @@ -30,7 +30,7 @@ def get_args(): return parser.parse_args() def adjust(current_package, text): - for pkg in vendored_packages: + 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') @@ -44,13 +44,14 @@ def copy_adjusted(pkg, src, dst): def main(): args = get_args() assert args.install_base.is_dir(), "Install base does not exist" - for pkg in vendored_packages: - assert (args.install_base / pkg).is_dir(), f"Install base does not contain {pkg}" + 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 vendored_packages: + for pkg, test_only in vendored_packages: src = args.install_base / pkg / 'share' / pkg / 'rust' / 'src' dst = vendor_dir / pkg dst.mkdir(parents=True) @@ -65,7 +66,9 @@ def main(): mod_contents += "#![allow(dead_code)]\n" mod_contents += "#![allow(missing_docs)]\n" mod_contents += "\n" - for pkg in vendored_packages: + for pkg, test_only in vendored_packages: + if test_only: + mod_contents += "#[cfg(feature = \"vendored_test_interfaces\")]\n" mod_contents += f"pub mod {pkg};\n" (vendor_dir / 'mod.rs').write_text(mod_contents) From d58d0859b2d3b203e7d760968aa0fdfbc250b5ce Mon Sep 17 00:00:00 2001 From: Esteve Fernandez Date: Tue, 14 Apr 2026 18:04:42 +0200 Subject: [PATCH 2/2] use cfg(test) and cfg(doctest) Signed-off-by: Esteve Fernandez --- .github/workflows/rust-minimal.yml | 8 +--- .github/workflows/rust-stable.yml | 8 +--- rclrs/Cargo.toml | 1 - rclrs/src/action.rs | 2 +- rclrs/src/action/action_client/goal_client.rs | 2 +- rclrs/src/client.rs | 8 ++-- .../src/dynamic_message/dynamic_publisher.rs | 2 +- .../src/dynamic_message/message_structure.rs | 2 +- rclrs/src/lib.rs | 8 ++-- rclrs/src/node.rs | 42 +++++++++++-------- rclrs/src/publisher.rs | 2 +- rclrs/src/publisher/loaned_message.rs | 2 +- rclrs/src/service.rs | 2 +- rclrs/src/subscription.rs | 2 +- .../into_async_subscription_callback.rs | 2 +- .../into_node_subscription_callback.rs | 2 +- .../subscription/readonly_loaned_message.rs | 2 +- rclrs/src/vendor/mod.rs | 4 +- rclrs/src/worker.rs | 6 +-- rclrs/vendor_interfaces.py | 5 +-- 20 files changed, 55 insertions(+), 57 deletions(-) diff --git a/.github/workflows/rust-minimal.yml b/.github/workflows/rust-minimal.yml index 8fe03e61b..1a9e8175d 100644 --- a/.github/workflows/rust-minimal.yml +++ b/.github/workflows/rust-minimal.yml @@ -117,7 +117,7 @@ jobs: echo "Running cargo test in $path" # Run cargo test for all features except use_ros_shim (needed for docs.rs) if [ "$(basename $path)" = "rclrs" ]; then - cargo test -F default,serde,vendored_test_interfaces + cargo test -F default,serde else cargo test --all-features fi @@ -131,10 +131,6 @@ jobs: 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" - if [ "$(basename $path)" = "rclrs" ]; then - cargo rustdoc -F vendored_test_interfaces -- -D warnings - else - cargo rustdoc -- -D warnings - fi + cargo rustdoc -- -D warnings cd - done diff --git a/.github/workflows/rust-stable.yml b/.github/workflows/rust-stable.yml index a115ebdea..707e2b7e4 100644 --- a/.github/workflows/rust-stable.yml +++ b/.github/workflows/rust-stable.yml @@ -117,7 +117,7 @@ jobs: echo "Running cargo test in $path" # Run cargo test for all features except use_ros_shim (needed for docs.rs) if [ "$(basename $path)" = "rclrs" ]; then - cargo test -F default,serde,vendored_test_interfaces + cargo test -F default,serde else cargo test --all-features fi @@ -131,10 +131,6 @@ jobs: 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" - if [ "$(basename $path)" = "rclrs" ]; then - cargo rustdoc -F vendored_test_interfaces -- -D warnings - else - cargo rustdoc -- -D warnings - fi + cargo rustdoc -- -D warnings cd - done diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index b7347ffdd..8692e19ab 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -68,7 +68,6 @@ rustflags = "0.1" [features] default = [] serde = ["dep:serde", "dep:serde-big-array", "rosidl_runtime_rs/serde"] -vendored_test_interfaces = [] # This feature is solely for the purpose of being able to generate documetation without a ROS installation # The only intended usage of this feature is for docs.rs builders to work, and is not intended to be used by end users use_ros_shim = ["rosidl_runtime_rs/use_ros_shim"] diff --git a/rclrs/src/action.rs b/rclrs/src/action.rs index 21318a30e..e2359914c 100644 --- a/rclrs/src/action.rs +++ b/rclrs/src/action.rs @@ -253,7 +253,7 @@ fn empty_goal_status_array() -> DropGuard { ) } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use crate::{ vendor::example_interfaces::action::{ diff --git a/rclrs/src/action/action_client/goal_client.rs b/rclrs/src/action/action_client/goal_client.rs index 016b983a3..c5171229c 100644 --- a/rclrs/src/action/action_client/goal_client.rs +++ b/rclrs/src/action/action_client/goal_client.rs @@ -95,7 +95,7 @@ impl GoalClient { /// # Example /// /// ``` -/// # #[cfg(feature = "vendored_test_interfaces")] +/// # #[cfg(doctest)] /// # { /// use rclrs::*; /// use crate::rclrs::vendor::example_interfaces::action::Fibonacci; diff --git a/rclrs/src/client.rs b/rclrs/src/client.rs index 521f3e32f..07d0719f1 100644 --- a/rclrs/src/client.rs +++ b/rclrs/src/client.rs @@ -162,7 +162,7 @@ where /// Define an `async fn` whose arguments are compatible with one of the above /// signatures and which returns a `()` (a.k.a. nothing). /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -193,7 +193,7 @@ where /// ### `fn` /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -225,7 +225,7 @@ where /// is also the most powerful option. /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -615,7 +615,7 @@ impl Drop for ClientHandle { // they are running in. Therefore, this type can be safely sent to another thread. unsafe impl Send for rcl_client_t {} -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::{test_helpers::*, vendor::test_msgs}; diff --git a/rclrs/src/dynamic_message/dynamic_publisher.rs b/rclrs/src/dynamic_message/dynamic_publisher.rs index 54bdbd6b5..22cbf3268 100644 --- a/rclrs/src/dynamic_message/dynamic_publisher.rs +++ b/rclrs/src/dynamic_message/dynamic_publisher.rs @@ -134,7 +134,7 @@ impl DynamicPublisherState { } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/dynamic_message/message_structure.rs b/rclrs/src/dynamic_message/message_structure.rs index 136bb1d8f..a28b38ab9 100644 --- a/rclrs/src/dynamic_message/message_structure.rs +++ b/rclrs/src/dynamic_message/message_structure.rs @@ -295,7 +295,7 @@ impl MessageStructure { } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::dynamic_message::*; diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 7d1254f81..5a9876abe 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -31,7 +31,7 @@ //! then tell the [`Executor`] to spin: //! //! ``` -//! # #[cfg(feature = "vendored_test_interfaces")] +//! # #[cfg(doctest)] //! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; @@ -57,7 +57,7 @@ //! callbacks: //! //! ``` -//! # #[cfg(feature = "vendored_test_interfaces")] +//! # #[cfg(doctest)] //! # { //! # use rclrs::*; //! # @@ -104,7 +104,7 @@ //! //! The following is a simple example of using a mandatory parameter: //! ``` -//! # #[cfg(feature = "vendored_test_interfaces")] +//! # #[cfg(doctest)] //! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; @@ -137,7 +137,7 @@ //! performed. //! //! ``` -//! # #[cfg(feature = "vendored_test_interfaces")] +//! # #[cfg(doctest)] //! # { //! use rclrs::*; //! # use crate::rclrs::vendor::example_interfaces; diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 770417fd8..44581826e 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -273,7 +273,7 @@ impl NodeState { /// /// In some cases the payload type can be inferred by Rust: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -289,6 +289,7 @@ impl NodeState { /// } /// )?; /// # Ok::<(), RclrsError>(()) + /// # } /// ``` /// /// If the compiler complains about not knowing the payload type, you can @@ -309,7 +310,7 @@ impl NodeState { /// /// The data given to the worker can be any custom data type: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -323,6 +324,7 @@ impl NodeState { /// } /// /// let worker = node.create_worker(MyNodeData::default()); + /// # } /// ``` /// /// In the above example, `addition_client` and `result_publisher` can be @@ -345,7 +347,7 @@ impl NodeState { /// /// Pass in only the service name for the `options` argument to use all default client options: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); @@ -355,13 +357,14 @@ impl NodeState { /// "my_service" /// ) /// .unwrap(); + /// # } /// ``` /// /// Take advantage of the [`IntoPrimitiveOptions`] API to easily build up the /// client options: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); @@ -373,6 +376,7 @@ impl NodeState { /// .transient_local() /// ) /// .unwrap(); + /// # } /// ``` /// /// Any quality of service options that you explicitly specify will override @@ -430,7 +434,7 @@ impl NodeState { /// /// Pass in only the topic name for the `options` argument to use all default publisher options: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # let executor = Context::default().create_basic_executor(); @@ -440,13 +444,14 @@ impl NodeState { /// "my_topic" /// ) /// .unwrap(); + /// # } /// ``` /// /// Take advantage of the [`IntoPrimitiveOptions`] API to easily build up the /// publisher options: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -523,7 +528,7 @@ impl NodeState { /// /// Pass in only the service name for the `options` argument to use all default service options: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -536,13 +541,14 @@ impl NodeState { /// test_msgs::srv::Empty_Response::default() /// }, /// ); + /// # } /// ``` /// /// Take advantage of the [`IntoPrimitiveOptions`] API to easily build up the /// service options: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -557,6 +563,7 @@ impl NodeState { /// test_msgs::srv::Empty_Response::default() /// }, /// ); + /// # } /// ``` /// /// Any quality of service options that you explicitly specify will override @@ -583,7 +590,7 @@ impl NodeState { /// multiple simultaneous runs of the callback. For example: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -612,7 +619,7 @@ impl NodeState { /// capturing it in the closure: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -709,7 +716,7 @@ impl NodeState { /// This allows one async service to share state data across multiple workers. /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -784,7 +791,7 @@ impl NodeState { /// /// Pass in only the topic name for the `options` argument to use all default subscription options: /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -796,13 +803,14 @@ impl NodeState { /// println!("Received message!"); /// }, /// ); + /// # } /// ``` /// /// Take advantage of the [`IntoPrimitiveOptions`] API to easily build up the /// subscription options: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::test_msgs; @@ -848,7 +856,7 @@ impl NodeState { /// simultaneous runs of the callback. For example: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -874,7 +882,7 @@ impl NodeState { /// the [`Arc`] before capturing it in the closure: /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -1108,7 +1116,7 @@ impl NodeState { /// multiple workers. /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -1582,7 +1590,7 @@ pub(crate) unsafe fn call_string_getter_with_rcl_node( // they are running in. Therefore, this type can be safely sent to another thread. unsafe impl Send for rcl_node_t {} -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use crate::{test_helpers::*, *}; diff --git a/rclrs/src/publisher.rs b/rclrs/src/publisher.rs index fcd708272..6a5717356 100644 --- a/rclrs/src/publisher.rs +++ b/rclrs/src/publisher.rs @@ -342,7 +342,7 @@ impl<'a, T: Message> MessageCow<'a, T> for &'a T { } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/publisher/loaned_message.rs b/rclrs/src/publisher/loaned_message.rs index 24ed28da8..4a39d7001 100644 --- a/rclrs/src/publisher/loaned_message.rs +++ b/rclrs/src/publisher/loaned_message.rs @@ -92,7 +92,7 @@ where } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; diff --git a/rclrs/src/service.rs b/rclrs/src/service.rs index 11d87cfd0..f5e43baef 100644 --- a/rclrs/src/service.rs +++ b/rclrs/src/service.rs @@ -480,7 +480,7 @@ impl From for rcl_service_introspection_state_e { } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::test_helpers::*; diff --git a/rclrs/src/subscription.rs b/rclrs/src/subscription.rs index 5b71c5ccf..be195b8f2 100644 --- a/rclrs/src/subscription.rs +++ b/rclrs/src/subscription.rs @@ -438,7 +438,7 @@ impl Drop for SubscriptionHandle { } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; use crate::{test_helpers::*, vendor::test_msgs::msg}; diff --git a/rclrs/src/subscription/into_async_subscription_callback.rs b/rclrs/src/subscription/into_async_subscription_callback.rs index 5e6bff605..334fc0a9a 100644 --- a/rclrs/src/subscription/into_async_subscription_callback.rs +++ b/rclrs/src/subscription/into_async_subscription_callback.rs @@ -99,7 +99,7 @@ where } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; diff --git a/rclrs/src/subscription/into_node_subscription_callback.rs b/rclrs/src/subscription/into_node_subscription_callback.rs index 58ba2c81a..007ee137e 100644 --- a/rclrs/src/subscription/into_node_subscription_callback.rs +++ b/rclrs/src/subscription/into_node_subscription_callback.rs @@ -127,7 +127,7 @@ where } } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; diff --git a/rclrs/src/subscription/readonly_loaned_message.rs b/rclrs/src/subscription/readonly_loaned_message.rs index b0c1de9fc..db891176e 100644 --- a/rclrs/src/subscription/readonly_loaned_message.rs +++ b/rclrs/src/subscription/readonly_loaned_message.rs @@ -52,7 +52,7 @@ unsafe impl Send for ReadOnlyLoanedMessage where T: Message {} // SAFETY: This type has no interior mutability, in fact it has no mutability at all. unsafe impl Sync for ReadOnlyLoanedMessage where T: Message {} -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use super::*; diff --git a/rclrs/src/vendor/mod.rs b/rclrs/src/vendor/mod.rs index fcae0d784..e639e833c 100644 --- a/rclrs/src/vendor/mod.rs +++ b/rclrs/src/vendor/mod.rs @@ -4,10 +4,10 @@ pub mod action_msgs; pub mod builtin_interfaces; -#[cfg(feature = "vendored_test_interfaces")] +#[cfg(any(test, doctest))] pub mod example_interfaces; pub mod rcl_interfaces; pub mod rosgraph_msgs; -#[cfg(feature = "vendored_test_interfaces")] +#[cfg(any(test, doctest))] pub mod test_msgs; pub mod unique_identifier_msgs; diff --git a/rclrs/src/worker.rs b/rclrs/src/worker.rs index b029c4f6b..907fcad08 100644 --- a/rclrs/src/worker.rs +++ b/rclrs/src/worker.rs @@ -233,7 +233,7 @@ impl WorkerState { /// payload by setting the first argument of the callback to `&mut Payload`. /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -386,7 +386,7 @@ impl WorkerState { /// payload by setting the first argument of the callback to `&mut Payload`. /// /// ``` - /// # #[cfg(feature = "vendored_test_interfaces")] + /// # #[cfg(doctest)] /// # { /// # use rclrs::*; /// # use crate::rclrs::vendor::example_interfaces; @@ -762,7 +762,7 @@ impl WorkScope for Worker { type Payload = Payload; } -#[cfg(all(test, feature = "vendored_test_interfaces"))] +#[cfg(test)] mod tests { use crate::{ vendor::test_msgs::{ diff --git a/rclrs/vendor_interfaces.py b/rclrs/vendor_interfaces.py index c92383b70..c4c731b24 100755 --- a/rclrs/vendor_interfaces.py +++ b/rclrs/vendor_interfaces.py @@ -45,8 +45,7 @@ def main(): args = get_args() assert args.install_base.is_dir(), "Install base does not exist" for pkg, _ in vendored_packages: - assert (args.install_base / pkg).is_dir(), - f"Install base does not contain {pkg}" + 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(): @@ -68,7 +67,7 @@ def main(): mod_contents += "\n" for pkg, test_only in vendored_packages: if test_only: - mod_contents += "#[cfg(feature = \"vendored_test_interfaces\")]\n" + mod_contents += "#[cfg(any(test, doctest))]\n" mod_contents += f"pub mod {pkg};\n" (vendor_dir / 'mod.rs').write_text(mod_contents)