Skip to content

Commit 137ca29

Browse files
committed
Add detailed guide for building ros2_rust packages, extend contributing doc
1 parent 29d4177 commit 137ca29

File tree

3 files changed

+259
-75
lines changed

3 files changed

+259
-75
lines changed

README.md

Lines changed: 26 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
ROS2 for Rust
2-
=============
3-
4-
Build status
5-
------------
1+
ROS 2 for Rust
2+
==============
63

74
| Target | Status |
85
|----------|--------|
@@ -11,94 +8,50 @@ Build status
118
Introduction
129
------------
1310

14-
This is a set of projects (bindings, code generator, examples and more) that enables developers to write ROS2
15-
applications in Rust.
11+
This is a set of projects (the `rclrs` client library, code generator, examples and more) that
12+
enables developers to write ROS 2 applications in Rust.
1613

17-
Features
18-
--------
14+
Features and limitations
15+
------------------------
1916

2017
The current set of features include:
21-
- Generation of all builtin ROS types
18+
- Message generation
2219
- Support for publishers and subscriptions
2320
- Tunable QoS settings
2421

25-
What's missing?
26-
---------------
27-
28-
Lots of things!
29-
- Component nodes
30-
- Clients and services
31-
- Tests
32-
- Documentation
22+
Lots of things are still missing however, see the [issue list](https://github.com/ros2-rust/ros2_rust/issues) for an overview.
3323

34-
### Limitations
35-
36-
- The `rclrs` interface is very limited for now and might not be idiomatic yet, any help and suggestion on the interface would be greatly appreciated
37-
- Due to the current ROS2 support of non-default clients, packages containing definitions of messages used in Rust crates must be present in the current workspace; otherwise message crates generation won't be triggered
24+
The client library is still rapidly evolving, and there are no stability guarantees.
3825

3926
Sounds great, how can I try this out?
4027
-------------------------------------
4128

42-
Here, the Foxy distribution of ROS 2 is used, but newer distributions can be used by simply replacing 'foxy' with the distribution name.
43-
44-
```
45-
# First, make sure to have ROS 2 and vcstool installed (alternatively, install vcstool with pip):
46-
# sudo apt install ros-foxy-desktop ros-foxy-test-interface-files python3-vcstool libclang-dev clang
47-
# Install the colcon-cargo and colcon-ros-cargo plugins
48-
pip install git+https://github.com/colcon/colcon-cargo.git git+https://github.com/colcon/colcon-ros-cargo.git
49-
# Install the cargo-ament-build plugin
50-
cargo install cargo-ament-build
29+
In a nutshell, the steps to get started are:
5130

52-
# In your workspace directory (ideally an empty one), run
53-
mkdir src
31+
```shell
32+
mkdir -p workspace/src && cd workspace
5433
git clone https://github.com/ros2-rust/ros2_rust.git src/ros2_rust
34+
docker build -t ros2_rust_dev - < src/ros2_rust/Dockerfile
35+
docker run --rm -it --volume $(pwd):/workspace ros2_rust_dev /bin/bash
36+
# The following steps are executed in Docker
5537
vcs import src < src/ros2_rust/ros2_rust_foxy.repos
56-
. /opt/ros/foxy/setup.sh
57-
cd /src
58-
colcon build --packages-up-to rclrs_examples
38+
tmux
39+
colcon build
5940
```
6041

61-
It's normal to see a `Some selected packages are already built in one or more underlay workspace` warning. This is because the standard message definitions that are part of ROS 2 need to be regenerated in order to create Rust bindings.
62-
63-
If something goes very wrong and you want to start fresh, make sure to delete all `install*`, `build*` and `.cargo` directories. Also, make sure your terminal does not have any install sourced (check with `echo $AMENT_PREFIX_PATH`, which should be empty).
64-
65-
66-
### Building with `cargo`
67-
As an alternative to `colcon`, Rust packages can be built with pure `cargo`.
68-
69-
However, this will not work out of the box, since the `Cargo.toml` files contain dependencies like `rclrs = "*"`, even though `rclrs` is not published on crates.io. This is intentional and follows ROS 2's principle for packages to reference their dependencies only with their name, and not with their path. At build-time, these dependencies are resolved to a path to the local package by `colcon`, and written into `.cargo/config.toml`. Therefore, the package in question should be built with `colcon` once, and after that `cargo` will be able to use the `.cargo/config.toml` file to find all dependencies.
70-
71-
A second catch is that `cargo` message packages link against native libraries. A convenient way to ensure that they are found is to also source the setup script produced by `colcon`.
72-
73-
As an example, here is how to build `rclcrs_examples` with `cargo`:
42+
Then, to run the minimal pub-sub example, do this:
7443

75-
```
76-
# Initial build of the package with colcon
77-
# Compare .cargo/config.toml with and without the --lookup-in-workspace flag to see its effect
78-
colcon build --packages-up-to rclrs_examples --lookup-in-workspace
79-
# Source the install directory
80-
. install/setup.sh
81-
cd rclrs_examples
82-
# Run cargo build, or cargo check, cargo doc, etc.
83-
cargo build
84-
```
85-
86-
### Running the publisher and subscriber
87-
88-
Publisher:
89-
90-
```
91-
# Do this in a new terminal
44+
```shell
45+
# In a new terminal (tmux window) inside Docker
9246
. ./install/setup.sh
9347
ros2 run rclrs_examples minimal_publisher
94-
```
95-
96-
Subscriber:
97-
98-
```
99-
# Do this in a new terminal
48+
# In a new terminal (tmux window) inside Docker
10049
. ./install/setup.sh
10150
ros2 run rclrs_examples minimal_subscriber
10251
```
10352

104-
Enjoy!
53+
For an actual guide, see the following documents:
54+
- [Building `ros2_rust` packages](docs/Building.md)
55+
- [Contributing to `ros2_rust`](docs/Contributing.md)
56+
57+
Let us know if you build something cool with `ros2_rust`!

docs/Building.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Building `ros2_rust` packages
2+
3+
This is a more detailed guide on how to build ROS 2 packages written in Rust that expands on the minimal steps in the README.
4+
5+
In this guide, the Foxy distribution of ROS 2 is used, but newer distributions can be used by simply replacing 'foxy' with the distribution name everywhere.
6+
7+
8+
## Environment setup
9+
10+
Building `rclrs` requires a standard [ROS 2 installation](https://docs.ros.org/en/foxy/Installation.html), and a few Rust-specific extensions.
11+
These extensions are: `colcon-cargo`, `colcon-ros-cargo`, `cargo-ament-build`. The former two are `colcon` plugins, and the latter is a `cargo` plugin.
12+
13+
It is recommended to use the premade Docker image that contains all the necessary dependencies.
14+
If you do not want to use Docker, see the `Dockerfile` in the `ros2_rust` repo for how dependencies can be installed.
15+
16+
17+
### Choosing a workspace directory and cloning `ros2_rust`
18+
19+
A "workspace directory", or just "workspace", is simply a directory of your choosing that contains your `ros2_rust` checkout and potentially other ROS 2 packages. It will also usually be your working directory for building. There is only one limitation: It must not contain the ROS 2 installation, so it can't be `/`, for instance. Note that this has nothing to do with a [`cargo` workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html).
20+
21+
If you don't have a workspace directory already, simply create an empty directory in a convenient location.
22+
23+
Next, clone `ros2_rust` into it. The rest of this article will assume that you have cloned it into a subdirectory named `src` like this:
24+
25+
```shell
26+
# Make sure to run this in the workspace directory
27+
mkdir src
28+
git clone https://github.com/ros2-rust/ros2_rust.git src/ros2_rust
29+
```
30+
31+
*Note: Once `rclrs` is published on crates.io, it's not technically needed anymore to clone the `ros2_rust` repo, and this section should be adapted to reflect that.*
32+
33+
### Using the Docker image
34+
35+
Build the Docker image with
36+
37+
```shell
38+
# Make sure to run this in the workspace directory
39+
docker build -t ros2_rust_dev - < src/ros2_rust/Dockerfile
40+
```
41+
42+
and then run it with
43+
44+
```shell
45+
# Make sure to run this in the workspace directory
46+
docker run --rm -it --volume $(pwd):/workspace ros2_rust_dev /bin/bash
47+
```
48+
49+
As you can see, this maps the workspace directory to `/workspace` inside the container. So, if you're using Docker, that is now the "workspace directory".
50+
51+
52+
### Importing repositories
53+
54+
`ros2_rust` also expects a few other repositories to be in the workspace. They can be imported from a `repos` file contained in `ros2_rust`, like this:
55+
56+
```shell
57+
# Make sure to run this in the workspace directory
58+
vcs import src < src/ros2_rust/ros2_rust_foxy.repos
59+
```
60+
61+
This uses the [`vcs` tool](https://github.com/dirk-thomas/vcstool), which is preinstalled in the Docker image.
62+
63+
The new repositories are now in `src/ros2`.
64+
The list of needed repositories should very rarely change, so you typically only need to do this once.
65+
66+
67+
### Sourcing the ROS 2 installation
68+
69+
Before building, the ROS 2 installation, and ideally _only_ the ROS 2 installation, should be sourced.
70+
Sourcing an installation populates a few environment variables such as `$AMENT_PREFIX_PATH`, so if you are not sure, you can check that the `$AMENT_PREFIX_PATH` environment variable contains the ROS 2 installation, e.g. `/opt/ros/foxy`.
71+
72+
In the pre-made Docker image, sourcing is already done for you. Otherwise, if `$AMENT_PREFIX_PATH` is empty, you need to source the ROS 2 installation yourself:
73+
74+
```shell
75+
# You can also do `source /opt/ros/foxy/setup.bash` in bash
76+
. /opt/ros/foxy/setup.sh
77+
````
78+
79+
If `$AMENT_PREFIX_PATH` contains undesired paths, exit and reopen your shell. If the problem persists, it's probably because of a sourcing command in your `~/.bashrc` or similar.
80+
81+
It's convenient to install `tmux`, especially in Docker, and open separate windows or panes for building and running.
82+
83+
84+
### Checking your setup
85+
86+
To verify that you've correctly installed dependencies and sourced your ROS 2 installation, you should be able to run
87+
88+
```shell
89+
colcon list
90+
```
91+
92+
without errors and see a line like this in the output:
93+
94+
```
95+
rclrs src/ros2_rust/rclrs (ament_cargo)
96+
```
97+
98+
The build type `ament_cargo` means that the `colcon-ros-cargo` plugin works as expected.
99+
100+
101+
## Building with `colcon`
102+
103+
Now that everything is set up, you can build with `colcon`. The basic command to build a package and its dependencies is
104+
105+
```shell
106+
# Make sure to run this in the workspace directory
107+
colcon build --packages-up-to $YOUR_PACKAGE
108+
```
109+
110+
where `$YOUR_PACKAGE` could be e.g. `rclrs_examples`. See `colcon build --help` for a complete list of options.
111+
112+
It's normal to see a `Some selected packages are already built in one or more underlay workspace` warning. This is because the standard message definitions that are part of ROS 2 need to be regenerated in order to create Rust bindings. If and when `ros2_rust` becomes an officially supported client library, this issue will disappear.
113+
114+
In addition to the standard `build`, `install` and `log` directories, `colcon` will have created a `.cargo/config.toml` file.
115+
116+
117+
### Tips and limitations
118+
119+
Don't start two build processes involving Rust packages in parallel; they might overwrite each other's `.cargo/config.toml`.
120+
121+
A clean build will always be much slower than an incremental rebuild.
122+
123+
`colcon test` is not currently implemented for Rust packages.
124+
125+
The plugin to build `cargo` projects with `colcon` currently has the issue that both `cargo` and `colcon` will [rebuild all dependencies for each package](https://github.com/colcon/colcon-ros-cargo/). This makes building large projects with `colcon` less efficient, but if this is an issue, you can build with `cargo` instead.
126+
127+
128+
## Building with `cargo`
129+
130+
Rust packages for ROS 2 can also be built with pure `cargo`, and still integrate with ROS 2 tools like `ros2 run`.
131+
132+
133+
### Learning by doing
134+
135+
*Note: The following needs to be adapted once we publish `rclrs` on crates.io.*
136+
137+
If you `cd` into e.g. `rclrs` before ever having built it with `colcon` and try to `cargo build` it, you'll see an error like
138+
139+
```
140+
Updating crates.io index
141+
error: no matching package named `rosidl_runtime_rs` found
142+
location searched: registry `crates-io`
143+
required by package `rclrs v0.2.0 (/workspace/ros2_rust/rclrs)`
144+
```
145+
146+
Why is that? It's because `rclrs/Cargo.toml` contains `rosidl_runtime_rs = "*"` instead of `rosidl_runtime_rs = { path = "../rosidl_runtime_rs" }`, even though the package at that path is the one that is meant. If that's confusing, please read on. The reason is that it is a principle of ROS 2 and `colcon` for packages to reference their dependencies only by their _name_, and not by their path. This allows packages to be moved around freely in a workspace, or replaced by versions installed through `apt`, with no changes to the source code.
147+
148+
Unfortunately, referring to a package only by its name makes `cargo` assume that it should download that package from `crates.io`. `colcon-ros-cargo` works around this with a little hack: At build-time, it resolves these names to concrete paths to the local package, which are then written into `.cargo/config.toml`. The entries in that file override the original locations on `crates.io`, and therefore the _local_ packages will be used instead.
149+
150+
So, the problem above is fixed by doing one initial build of the package, or the whole workspace, with `colcon`. This creates the `.cargo/config.toml` file, and `cargo` will now use it to find `rosidl_runtime_rs`. Of course, if you don't want to install/use `colcon` for some reason, you can also create that file yourself, or replace the name-only dependencies in `rclrs/Cargo.toml` with paths.
151+
152+
Running `cargo build` in `rclrs` will now work, as well as `cargo doc`, `cargo test` and all the other `cargo` commands. Unfortunately, `cargo` may sometimes print messages saying
153+
154+
> warning: Patch `rclrs v0.1.0 (/workspace/install/rclrs/share/rclrs/rust)` was not used in the crate graph.
155+
156+
This can be ignored.
157+
158+
However, `cargo build` will not yet work for Rust packages that depend on message packages, like `rclrs_examples`:
159+
160+
161+
```
162+
/usr/bin/ld: cannot find -lrclrs_example_msgs__rosidl_typesupport_c
163+
/usr/bin/ld: cannot find -lrclrs_example_msgs__rosidl_generator_c
164+
collect2: error: ld returned 1 exit status
165+
```
166+
167+
That is, the linker does not know where to look for the `rclrs_example_msgs` libraries. This location is contained in an environment variable set by the `setup.sh` script for `rclrs_example_msgs`. So we can just source the `install/setup.sh`, and after that, `rclrs_examples` can be built.
168+
169+
To summarize:
170+
171+
```shell
172+
# Initial build of the package with colcon
173+
# The --lookup-in-workspace flag is optional
174+
# Compare .cargo/config.toml with and without it to see its effect
175+
colcon build --packages-up-to rclrs_examples --lookup-in-workspace
176+
. install/setup.sh
177+
cd src/ros2_rust/rclrs_examples
178+
# Run cargo build, or cargo check, cargo doc, etc.
179+
cargo build
180+
```
181+
182+
If you'd like to not have any of that "overriding dependencies through `.cargo/config.toml`", you can do that too of course. You just need to use concrete paths for any dependency that isn't published on `crates.io`, such as message packages. For instance, you might have to write `std_msgs = {path = '/workspace/install/std_msgs/share/std_msgs/rust'}`.
183+
184+
185+
### Integration with ROS 2 tools
186+
187+
How can a binary created in Rust be made available to `ros2 run`, `ros2 launch` etc.? And how can other ROS 2 packages use a `cdylib` created in Rust? For that to work, the correct files must be placed at the correct location in the install directory, see [REP 122](https://www.ros.org/reps/rep-0122.html).
188+
189+
It's not necessary to learn about which marker file goes where. The functionality to properly set up the install directory was extracted from `colcon-ros-cargo` into a `cargo` plugin, so that `colcon` is not required: [`cargo-ament-build`](https://github.com/ros2-rust/cargo-ament-build).
190+
191+
Simply use `cargo ament-build --install-base <path to install dir>` as a drop-in replacement for `cargo build`. After building, simply `. install/setup.sh` and you're good to run your executable with `ros2 run`.
192+
193+
194+
## Troubleshooting
195+
196+
If something goes very wrong and you want to start fresh, make sure to delete all `install*`, `build*`, `target`, and `.cargo` directories. Possibly update the Docker container if it is out of date.
197+
198+
199+
### Package identification
200+
201+
When you forgot to source the ROS 2 installation, you'll get this error:
202+
203+
> ERROR:colcon.colcon_core.package_identification:Exception in package identification extension 'python_setup_py'
204+
205+
Source the ROS 2 installation and try again.
206+
207+
208+
### Failed to resolve patches
209+
210+
When you've deleted your `install` directory but not your `.cargo` directory, you'll get this error:
211+
212+
> error: failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
213+
214+
Delete the `.cargo` directory, and rebuild.

CONTRIBUTING.md renamed to docs/Contributing.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing
1+
# Contributing to `ros2_rust`
22
Contributions to `ros2_rust` are very welcome!
33

44
Work is coordinated in the [Matrix chat](https://matrix.to/#/+rosorg-rust:matrix.org). It's a good idea to check in with the other contributors there before starting development on a new feature.
@@ -11,7 +11,9 @@ This section is not comprehensive, and conventions from the broader Rust communi
1111

1212
Use `cargo clippy` to check that code is idiomatic.
1313

14-
For documenting functions, use declarative sentences, not imperative sentences. For instance, `Creates a by-value iterator.` should be used instead of `Create a by-value iterator.`.
14+
For documenting functions, use declarative sentences, not imperative sentences. For instance, `Creates a by-value iterator.` should be used instead of `Create a by-value iterator.`. Use a period at the end of every phrase, even the "brief" description.
15+
16+
Document any panics that might happen in a function, and where it makes sense, also document the errors that may be returned by a function.
1517

1618
### Unsafe code
1719
Keep `unsafe` blocks as small as possible.
@@ -32,6 +34,21 @@ Put a `struct`'s definition first, then its trait `impl`s in alphabetical order,
3234

3335
Try to not exceed a line length of 100 characters for comments.
3436

37+
### Error messages
38+
Error messages should
39+
- be capitalized
40+
- not have punctuation at the end
41+
- not include an "Error:" prefix or similar
42+
43+
### Tests
44+
Prefer using `quickcheck` tests and doctests where they make sense.
45+
46+
Avoid writing tests that simply restate the definition of a function. E.g. for `fn square(x: i32) { x*x }` do not write a test that asserts `square(3) == 3*3`.
47+
48+
### Wrapping `rcl` functions
49+
Functions from `rcl` can typically return error codes. This does not necessarily mean that the `rclrs` function using it must return `Result`. If the error conditions can be determined, from reading `rcl` comments and source code, to never occur, the return code doesn't need to be propagated but only `debug_assert!`ed to be ok. This assert guards against `rcl` introducing a new error condition in the future.
50+
51+
An example would be a function that only returns an error when the pointer we pass in is null or points to an uninitialized object. If we obtain the pointer from a reference, it can't be null, and if the object that is referenced is guaranteed to be initialized, there is no need to return a `Result`.
3552

3653
## License
3754
Any contribution that you make to this repository will

0 commit comments

Comments
 (0)