Build as a CMake subdirectory
This is the escape hatch, not the path. If your project is a
nano-ros package or workspace, you state the board once in
system.toml and let nros sync + nros build drive CMake — you
never set a cache variable by hand, because the build root it generates
sets them from that one board name (RFC-0098). Start at
First Project and
Project layout instead.
This page is for the C or C++ project that is not a nano-ros
workspace: an existing CMake tree with its own layout and its own
conventions, that wants nano-ros as a subdirectory and nothing else.
There the cache variables below are how you say what you are building
for, because there is no system.toml to read it from. The mechanism
is unchanged by RFC-0098 — nros build sets exactly these variables in
the root it generates.
The repo’s top-level CMakeLists.txt exposes everything via
add_subdirectory(...). The higher-level, ament-shaped alternative is
find_package(nano_ros REQUIRED) — nano_rosConfig.cmake at the
checkout root, located via -Dnano_ros_ROOT=<root> (RFC-0048), which
nros build and the CMakePresets nros init writes pass for you (a raw
cmake names it; no shell exports it) — which every in-tree example
reaches through the workspace helpers. Either way there
is NO install step: phase-140 removed the install prefix and the old
find_package(NanoRos) (capital) config pipeline. Vendoring the repo
into a company tree (pinning, offline CI, upgrade workflow) has its own
page: Integrating into a Vendored Tree.
Layout
my_app/
├── CMakeLists.txt
├── main.c
└── third_party/
└── nano-ros/ # git clone / git submodule of this repo
User project CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(my_app C)
# Pick platform + RMW BEFORE add_subdirectory.
set(NROS_PLATFORM posix) # posix | freertos | nuttx | threadx | zephyr | baremetal
set(NROS_RMW zenoh) # zenoh | xrce | cyclonedds
add_subdirectory(third_party/nano-ros nano_ros)
add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE NanoRos::NanoRos)
if(COMMAND nros_platform_link_app) # defined by zephyr/threadx platforms only
nros_platform_link_app(my_app)
endif()
# Optional — generate C bindings for ROS 2 .msg / .srv / .action files.
# (LANGUAGE defaults to CPP; full spelling table → user-guide
# "Message Generation".)
nano_ros_generate_interfaces(std_msgs LANGUAGE C
DEPENDENCIES builtin_interfaces)
target_link_libraries(my_app PRIVATE std_msgs__nano_ros_c)
That’s the whole story for the host-POSIX / zenoh case. CMake’s
transitive target propagation pulls in libnros_c.a,
libnros_rmw_zenoh_staticlib.a, the POSIX platform shim, system
libraries (pthread, dl, m), and the per-build
nros_config_generated.h header automatically.
Cache variables
| Variable | Default | Values |
|---|---|---|
NROS_PLATFORM | posix | posix, freertos, nuttx, threadx (legacy spellings: freertos_armcm3, nuttx_armv7a, threadx_linux, threadx_riscv64). esp_idf was accepted until phase-468 W2 and is now REJECTED. Zephyr and bare-metal are not add_subdirectory platforms — they enter via the Zephyr module / cargo directly |
NROS_BOARD | (unset) | required for threadx (threadx-linux or rv-virt-threadx) |
NROS_SKIP_BOOTSTRAP | OFF | ON skips the configure-time bootstrap (git submodule updates). Required for air-gapped/mirrored CI — pre-seed submodules. Corrosion is never fetched: a configure without it in the SDK store stops and names nros setup --tool corrosion |
NROS_FEATURES | (empty) | extra cargo features forwarded to the Rust build |
NROS_RMW | zenoh | zenoh, dds, xrce, cyclonedds |
NROS_ROS_EDITION | humble | humble, iron, jazzy |
NROS_BUILD_CODEGEN | ON | ON / OFF |
Variables MUST be set(...) BEFORE add_subdirectory(...) — the
sub-project consumes them at include time.
In a nano-ros package or workspace none of these are yours to set. The
CMake root nros build generates writes NROS_PLATFORM and
NROS_BOARD itself, both derived from the one [image.<id>] board
you named in system.toml, and passes the backend from [system] rmw
through the workspace helper. Setting them by hand there means two
answers to one question, which is the arrangement RFC-0098 exists to
end.
What about installing?
Phase-140 deleted every nano-ros-side install(...) rule. nano-ros is
consumed in source form — never out of an installed prefix. If you need a
shippable artefact, your user project owns the install layout; ship
your binary, not nano-ros itself.
For RTOS users who want a more idiomatic surface than raw
add_subdirectory, see the integration shells under
integrations/<rtos>/ — they translate west / esp-idf / NuttX / PX4
manifests into the same root CMake. Each shell is a
~20-line wrapper around add_subdirectory(<repo>).
What the in-tree examples do instead
Worth reading as the contrast, because it is the shape most projects
want. examples/linux/c/talker/CMakeLists.txt is fourteen lines and
sets no cache variable at all:
cmake_minimum_required(VERSION 3.22)
project(c_talker LANGUAGES C CXX)
find_package(nano_ros REQUIRED)
find_package(std_msgs REQUIRED)
nano_ros_add_executable(c_talker src/main.c)
ament_target_dependencies(c_talker std_msgs)
The board, the RMW, the domain and the network identity are not in
that file — they are in the system.toml beside it, and the build
reads them from there. A single-package C/C++ leaf like this one
configures and builds with plain CMake, no sync step required, because
its message bindings are a CMake-time output. A raw cmake names the
SDK root (nros sdk-root prints it; nothing exports it):
cmake -B build -Dnano_ros_ROOT="$(nros sdk-root)"
cmake --build build
(nros build in the leaf runs these same two commands for you, into
build/<image>/cmake, and passes the board’s toolchain file
(issue 1296, resolved). A C/C++ workspace builds with
nros sync + nros build as usual.)
All in-tree C/C++ examples follow that shape. The add_subdirectory
mechanism on this page sits underneath it.