Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3094ff8
Structured parameters initial draft
BCSol Aug 19, 2025
c76651c
Default values for parameters
BCSol Aug 28, 2025
cba5837
Make declare_parameters public
BCSol Aug 30, 2025
80ed497
Move NodeState from parameter options to enabled default derivation
BCSol Aug 30, 2025
2ba7748
Remove parameter struct
BCSol Aug 30, 2025
2561549
Add support for description
BCSol Aug 30, 2025
d9bcf97
Add some documentation
BCSol Aug 30, 2025
0b7ac44
Add module doc with example
BCSol Aug 30, 2025
2d0be3d
Fix format
BCSol Aug 30, 2025
073322c
Revert changes to build
BCSol Aug 30, 2025
c686ba5
Merge remote-tracking branch 'forked/main'
BCSol Aug 30, 2025
4a07678
Fix error message punctuation
BCSol Aug 30, 2025
620d09d
cargo fmt nightly
BCSol Aug 30, 2025
ba3d941
Remove docstring test
balthasarschuess Sep 2, 2025
6729752
Only require macros at test time
balthasarschuess Sep 2, 2025
b07fd43
Add setup to bashrc
balthasarschuess Sep 19, 2025
cd3a8ea
Merge branch 'ros2-rust:main' into main
balthasarschuess Sep 19, 2025
e760adf
Fix bug in Dockerfile
balthasarschuess Sep 19, 2025
36de62a
Fix cargo fmt
balthasarschuess Sep 19, 2025
4665263
Follow standard macro package naming convention
balthasarschuess Sep 19, 2025
5f93232
Prepare macro to implement enum derive
balthasarschuess Sep 19, 2025
0879517
Reduce visibility of struct macro implementation
balthasarschuess Sep 19, 2025
74cf511
Extend macro with more builder options
balthasarschuess Sep 19, 2025
6616988
support discimator function
balthasarschuess Sep 19, 2025
e688869
Fix bug
balthasarschuess Sep 19, 2025
22a022a
Add support for ranges
balthasarschuess Sep 19, 2025
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-macros",
]
resolver = "2"
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,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 "\nsource /opt/ros/${ROS_DISTRO}/setup.sh" >> /root/.bashrc
WORKDIR /workspace


COPY src/ros2_rust/docker/rosidl_rust_entrypoint.sh /
ENTRYPOINT ["/rosidl_rust_entrypoint.sh"]
CMD ["/bin/bash"]
18 changes: 18 additions & 0 deletions rclrs-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name= "rclrs-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"
162 changes: 162 additions & 0 deletions rclrs-macros/src/impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DeriveInput, Expr};

pub(crate) fn derive_structured_parameters(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident;

match input.data {
syn::Data::Struct(ref s) => derive_structured_parameters_struct(ident, s),
_ => {
return syn::Result::Err(syn::Error::new_spanned(
ident,
"StructuredParameters trait can only be derived for structs",
));
}
}
}

fn derive_structured_parameters_struct(
ident: proc_macro2::Ident,
struct_: &syn::DataStruct,
) -> syn::Result<TokenStream> {
let fields = &struct_.fields;

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 mut default: Option<Expr> = None;
let mut description: Option<Expr> = None;
let mut constraints: Option<Expr> = None;
let mut ignore_override = false;
let mut discard_mismatching_prior_value = false;
let mut discriminate: Option<Expr> = None;
let mut range: Option<Expr> = None;

for attr in &f.attrs {
if attr.path().is_ident("param") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
default = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("description") {
description = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("constraints") {
constraints = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("ignore_override") {
ignore_override = true;
Ok(())
} else if meta.path.is_ident("discard_mismatching_prior_value") {
discard_mismatching_prior_value = true;
Ok(())
} else if meta.path.is_ident("discriminate") {
discriminate = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("range") {
range = Some(meta.value()?.parse()?);
Ok(())
} else {
let err = format!("Unknown key: {:?}", &meta.path.get_ident());
syn::Result::Err(syn::Error::new_spanned(meta.path, err))
}
})?;
}
}

let default = match default {
Some(expr) => quote! {Some(#expr)},
None => quote! {None},
};
let description = match description {
Some(expr) => quote! {#expr},
None => quote! {""},
};
let constraints = match constraints {
Some(expr) => quote! {#expr},
None => quote! {""},
};
let discriminate = match discriminate {
Some(expr) => quote! {
core::option::Option::Some(Box::new(#expr))
},
None => quote! {core::option::Option::None},
};
let range = match range {
Some(expr) => quote! {
core::option::Option::Some(#expr)
},
None => quote! {core::option::Option::None},
};

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,
"Only PathType attributes are supported",
));
}
};
let r = quote! {
#ident : #field_type::declare_structured(
node,
&{match name {
"" => #ident_str.to_string(),
prefix => [prefix, ".", #ident_str].concat(),
}},
#default,
#description,
#constraints,
#ignore_override,
#discard_mismatching_prior_value,
#discriminate,
#range,
)?,
};
args.push(r);
}

let result = quote!(
impl #ident {
const _ASSERT_PARAMETER: fn() = || {
fn assert_parameter<T: rclrs::parameter::structured::StructuredParameters>() {}
#(
assert_parameter::<#field_types>();
)*
};
}
impl rclrs::parameter::structured::StructuredParametersMeta<rclrs::parameter::structured::DefaultForbidden> for #ident {
fn declare_structured_(
node: &rclrs::NodeState,
name: &str,
default: core::option::Option<rclrs::parameter::structured::DefaultForbidden>,
description: impl core::convert::Into<std::sync::Arc<str>>,
constraints: impl core::convert::Into<std::sync::Arc<str>>,
ignore_override: bool,
discard_mismatching_prior_value: bool,
discriminate: core::option::Option<Box<
dyn core::ops::FnOnce(rclrs::AvailableValues<rclrs::parameter::structured::DefaultForbidden>)
-> core::option::Option<rclrs::parameter::structured::DefaultForbidden
>>>,
range: core::option::Option<<rclrs::structured::DefaultForbidden as rclrs::ParameterVariant>::Range>,
)
-> core::result::Result<Self, rclrs::DeclarationError> {
core::result::Result::Ok(Self{ #(#args)*})
}

}
impl rclrs::StructuredParameters for #ident {}
);
syn::Result::Ok(result)
}
11 changes: 11 additions & 0 deletions rclrs-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, attributes(param))]
pub fn derive_structured_parameters(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
r#impl::derive_structured_parameters(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
2 changes: 2 additions & 0 deletions rclrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ rosidl_runtime_rs = "0.4"
serde = { version = "1", optional = true, features = ["derive"] }
serde-big-array = { version = "0.5.1", optional = true }


[dev-dependencies]
# Needed for e.g. writing yaml files in tests
tempfile = "3.3.0"
# Needed for parameter service tests
tokio = { version = "1", features = ["rt", "time", "macros"] }
rclrs-macros = {path = "../rclrs-macros"}

[build-dependencies]
# Needed for uploading documentation to docs.rs
Expand Down
5 changes: 4 additions & 1 deletion rclrs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ pub use error::*;
pub use executor::*;
pub use logging::*;
pub use node::*;
pub use parameter::*;
pub use parameter::{
structured::{StructuredParameters, StructuredParametersMeta},
*,
};
pub use publisher::*;
pub use qos::*;
pub use rcl_bindings::rmw_request_id_t;
Expand Down
1 change: 1 addition & 0 deletions rclrs/src/parameter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod override_map;
mod range;
mod service;
pub mod structured;
mod value;

pub(crate) use override_map::*;
Expand Down
Loading
Loading