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()
+}