Application Workflow
nano-ros users usually want one path: prepare package, write node, build it, then deploy to target. Use Concepts only when workflow raises a technical question.
1. Prepare Workspace and Package
nano-ros is shipped source-only — vendor it next to (or inside) your
workspace, then provision the board’s toolchain with nros setup:
# Build the in-tree nros CLI (Phase 218), then provision your board (+ RMW):
./scripts/bootstrap.sh base
source ./activate.sh # OR: direnv allow / source ./activate.fish
nros setup linux --rmw zenoh # or mps2-an385-freertos, zephyr, …
nros setup provides most components prebuilt per platform per RMW; a few
without a seeded asset (the vendored submodules — zenoh-pico, mbedtls,
RTOS sources) are built from source. nros setup <board> --rmw <rmw> --dry-run
shows which is which on your host — see
Installation.
It does not provide a zenoh router: that is ROS’s rmw_zenohd, so a
multi-process zenoh example needs a ROS 2 install (or NROS_RMW_ZENOHD).
--rmw cyclonedds needs no daemon at all.
For multi-package workspaces (Pattern A — recommended for POSIX +
mixed C / C++ / Rust deployments), put nano-ros and your packages
side-by-side under a shared src/:
~/ros2_ws/
├── src/
│ ├── nano-ros/ # this repo
│ ├── my_robot_node/
│ │ ├── package.xml
│ │ ├── Cargo.toml # Rust — dependencies; nothing about the board
│ │ ├── CMakeLists.txt # C/C++ — targets; nothing about the board
│ │ └── src/
│ └── my_robot_bringup/
│ ├── system.toml # board, RMW, components — the one place
│ └── launch/
└── build/ log/ # generated; the workspace has no root build file
A package’s Cargo.toml and CMakeLists.txt carry language-toolchain facts
only. Which board the program deploys to, which RMW it speaks, and which
components it runs are stated once in system.toml — in the bringup package
for a workspace, beside the package for a single-package project — and
everything that choice implies is generated by nros sync (RFC-0098).
For third-party C/C++ projects without colcon (Pattern B), pull
nano-ros in as a third_party/nano-ros/ git submodule and consume
it via add_subdirectory(third_party/nano-ros nano_ros).
See Installation for both patterns. The per-language starter pages document the canonical package shape in their “Project layout” sections: Rust, C, C++. For two or more nodes, use the Multi-Node Projects group: start from the full project layout, then drill into Node, Bringup, and Entry packages.
2. Write Node Code
Choose API language first:
- Rust — use
nros, generated message crates, andExecutor. - C — include
rclc/rclc.hand generate interfaces withnros_find_interfaces(LANGUAGE C)in CMake (one of five codegen entry points — which to use). - C++ — include
rclcpp/rclcpp.hppand use typed wrappers.
Start with one of the Linux starters above, then adapt to your target via the Embedded Starters section.
3. Generate Messages
If you use custom .msg, .srv, or .action files, generate bindings
inside the workspace/build tree. See
Message Binding Generation.
4. Configure Target
The board and the RMW backend are a build-time choice — there is no
RMW_IMPLEMENTATION runtime switch on embedded targets — and you state
them in system.toml, the same way in every language:
[system]
name = "my_robot"
rmw = "zenoh" # zenoh | cyclonedds | xrce
domain_id = 0
[[component]]
pkg = "my_robot_node"
class = "my_robot_node::Talker"
name = "talker"
[image.linux] # one per program you want to build
board = "linux"
[image.mps2] # an embedded image carries its network identity too
board = "qemu-mps2-an385"
locator = "tcp/10.0.2.2:10500"
net = { eth0 = { network = "qemu", host = 10 } }
Switching board is editing [image.<id>] board and re-running nros sync.
The platform, the rustc triple, the linker flags, the cross compiler and
the pool budgets all follow from that one word — none of them is yours to
write. The board crate follows too in a workspace, where the entry is
generated, and in a single-package Rust project that depends on its
generated <pkg>_nros_selection package (every in-tree single-package
example on a supported board does); one that still names its board crate in [dependencies]
must move the two together
(issue 1305).
Supported pairs: posix / freertos / nuttx / threadx / zephyr / esp32 / baremetal × zenoh / xrce / cyclonedds. Not every cell is implemented —
see the
Coverage Matrix.
Runtime configuration (ROS_DOMAIN_ID, NROS_LOCATOR, …) works on
POSIX; embedded targets bake the [image.*] values into the firmware,
plus Kconfig on Zephyr. See Configuration.
5. Build, Test, Deploy
Two commands, everywhere:
nros sync # generate what the board choice implies
nros build # build every declared image; or: nros build <image-id>
nros sync writes the generated message crates, resolves the launch
files into the system model, and writes each image’s build settings under
build/. nros build generates that image’s entry, then hands off to the
native tool — cargo, CMake or west — so compiler diagnostics are exactly
what you would see running it yourself. nros build is where a ROS 2
user’s colcon build goes: same discovery of src/, same dependency
order, same build/ and log/ beside the workspace.
Sync is a separate step, not an implicit one. Re-run it after editing
system.toml or a .msg file; if you forget, nros build stops at its
preflight and says so in one line —
generated message bindings (this workspace has never been synced) / run: nros sync — rather than failing somewhere inside cargo.
A single Rust package works the same way:
cd examples/linux/rust/talker
nros sync
nros build
A single C/C++ package keeps its own CMake, and needs no sync — its message bindings are a CMake-time output:
cd examples/linux/c/talker
cmake -B build -Dnano_ros_ROOT="$(nros sdk-root)" # raw cmake names the SDK root
cmake --build build
(nros build in the leaf runs the same pair into build/<image>/cmake,
with the board’s toolchain file —
issue 1296, resolved.)
Zephyr is the carve-out: west build is still its build verb, with
nros sync run first — see
Deployment Workflow.
# Contributors: per-platform multi-example fixture build, in-tree
# checkout only (these run `nros sync` for you):
just freertos build-fixtures
just zephyr build-fixtures
just nuttx build-fixtures
# Contributors: discover full-matrix commands for a platform:
just --group full-matrix --list zephyr
Which shape your target uses is Workflow by Platform and Language.
nros sync resolves the bringup’s system.toml (plus its launch files)
into a SystemModel under the workspace build tree. SystemModels are
build artifacts — never committed, and never named by an entry
package; an entry names its input
(nros::main!(launch = "bringup"), nano_ros_entry(BRINGUP … LAUNCH …))
and the build locates the artifact. nros model-path prints where a
resolved one landed.
nros metadata / nros plan / nros check are the inspection path,
not the build path: they produce and validate an nros-plan.json you
can read with nros explain. You do not need them to build.
For target-specific deployment, go to the matching platform guide. Each guide covers toolchain setup, package layout, code example, build command, run/flash command, and deployment notes.
See Deployment Workflow.