Skip to content

Draft: Derive macro for StructuredParameters #516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"rclrs",
"rclrs_proc_macros",
]
resolver = "2"
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Comment on lines +12 to +13
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was unable to build with cargo without these changes.
With colcon everything was working fine.
These can probably be reverted, but then development with rust-analyzer doesn't really work

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only ran into this issue after adding the crate rclrs_proc_macros and enabling proc-macro=True in the cargo config

&& rm -rf /var/lib/apt/lists/*

# Install Rust
Expand All @@ -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"]
2 changes: 2 additions & 0 deletions rclrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions rclrs/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
<depend>builtin_interfaces</depend>
<depend>rcl_interfaces</depend>
<depend>rosgraph_msgs</depend>
<depend>rosidl_default_generators</depend>

<test_depend>test_msgs</test_depend>
<test_depend>example_interfaces</test_depend>
<depend>test_msgs</depend>
<depend>example_interfaces</depend>
Comment on lines +22 to +25
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are tinkering steps to resolve cargo build without colcon.
I'll remove these


<export>
<build_type>ament_cargo</build_type>
Expand Down
2 changes: 2 additions & 0 deletions rclrs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,5 @@ pub use time::*;
use time_source::*;
pub use wait_set::*;
pub use worker::*;

pub use rclrs_proc_macros::StructuredParameters;
94 changes: 94 additions & 0 deletions rclrs/src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,38 @@ impl ParameterInterface {
}
}

pub trait StructuredParameters: Sized {
fn declare_structured(
node: &crate::NodeState,
name: &str,
) -> core::result::Result<Self, crate::DeclarationError>;
}

impl<T: crate::ParameterVariant> StructuredParameters for crate::MandatoryParameter<T> {
fn declare_structured(
node: &crate::NodeState,
name: &str,
) -> core::result::Result<crate::MandatoryParameter<T>, crate::DeclarationError> {
node.declare_parameter(name).mandatory()
}
}
impl<T: crate::ParameterVariant> StructuredParameters for crate::OptionalParameter<T> {
fn declare_structured(
node: &crate::NodeState,
name: &str,
) -> core::result::Result<crate::OptionalParameter<T>, crate::DeclarationError> {
node.declare_parameter(name).optional()
}
}
impl<T: crate::ParameterVariant> StructuredParameters for crate::ReadOnlyParameter<T> {
fn declare_structured(
node: &crate::NodeState,
name: &str,
) -> core::result::Result<crate::ReadOnlyParameter<T>, crate::DeclarationError> {
node.declare_parameter(name).read_only()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -1410,4 +1442,66 @@ mod tests {
.optional()
.unwrap();
}

use crate as rclrs;
use rclrs_proc_macros::StructuredParameters;

#[derive(StructuredParameters, Debug)]
struct SimpleStructuredParameters {
_mandatory: MandatoryParameter<f64>,
_optional: OptionalParameter<f64>,
_readonly: ReadOnlyParameter<f64>,
}

#[test]
fn test_simple_structured_parameters() {
let args: Vec<String> = [
"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<Arc<str>>,
}

#[test]
fn test_nested_structured_parameters() {
let args: Vec<String> = [
"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();
}
}
18 changes: 18 additions & 0 deletions rclrs_proc_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name= "rclrs_proc_macros"
version = "0.0.1"
authors = ["Balthasar Schüss <[email protected]>"]
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"
67 changes: 67 additions & 0 deletions rclrs_proc_macros/src/impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

pub(crate) fn derive_struct_parameters(input: DeriveInput) -> syn::Result<TokenStream> {
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<T: rclrs::StructuredParameters>() {}
#(
assert_parameter::<#field_types>();
)*
};
}

impl rclrs::StructuredParameters for #ident {
fn declare_structured(node: &rclrs::NodeState, name: &str) -> core::result::Result<Self, rclrs::DeclarationError> {
core::result::Result::Ok(Self{ #(#args)*})
}
}
);
syn::Result::Ok(result)
}
11 changes: 11 additions & 0 deletions rclrs_proc_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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()
}
Loading