# Building and testing

> Build and test commands for each RosieOS component, the environment the rt-core tests expect, and what each CI workflow runs and gates.

URL: https://advancedmetalresearch.com/docs/contributing/building-and-testing
Section: RosieOS docs / Contributing
Last updated: 2026-10-10

Each component builds and tests on its own. Run the commands on this page from the repository root unless a step says otherwise. Install the toolchains first: see [Install the toolchain](https://advancedmetalresearch.com/docs/get-started/installation).

A quick check of the controller and two of its consumers, on Linux or WSL:

```bash
export TMPDIR=/dev/shm/rosie-tests && mkdir -p "$TMPDIR"
make -C rt-core control sim test
make -C motion-server/joint-trajectory/v1 all test BIN_DIR="$PWD/rt-core/build/dense"
(cd offline-programming/v1 && go test -race -count=1 ./...)
```

## Test environment

The rt-core tests start real processes and bind Unix sockets, and some assert real locked-memory behaviour.

- **Keep `TMPDIR` on a Linux tmpfs**, such as `/dev/shm/...`. On WSL, sockets cannot bind under `/mnt/c`.
- **Allow unlimited locked memory** for the test shell. CI runs `sudo prlimit --pid $$ --memlock=unlimited:unlimited` and checks `ulimit -l` prints `unlimited`. Do the same, or raise `memlock` in `/etc/security/limits.conf`.
- **Node 22.13.1** is used by the generator golden tests. They look for it at `~/.rosie/node-22.13.1/bin/node` or in `NODE`. CI symlinks its Node there.
- To keep Go caches inside the checkout, set `GOCACHE`, `GOMODCACHE` and `GOTMPDIR` under `rt-core/build/`, and `GOFLAGS=-mod=mod`.

```bash
cd rt-core
export GOCACHE=$PWD/build/go-cache GOMODCACHE=$PWD/build/go-mod-cache GOTMPDIR=$PWD/build/go-tmp GOFLAGS=-mod=mod
mkdir -p "$GOCACHE" "$GOMODCACHE" "$GOTMPDIR"
```

## rt-core

```bash
cd rt-core
make control sim     # rt-control, rtctl, benchdrive, rt-natspublisher, rosie-rt-core-sim
make test
```

| Target | What it does |
|---|---|
| `make control` | Builds `build/rt-control`, `build/rtctl`, `build/benchdrive` and `build/rt-natspublisher`. |
| `make sim` | Builds `build/rosie-rt-core-sim`, the core with a simulated bus. |
| `make ipc-only` | Builds `build/rosie-rt-core-ipc`, for IPC-only validation. |
| `make live` (default `all`) | Builds `build/rosie-rt-core` against IgH `libethercat`. Refuses missing or mismatched IgH metadata. |
| `make clients` | Builds the C++ client examples. |
| `make test` | C++ policy tests, Go oracle parity, and `check-protocol` and `check-api`. |
| `make test-go` | Every Go package, with `-race -count=1 -p=1`. |
| `make test-faults` | The IPC fault matrix. |
| `make test-sanitize` | ASan/UBSan builds of the core and cell I/O tests, with the native oracle. |
| `make test-tsan` | ThreadSanitizer builds of the policy tests and core. |
| `make check-protocol`, `make check-api` | Fail if generated code differs from the contracts. |
| `make generate-protocol`, `make generate-api` | Regenerate from the contracts. See [generated code](https://advancedmetalresearch.com/docs/contributing/repo-layout#generated). |
| `make soak` | 50 simulation soak iterations; writes a JSON record under `build/`. |
| `make ci` | Runs the local CI driver. |

To build the live daemon without hardware, build the pinned IgH userspace library first:

```bash
cd rt-core
bash tools/build-igh-userlib.sh          # IgH 1.6.9 by default; 1.6.10 to 1.6.12 are also pinned
export PKG_CONFIG_PATH="$PWD/build/deps/igh-prefix/lib/pkgconfig"
make live control
build/rtctl validate --backend live --config config/machines/a6ec-bench.example.json
```

The script downloads the IgH source archive and checks its SHA-256 before building.

## Robot descriptions

```bash
python3 robot_description/tools/manifest.py --check
(cd robot_description/go && go test ./...)
```

## Motion servers

Both build against rt-core's C++ client and test against the simulated core, so build `rt-core` first.

```bash
make -C rt-core control sim
make -C motion-server/v1 all test test-composed \
  BIN_DIR="$PWD/rt-core/build/cartesian" OBJECT_CACHE_DIR="$PWD/rt-core/build/cartesian-cache"
make -C motion-server/joint-trajectory/v1 all test \
  BIN_DIR="$PWD/rt-core/build/dense" TEST_BUILD_DIR="$PWD/rt-core/build/dense-tests"
```

The dense daemon also has `test-go` and `test-sanitize`. `make -C motion-server/joint-trajectory/v1 print-bin-dir` prints its default output directory. The Cartesian server's default `BIN_DIR` is outside the repository, which is why these commands set it.

## Offline programming

```bash
make -C rt-core control sim
(cd offline-programming/v1 && go test -race -count=1 ./...)
(cd offline-programming/v1/ui && npm ci && npm run lint && npm test)
```

## Simulation stack and virtual pendant

```bash
make -C rt-core control sim
(cd steamdeck/virtual && go test -race -count=1 ./bridge ./stack ./v1)
DEV_STACK_BACKEND=rt_core DEV_STACK_FIREWALL=0 DEV_STACK_UI_HOST=127.0.0.1 \
  bash motion-server/v1/tests/dev-stack-smoke.sh
(cd steamdeck/virtual/v1/ui && npm ci && npm test && npm run build)
```

`DEV_STACK_FIREWALL=0` stops the dev stack from changing firewall rules. See [Ports, sockets and environment variables](https://advancedmetalresearch.com/docs/reference/ports-and-environment#dev-stack-dev-stacksh).

## Weld planner

```bash
cd weld_planner/v1
pixi install -e default
pixi run -e default pytest -q tests/programmer/test_dense_joint_trajectory.py
pixi install -e motion                          # Linux, NVIDIA GPU with CUDA 12
pixi run -e motion pytest -q tests/motion_planner
```

Always pass `-e`. Several tasks exist in more than one environment, and the motion tests need the CUDA `motion` environment. [Run the weld planner](https://advancedmetalresearch.com/docs/guides/run-the-weld-planner) covers the environments.

## Pendants and deploy module

```bash
(cd steamdeck/real/v4 && go build ./... && go vet ./... && go test -race -count=1 -p=1 ./...)
make -C steamdeck/real/v4/steamdeck all
(cd offline-programming/v1/ui && npm ci) && make -C steamdeck/real/v5 -j4 all check
```

The v5 pendant needs Qt 5.15 and Assimp. No CI workflow builds it. See [Build and install the pendant](https://advancedmetalresearch.com/docs/guides/install-the-pendant).

## Quality checks

Formatting and lint runners live in `quality/`. See [Code style and quality](https://advancedmetalresearch.com/docs/contributing/code-style).

## CI

All workflows run on GitHub-hosted Ubuntu runners, on pull requests and pushes that touch their paths.

| Workflow | Runs | Paths |
|---|---|---|
| `rt-core.yml` | On a sparse checkout of only `rt-core/` and `robot_description/`: `make test` (native lane), `make test-sanitize` (sanitize lane), and an IgH lane that builds the userspace library, `make live control`, `rtctl validate --backend live`, and a runtime package | `rt-core/**`, `robot_description/**`, the dense daemon, `steamdeck/real/v4/**` |
| `rt-core-consumers.yml` | Cartesian and dense motion server builds and tests; the virtual bridge and dev-stack smoke test; the v4 pendant; OLP `go test` and UI lint and tests; deploy-module package tests | rt-core, motion servers, OLP, `steamdeck/**`, `dev-stack.sh` |
| `virtual-deck-ui.yml` | Virtual pendant UI tests, build, strict `tsc`, trailing-whitespace check | `steamdeck/virtual/**` |
| `weld-planner-verifier.yml` | The dense trajectory contract test (default env), the M6 verifier contract with CPU PyTorch, and the sphere tool build | weld planner, sphere tool |
| `steamdeck-client-contracts.yml` | Pendant client authority contracts | `steamdeck/real/v4/**` |
| `robot-runtime-contracts.yml` | Mesh-daemon restart-ordering tests | `daemon/v1/**` |
| `quality-pilot.yml` | `node quality/quality.mjs check` and `quality.mjs workflows` (actionlint) | `quality/**`, `.github/workflows/**` |
| `deployment-surface.yml` | Advisory deployment-surface report | deployment manifests, units |
| `triage-labels.yml` | Issue and pull request labelling | — |

What CI does **not** cover: the v5 pendant, `mujoco-sim`, the Tesseract environment's self-test, the full weld planner suite (only the contract tests above run, on CPU), and anything on real hardware. A green CI run says nothing about powered motion.

## Sources

Written from these files in the RosieOS repository (https://github.com/advanced-metal-research/RosieOS):

- `rt-core/Makefile:1-50,138,224,276-350`
- `rt-core/tools/build-igh-userlib.sh:9-40`
- `.github/workflows/rt-core.yml:1-72`
- `.github/workflows/rt-core-consumers.yml:1-135`
- `.github/workflows/virtual-deck-ui.yml:1-45`
- `.github/workflows/weld-planner-verifier.yml:1-112`
- `.github/workflows/robot-runtime-contracts.yml:36-60`
- `.github/workflows/steamdeck-client-contracts.yml:38-70`
- `.github/workflows/quality-pilot.yml:1-33`
- `.github/workflows/deployment-surface.yml:1-25`
- `.github/workflows/triage-labels.yml:1-40`
- `motion-server/v1/Makefile:26-60`
- `motion-server/joint-trajectory/v1/Makefile:16-70`
- `weld_planner/v1/pixi.toml:9-43,60-128`
- `robot_description/tools/manifest.py:13-18`
- `offline-programming/v1/ui/package.json`
