Skip to content
Open
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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ members = [
"rclrs",
]
resolver = "2"

# rclrs-macros is deliberately not a member. rclrs depends on it by version, which
# colcon-ros-cargo resolves to the installed copy, the same arrangement rclrs already has with
# rosidl_runtime_rs. A path dependency would be simpler here, but `cargo ament-build` copies
# Cargo.toml into the install prefix verbatim, where a relative path no longer resolves and the
# installed crate becomes unusable to anything that depends on it.
#
# Since it is not a member, colcon would not discover it on its own.
[workspace.metadata.colcon]
additional-packages = ["rclrs-macros"]
33 changes: 33 additions & 0 deletions rclrs-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Its own workspace, and not a member of the one in the parent directory, so that building
# rclrs-macros does not have to resolve rclrs's dependencies. rclrs depends on this crate by
# version, which colcon-ros-cargo resolves to the installed copy, the same arrangement rclrs
# already has with rosidl_runtime_rs.
[workspace]

[package]
name = "rclrs-macros"
# These are spelled out rather than inherited from [workspace.package]. `cargo ament-build`
# installs this crate by copying Cargo.toml into its own prefix, where there is no workspace root
# to inherit from, and cargo then refuses to parse the copy.
version = "0.7.0"
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
edition = "2021"
license = "Apache-2.0"
description = "Procedural macros for rclrs, a ROS 2 client library for Rust"
rust-version = "1.85"

[lib]
path = "src/lib.rs"
proc-macro = true

# Please keep the list of dependencies alphabetically sorted,
# and also state why each dependency is needed.
[dependencies]
# Needed to build up the generated code
proc-macro2 = "1.0"

# Needed to quasi-quote the generated code
quote = "1.0"

# Needed to parse the annotated struct and the contents of its attributes
syn = { version = "2.0", features = ["full", "extra-traits"] }
16 changes: 16 additions & 0 deletions rclrs-macros/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<?xml-model
href="http://download.ros.org/schema/package_format3.xsd"
schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>rclrs-macros</name>
<version>0.7.0</version>
<description>Procedural macros for rclrs, the Rust client library.</description>
<author email="mathieudavid@mathieudavid.org">Mathieu David</author>
<maintainer email="mathieudavid@mathieudavid.org">Mathieu David</maintainer>
<license>Apache License 2.0</license>

<export>
<build_type>ament_cargo</build_type>
</export>
</package>
56 changes: 56 additions & 0 deletions rclrs-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Procedural macros for [rclrs](https://docs.rs/rclrs).
//!
//! Everything here is re-exported from `rclrs`, so depend on that crate rather than on this one.

use proc_macro::TokenStream;

mod parameter_set;

/// Declares a struct's fields as a group of ROS 2 parameters.
///
/// See the `rclrs::ParameterSet` trait for the full description, and
/// `rclrs::NodeState::declare_parameters` for how to declare the result on a node.
///
/// # Struct attributes
///
/// * `#[parameters(namespace = "drive")]`: declare this set's parameters under `drive` when it
/// is declared at the top level. Defaults to the node's root, so that the struct mirrors the
/// shape of a parameter YAML file.
/// * `#[parameters(default = expr)]`: take the default value of every field from `expr`, which
/// must evaluate to `Self`, e.g. `Self::default()`. A field with its own
/// `#[param(default = ...)]` keeps that default.
/// * `#[parameters(handles = MyHandles)]`: name of the generated handles struct. Defaults to
/// the struct's own name with `Params` appended.
///
/// # Field attributes
///
/// * `#[param(default = expr)]`: the default value, in the field's own type. An array literal
/// is converted element by element, so `["a", "b"]` works for a `Vec<String>`.
/// * `#[param(description = "...")]`: descriptor description. Defaults to the doc comment.
/// * `#[param(constraints = "...")]`: descriptor constraints. Defaults to whatever the field's
/// type says about itself.
/// * `#[param(range = 0.0..=10.0, step = 0.5)]`: valid range, in the field's own units, or in
/// the units the conversion stores when `convert` is given. Any Rust range works.
/// * `#[param(convert = expr)]`: a `ParameterConversion<T>` saying how the field is represented,
/// for a type that does not implement `ParameterVariant` and cannot, such as one belonging to
/// another crate.
/// * `#[param(read_only)]`: declare as a read-only parameter.
/// * `#[param(validate = expr)]`: `fn(&T) -> Result<(), String>` run before a value is applied.
/// * `#[param(on_change = expr)]`: `fn(&T)`, or `fn(Option<&T>)` for an `Option` field, run
/// after a value has been applied.
/// * `#[param(discriminate = expr)]`: choose the initial value from those available.
/// * `#[param(ignore_override)]`, `#[param(discard_mismatching_prior_value)]`: as on the
/// parameter builder.
/// * `#[param(rename = "name")]`: the ROS 2 name of this parameter, if not the field name.
/// * `#[param(flatten)]`: for a nested set: declare its parameters directly under this set's
/// namespace, without one of its own.
/// * `#[param(skip)]`: not a parameter. Filled in with `Default::default()` when the set is
/// read back.
#[proc_macro_derive(ParameterSet, attributes(parameters, param))]
pub fn derive_parameter_set(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
match parameter_set::expand(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
Loading
Loading