Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What nros build Produces

nros build ends at an artifact and stops. It discovers your packages, resolves the image, checks the toolchain, generates the root build file and the entry, and then hands off to the native tool — cargo, cmake, west or idf.py. The last stage is an exec, so the artifact is exactly the one that tool would have produced had you run it yourself.

This page answers the question the build does not: what are you holding, and where is it?

There is no nros run and no nros flash

That is a decision, not a gap. How an image is flashed or started is a property of the board and of your bench — a probe, a bootloader, a QEMU command line, a CI fixture — and nano-ros cannot know it without inventing a device inventory nobody asked for. So nano-ros delivers binaries, and the board’s own tooling takes them from there.

The commands quoted below are that tooling’s, reproduced here as a reference. They are not nano-ros wrappers and nano-ros does not run them for you.

There is also no install step: nros build never runs cmake --install, writes no install/ prefix and stages nothing. Whatever the native tool wrote in its own build tree is the deliverable.

Where it lands

Which tool builds an image is decided by its platform and by whether the workspace crosses languages:

platform / shapedriverthe artifact
Rust single package, any non-Zephyr board (ESP32 included — see below)cargo<leaf>/build/<image-id>/target/[<triple>/]<profile>/<bin>
Rust-only workspace, the same boardscargo<ws>/build/<coordinate>/<image>_entry/target/[<triple>/]<profile>/<image>_entry
any workspace containing C or C++cmake<ws>/build/<coordinate>/cmake/<image>_entry
a zephyr boardwest<ws>/build/<coordinate>/<bringup>__<image>_west/zephyr/zephyr.{elf,bin,exe}
an esp32 board, image crossing languagesidf.pyESP-IDF’s own build/ in the project directory — an OUT-OF-TREE project; nano-ros ships no ESP-IDF component

The coordinate is the platform and the RMW — posix-zenoh, freertos-cyclonedds — plus, for CMake, the board, because CMake pins one compiler per configure and two boards on one platform would otherwise share a cache: posix-zenoh-linux, freertos-zenoh-mps2-an385-freertos.

Everything above is under build/, and that is the rule rather than a coincidence: a workspace has no root Cargo.toml and no root CMakeLists.txt (RFC-0098 D9). It is a directory of packages, like a colcon workspace, and every generated thing — the entry, the root build file, the cargo settings, the compiled output — lives in build/.

One path the build does print is easy to mistake for the binary:

nros build:   entry → …/build/posix-zenoh/linux_entry

That is the generated entry package — source that nano-ros wrote for you, under build/ because it is build output. The compiled program is elsewhere; see below.

cargo

Every image gets its own cargo settings file, build/<image-id>/nros-cargo.toml for a single package and build/<coordinate>/<entry>/nros-cargo.toml for a workspace, and nros build points cargo at it:

cargo build --manifest-path talker/Cargo.toml --config talker/build/linux/nros-cargo.toml

That file states the image’s target-dir, so each image compiles into its own tree rather than sharing one target/. It also carries the board’s triple, linker flags and cross compiler, the resolved pool budgets as [env], the in-repo [patch.crates-io] rows, and the nros-* build profiles. It is generated on every sync and build — read it, never edit it.

For a single package the artifact is that package’s own binary:

<leaf>/build/<image-id>/target/[<triple>/]<profile-dir>/<bin>

examples/linux/rust/talker lands at build/linux/target/debug/talker; its bare-metal sibling examples/mps2-an385-baremetal/rust/talker at build/mps2-an385-baremetal/target/thumbv7m-none-eabi/debug/qemu-bsp-talker. The triple level appears whenever the board pins one — which it does in the generated [build] target, not on your command line.

For a workspace the binary is the generated entry, inside that entry’s own target dir — the Rust scaffold’s linux image is build/posix-cyclonedds/linux_entry/target/debug/linux_entry.

<profile-dir> is debug unless the image declares [image.<id>] profile = …, in which case it is that profile’s directory (nros profile dir <name> prints it).

Next step: on a host board, run it. It is an ordinary executable.

./build/linux/target/debug/talker

For a QEMU board, the generated settings file also carries that board’s runner, so cargo run through the same --config boots the image — see Build Profiles for driving cargo yourself.

