From 3094ff8af60361f4e19b375080aaa4a38f5e0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balthasar=20Sch=C3=BCss?= Date: Wed, 20 Aug 2025 01:00:01 +0200 Subject: [PATCH] Structured parameters initial draft --- Cargo.toml | 1 + Dockerfile | 4 ++ rclrs/Cargo.toml | 2 + rclrs/package.xml | 5 +- rclrs/src/lib.rs | 2 + rclrs/src/parameter.rs | 94 +++++++++++++++++++++++++++++++++++ rclrs_proc_macros/Cargo.toml | 18 +++++++ rclrs_proc_macros/src/impl.rs | 67 +++++++++++++++++++++++++ rclrs_proc_macros/src/lib.rs | 11 ++++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 rclrs_proc_macros/Cargo.toml create mode 100644 rclrs_proc_macros/src/impl.rs create mode 100644 rclrs_proc_macros/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index afac85b2..0fb0e9a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ "rclrs", + "rclrs_proc_macros", ] resolver = "2" diff --git a/Dockerfile b/Dockerfile index 07cb6970..0d500fcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,8 @@ RUN apt-get update && apt-get install -y \ libclang-dev \ tmux \ python3-pip \ + ros-humble-test-msgs \ + ros-humble-example-interfaces \ && rm -rf /var/lib/apt/lists/* # Install Rust @@ -22,8 +24,10 @@ COPY src/ros2_rust/docker/rosidl_rust_setup.sh / RUN ./rosidl_rust_setup.sh RUN mkdir -p /workspace && echo "Did you forget to mount the repository into the Docker container?" > /workspace/HELLO.txt +RUN echo -e "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" WORKDIR /workspace + COPY src/ros2_rust/docker/rosidl_rust_entrypoint.sh / ENTRYPOINT ["/rosidl_rust_entrypoint.sh"] CMD ["/bin/bash"] diff --git a/rclrs/Cargo.toml b/rclrs/Cargo.toml index ee4d948c..262ad79f 100644 --- a/rclrs/Cargo.toml +++ b/rclrs/Cargo.toml @@ -36,6 +36,8 @@ rosidl_runtime_rs = "0.4" serde = { version = "1", optional = true, features = ["derive"] } serde-big-array = { version = "0.5.1", optional = true } +rclrs_proc_macros = {path = "../rclrs_proc_macros"} + [dev-dependencies] # Needed for e.g. writing yaml files in tests tempfile = "3.3.0" diff --git a/rclrs/package.xml b/rclrs/package.xml index d947bbf8..8d91b2de 100644 --- a/rclrs/package.xml +++ b/rclrs/package.xml @@ -19,9 +19,10 @@ builtin_interfaces rcl_interfaces rosgraph_msgs + rosidl_default_generators - test_msgs - example_interfaces + test_msgs + example_interfaces ament_cargo diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs index 3952682a..f3c4f915 100644 --- a/rclrs/src/lib.rs +++ b/rclrs/src/lib.rs @@ -223,3 +223,5 @@ pub use time::*; use time_source::*; pub use wait_set::*; pub use worker::*; + +pub use rclrs_proc_macros::StructuredParameters; diff --git a/rclrs/src/parameter.rs b/rclrs/src/parameter.rs index fe9fa091..699a0d41 100644 --- a/rclrs/src/parameter.rs +++ b/rclrs/src/parameter.rs @@ -871,6 +871,38 @@ impl ParameterInterface { } } +pub trait StructuredParameters: Sized { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result; +} + +impl StructuredParameters for crate::MandatoryParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).mandatory() + } +} +impl StructuredParameters for crate::OptionalParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).optional() + } +} +impl StructuredParameters for crate::ReadOnlyParameter { + fn declare_structured( + node: &crate::NodeState, + name: &str, + ) -> core::result::Result, crate::DeclarationError> { + node.declare_parameter(name).read_only() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1410,4 +1442,66 @@ mod tests { .optional() .unwrap(); } + + use crate as rclrs; + use rclrs_proc_macros::StructuredParameters; + + #[derive(StructuredParameters, Debug)] + struct SimpleStructuredParameters { + _mandatory: MandatoryParameter, + _optional: OptionalParameter, + _readonly: ReadOnlyParameter, + } + + #[test] + fn test_simple_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "_mandatory:=1.0", + "-p", + "_optional:=1.0", + "-p", + "_readonly:=1.0", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(NodeOptions::new("test")).unwrap(); + let _params = SimpleStructuredParameters::declare_structured(&node, "").unwrap(); + } + + #[derive(StructuredParameters, Debug)] + struct NestedStructuredParameters { + _simple: SimpleStructuredParameters, + _mandatory: MandatoryParameter>, + } + + #[test] + fn test_nested_structured_parameters() { + let args: Vec = [ + "test", + "--ros-args", + "-p", + "nested._simple._mandatory:=1.0", + "-p", + "nested._simple._optional:=1.0", + "-p", + "nested._simple._readonly:=1.0", + "-p", + "nested._mandatory:=foo", + ] + .into_iter() + .map(str::to_string) + .collect(); + + let context = crate::Context::new(args, InitOptions::default()).unwrap(); + let exec = context.create_basic_executor(); + let node = exec.create_node(NodeOptions::new("test")).unwrap(); + let _params = NestedStructuredParameters::declare_structured(&node, "nested").unwrap(); + } } diff --git a/rclrs_proc_macros/Cargo.toml b/rclrs_proc_macros/Cargo.toml new file mode 100644 index 00000000..51ee68ba --- /dev/null +++ b/rclrs_proc_macros/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name= "rclrs_proc_macros" +version = "0.0.1" +authors = ["Balthasar Schüss "] +edition = "2021" +license = "Apache-2.0" +description = "A rust library providing proc macros for rclrs" +rust-version = "1.75" + + +[lib] +path = "src/lib.rs" +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" diff --git a/rclrs_proc_macros/src/impl.rs b/rclrs_proc_macros/src/impl.rs new file mode 100644 index 00000000..7607669b --- /dev/null +++ b/rclrs_proc_macros/src/impl.rs @@ -0,0 +1,67 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::DeriveInput; + +pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result { + let ident = input.ident; + + let fields = match input.data { + syn::Data::Struct(ref s) => &s.fields, + _ => { + return syn::Result::Err(syn::Error::new_spanned( + ident, + "StrucutredParameter trait can only be derived for structs", + )); + } + }; + + let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect(); + + let mut args = Vec::new(); + for f in fields { + let ident = f.ident.as_ref().unwrap(); + let ident_str = syn::LitStr::new(&f.ident.as_ref().unwrap().to_string(), ident.span()); + let field_type = match &f.ty { + syn::Type::Path(p) => { + let mut p = p.path.clone(); + for segment in &mut p.segments { + segment.arguments = syn::PathArguments::None; + } + p + } + e => { + return syn::Result::Err(syn::Error::new_spanned( + e, + "attribute can only be path type", + )); + } + }; + let r = quote! { + #ident : #field_type::declare_structured( + node, &{match name { + "" => #ident_str.to_string(), + prefix => [prefix, ".", #ident_str].concat(), + } + })?, + }; + args.push(r); + } + + let result = quote!( + impl #ident { + const _ASSERT_PARAMETER: fn() = || { + fn assert_parameter() {} + #( + assert_parameter::<#field_types>(); + )* + }; + } + + impl rclrs::StructuredParameters for #ident { + fn declare_structured(node: &rclrs::NodeState, name: &str) -> core::result::Result { + core::result::Result::Ok(Self{ #(#args)*}) + } + } + ); + syn::Result::Ok(result) +} diff --git a/rclrs_proc_macros/src/lib.rs b/rclrs_proc_macros/src/lib.rs new file mode 100644 index 00000000..fcac0543 --- /dev/null +++ b/rclrs_proc_macros/src/lib.rs @@ -0,0 +1,11 @@ +mod r#impl; +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(StructuredParameters)] +pub fn derive_struct_parameters(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + r#impl::derive_struct_parameters(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +}