Installation
nano-ros is distributed as source, vendored into the consumer’s
project tree (git submodule, west manifest, ESP-IDF component, etc.)
and built in-tree via add_subdirectory(nano-ros). There is no binary
tarball, no system-wide install step, no find_package(NanoRos).
activate.shis the after-install step, NOT a prereq. Sourcingactivate.sh(oractivate.fish, ordirenv allow) only wires the workspace env into a new shell — it presumes thenrosbinary already exists atpackages/cli/target/release/nros. The three bootstrap paths below (A/B/C) are what produce that binary; the activate file is what makes it findable in every subsequent shell.
See build-as-subdirectory.md for the canonical user incantation (4-line
CMakeLists.txt). This page walks the surrounding workspace + per-target setup choices.
Pattern A: nano-ros lives inside your ROS 2 workspace
The recommended layout is to clone nano-ros into your workspace’s
src/ directory alongside your packages:
~/ros2_ws/
├── src/
│ ├── nano-ros/ # <-- this repo
│ ├── pkg_a/ # your package(s)
│ ├── pkg_b/
│ └── …
└── (build/, install/, log/ — generated by colcon)
colcon build discovers nano-ros + each user package via package.xml,
builds them in dependency order, and shares one nano-ros build across
every consuming package. Users never run cmake manually — the per-user
package’s CMakeLists.txt does add_subdirectory(../nano-ros nano_ros)
under the hood.
A complete working example lives at
examples/templates/multi-package-workspace/.
Pattern B: nano-ros as a third-party subdirectory
For C/C++ projects that don’t use colcon at all:
~/my_project/
├── CMakeLists.txt
├── src/main.c
└── third_party/
└── nano-ros/ # git submodule
The top-level CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(my_app LANGUAGES C)
# `NROS_RMW` is the user-facing cache var (overridable via
# `-DNROS_RMW=<rmw>`); forward it to `NANO_ROS_RMW`, the var the
# nano-ros add_subdirectory reads. Matches the canonical example
# shape in examples/native/c/talker/CMakeLists.txt.
set(NANO_ROS_PLATFORM posix)
set(NROS_RMW "zenoh" CACHE STRING
"Active RMW (zenoh|xrce|cyclonedds) — selects the backend linked into my_app.")
set(NANO_ROS_RMW "${NROS_RMW}")
add_subdirectory(third_party/nano-ros nano_ros)
add_executable(my_app src/main.c)
target_link_libraries(my_app PRIVATE NanoRos::NanoRos)
nros_platform_link_app(my_app)
nros_platform_link_app transitively wires the selected RMW backend
on POSIX — no explicit nano_ros_link_rmw() call is needed. That is
the entire consumption shape. See
build-as-subdirectory.md for the full
walkthrough.
Provision your toolchain with nros setup
nros setup is the single command that prepares a machine to build
nano-ros for a given board. It ships prebuilt toolchains per platform
per RMW — the cross-compiler, emulator, RMW host daemon, and any SDK
sources a board needs are fetched from a pinned index and placed in a
shared store (~/.nros/sdk). You do not install cross-toolchains by
hand, and you do not need ROS 2 on the machine.
1. Get the nros CLI onto PATH
Pick one path from a fresh checkout — just is NOT a prereq.
A. Bare machine (no Rust, no just, no cargo):
git clone https://github.com/NEWSLabNTU/nano-ros.git
cd nano-ros
./scripts/bootstrap.sh base
Installs rustup, just, builds the in-tree nros CLI at
packages/cli/target/release/nros, leaves the binary on PATH for
this shell.
B. Already have cargo (most contributors):
cargo build --release --manifest-path packages/cli/Cargo.toml --bin nros
export PATH="$PWD/packages/cli/target/release:$PATH"
C. Tagged release, no Rust at all:
./scripts/install-nros-prebuilt.sh
Downloads the matching nros-<triple>.tar.gz from the GitHub release,
sha256-verifies, installs to packages/cli/target/release/nros.
All three paths produce the per-checkout binary at
packages/cli/target/release/nros. One checkout = one CLI version =
one runtime ABI — no global install, no ~/.nros/bin PATH skew
across worktrees.
2. Activate the workspace (every subsequent shell)
The bootstrap path you ran in §1 left nros on PATH for that shell
only. Every new shell needs the workspace env wired in. Pick whichever
fits your shell — all three wire the same env exports + PATH entries
from the Phase 218.C SSoT activate.sh:
direnv allow # auto-activates on `cd nano-ros` (recommended)
source ./activate.sh # bash / zsh, one-shot per shell
source ./activate.fish # fish, one-shot per shell
The activate file also sources /opt/ros/humble/setup.bash if
present (required by nros generate-rust + cyclonedds codegen +
rmw_zenoh interop tests) and exports NROS_REPO_DIR.
3. Provision a board (+ RMW)
nros setup <board> --rmw <zenoh|xrce|cyclonedds>
nros setup resolves the board’s package set union the RMW’s host
packages and fetches them all — prebuilt cross-toolchain + emulator +
RMW daemon + board SDK sources. --rmw defaults to zenoh.
| Command | Provisions |
|---|---|
nros setup native | host build; the zenoh router (zenohd) |
nros setup native --rmw xrce | host build; the Micro-XRCE-DDS agent |
nros setup native --rmw cyclonedds | host build; Cyclone DDS runtime + idlc |
nros setup qemu-arm-freertos | arm-none-eabi-gcc, patched qemu-system-arm, FreeRTOS + lwIP sources, zenohd |
nros setup qemu-arm-nuttx | arm-none-eabi-gcc, qemu, NuttX sources |
nros setup qemu-riscv64-threadx | riscv64-*-gcc, qemu, ThreadX/NetX sources |
nros setup threadx-linux | ThreadX POSIX-sim sources |
nros setup esp32 | the Espressif toolchain bits the esp-hal path needs |
nros setup zephyr | the Zephyr west workspace + Zephyr SDK bits |
nros setup mps2-an385 / stm32f4 / qemu-arm-baremetal | bare-metal arm-none-eabi-gcc + qemu |
Useful flags:
nros setup --list # every package in the index + its version
nros setup <board> --dry-run # resolve + print the plan, fetch nothing
nros setup --licenses # license-gated packages + how to install them
nros setup --tool qemu # one tool by name
nros setup --source freertos-kernel # one source package by name
Each board’s exact package set lives in the index; run
nros setup <board> --dry-run to see precisely what a board pulls.
See Supported Boards for the full
board list and nros CLI for every subcommand.
Heads-up before your first example. Every nano-ros example (Linux talker, FreeRTOS talker, …) connects to its RMW host daemon at startup —
zenohdfor zenoh, the Micro-XRCE-DDS agent for xrce. Cyclone DDS is in-process — no separate daemon — so the heads-up below doesn’t apply if you rannros setup … --rmw cyclonedds.nros setup … --rmw <rmw>installs the daemon into the nros store (~/.nros/sdk/<tool>/<version>/bin/; the cache root is~/.nros/sdk/— toolchains, transports, and daemons all land under there); you must then run it in a dedicated terminal before launching any example. For zenoh, put the store binary on PATH once and run it:export PATH="$(dirname "$(ls -d ~/.nros/sdk/zenohd/*/bin/zenohd | tail -1)")":$PATH zenohd # leave running for the whole sessionWithout it the talker blocks forever on
Executor::openwith no output. Default ports:tcp/127.0.0.1:7447on POSIX,tcp/10.0.2.2:7451on QEMU FreeRTOS (Slirp forwards to host). Mismatch = silent hang; see Troubleshooting — First 10 Minutes.
After provisioning, follow the per-platform starter page (FreeRTOS, Zephyr, NuttX, …) for the board’s build + run steps.
Rust-only consumers
nano-ros is source-only — nothing is published to crates.io
(decision 2026-05-14). The full nros crate can’t be published
because it depends transitively on C/C++ submodules (zenoh-pico,
mbedtls); nros-core isn’t carved out for crates.io either, to
avoid a hybrid distribution model with version drift between the
crates.io snapshot and in-repo HEAD.
Rust packages consume nano-ros via path dependency on the in-workspace checkout:
[dependencies]
nros = { path = "src/nano-ros/packages/core/nros",
default-features = false,
features = ["std", "rmw-cffi", "platform-posix", "ros-humble"] }
Each Rust package carries its own .cargo/config.toml patch entries
when needed — see
examples/templates/multi-package-workspace/src/pkg_rust_publisher/
for the pattern.
Contributor setup (working on nano-ros itself)
Contributors clone the repo and drive everything through just. The
just setup recipes are thin wrappers over the same nros setup index —
just <module> setup calls nros setup <board> under the hood, so the
toolchains a contributor gets are identical to a user’s.
git clone https://github.com/NEWSLabNTU/nano-ros.git
cd nano-ros
./scripts/bootstrap.sh base # path A from §1 above; gets rustup + just + nros
source ./activate.sh # OR: direnv allow / source ./activate.fish
just setup all # provision every supported board's SDK/toolchain
Diagnose missing tools (read-only):
just doctor tier=all
Provision one module:
just freertos setup # → nros setup qemu-arm-freertos
just nuttx setup # → nros setup qemu-arm-nuttx
just threadx_linux setup # → nros setup threadx-linux
Docker environment
For a containerized environment with QEMU 7.2+:
just docker build
just docker shell # Interactive shell with all tools
just docker test-qemu # Run QEMU tests in container
Migrating from a pre-140 checkout
If you were on a nano-ros version that still had just install-local,
see
migration-install-local-removal.md
for the one-page rewrite.
Next Steps
- First Node — Rust — build + run a Rust talker on Linux
- First Node — C — build + run a C talker on Linux
- First Node — C++ — build + run a C++ talker on Linux
- Build as Subdirectory — CMake consumption walkthrough
- C API — API entry points and CMake integration
examples/templates/multi-package-workspace/— full mixed C / C++ / Rust example