A multi-process zenoh example also needs a router; that is ROS’s rmw_zenohd, not something nano-ros ships — see Deployment Workflow and Install.

C, C++ and mixed workspaces (cmake)

CMake is chosen whenever the package graph contains anything that is not Rust, on the host and when cross-compiling alike. The generated root is <ws>/build/<coordinate>/CMakeLists.txt, it is configured into <ws>/build/<coordinate>/cmake/, and each image’s executable lands at the top of that directory:

<ws>/build/<coordinate>/cmake/<image>_entry

For example, examples/workspaces/c builds [image.linux] to build/posix-zenoh-linux/cmake/linux_entry and its FreeRTOS image to build/freertos-zenoh-mps2-an385-freertos/cmake/freertos_entry. (Node packages also build their own executables, under cmake/pkg/<pkg>/<pkg>; the image is the one at the top level.)

The file has no extension in either case. On the host it is a host executable; for a cross board it is a statically linked ELF for that architecture — the FreeRTOS one above is ELF 32-bit LSB executable, ARM, EABI5, statically linked.

Next step:

  • host — run it directly.
  • QEMU — the ELF is what -kernel takes, with the machine and CPU your board’s guide names.
  • hardware — hand the ELF to your flasher (probe-rs, OpenOCD, a vendor tool). nano-ros has no opinion about which.

Zephyr (west)

nros build resolves your application and its overlays and runs west build from the workspace root with each image’s OWN build directory, <ws>/build/<coordinate>/<bringup>__<image>_west (issue 1669 — west’s default, build/ under the cwd, put every image of a workspace in one tree and refused the second). Pass -- -d <dir> to choose another. Zephyr writes its image under zephyr/ inside it; for demo_bringup:zephyr on zenoh:

<ws>/build/zephyr-zenoh/demo_bringup__zephyr_west/zephyr/zephyr.elf   # always
<ws>/build/zephyr-zenoh/demo_bringup__zephyr_west/zephyr/zephyr.exe   # native_sim — a host executable
<ws>/build/zephyr-zenoh/demo_bringup__zephyr_west/zephyr/zephyr.bin   # cross targets — the raw image
<ws>/build/zephyr-zenoh/demo_bringup__zephyr_west/zephyr/zephyr.map   # the link map

Which of .exe and .bin appears is Zephyr’s choice, not nano-ros’s: a native_sim build produces zephyr.exe, a Cortex-M build produces zephyr.bin.

Next step — Zephyr’s own verbs:

./build/zephyr/zephyr.exe     # native_sim: just run it
west build -t run             # emulated boards: Zephyr's runner
west flash                    # hardware: the runner declared by the board

west flash picks its runner from the board’s own board.cmake; nano-ros adds nothing to it. Note that these are run against the west build directory, so they work from wherever you would normally run west.

ESP32

Two different shapes, because there are two ways to build for an ESP32:

ESP-IDF (idf.py driver) — reached only when an esp32 image crosses languages. nros build runs idf.py build in the project directory and the artifact is whatever ESP-IDF writes under that project’s build/; the next step is Espressif’s:

idf.py -p /dev/ttyUSB0 flash monitor

The driver survives, but the project it drives must be your own: the in-tree ESP-IDF component was retired in phase-468 W2. A Rust board-run esp32 image is a cargo image, not an idf.py one — deciding that by platform alone is what made nros build esp32 exec a tool with no project to build.

Rust bare-metal (esp-hal) — a single-package image on the cargo driver like any other, so the ELF is under that image’s own target dir: build/<image-id>/target/<triple>/<profile-dir>/<name>. espflash turns it into a flash image or writes it to a board:

espflash flash --monitor \
    build/esp32-c3-baremetal/target/riscv32imc-unknown-none-elf/release/talker
espflash save-image --chip esp32c3 --flash-size 4mb --merge \
    build/esp32-c3-baremetal/target/riscv32imc-unknown-none-elf/release/talker talker.bin

See ESP32 (esp-hal).

Finding it yourself

When in doubt, ask the build what it is about to do. --dry-run prints the stages and the exact command, and performs no I/O:

nros build <image> --dry-run

The tool named in that command owns the output location, and its own documentation is the authority — which is the whole point of handing off rather than wrapping.

For the generation side of this — which root file is written, why the entry is derived rather than hand-written, and what the coordinate directory holds — see Images.