SafeAPI RBC 2oo2 Test Environment
End-to-end Robot Framework test suite + Docker Compose stack for the safeAPIRBC2oo2 system
Loading...
Searching...
No Matches
Build guide: source → install package

How to go from a clean checkout of the SAPI workspace to a verified, installable package for the safeAPIRBC2oo2 RBC application (roles A/B/ Gateway). Covers the plain host path and the Docker path. For test-suite usage (Robot Framework, the 11-container stack), see doc/design/DESIGN.md and this project's own etc/scripts/{start_docker,start_containers}.sh instead - this guide stops at "I have a package/image," not "I ran the end-to-end test suite."

The dependency chain

safeAPIFreamwork (the library: timers, IPC, memory, logging, appmanager, ...)
|
+---------------------+----------------------+
| | |
safeAPIBackendPosix safeAPIRBC2oo2GP safeAPIRBC2oo2GA
(POSIX OAL backend) (the exe: A/B/Gateway) (IL/CTC logic, .so)
| | |
+---------------------+----------------------+
|
safeAPIRBC2oo2SA
(site-data libs, installer packaging,
Docker image)

safeAPIRBC2oo2GP is the one that actually links everything (Framework, Backend, SA's sa::site::gp, and GA's shared library) into safeapi_example/safeapi_gateway_c - building GP's own CMakeLists.txt transitively builds every project above it via add_subdirectory() defaults, exactly like a plain local build. safeAPIRBC2oo2SA is where the final, installable output gets assembled: it doesn't relink anything, it packages GP's already-built binaries (plus GA's .so) into a self-extracting installer, and - for Docker - actually runs that installer inside the image build as a live proof the install works. See each project's own CLAUDE.md for the full design story (the GP<->GA function-pointer interface, why SA reaches into GP's/GA's headers via a sibling path instead of a Conan requires, etc.) - this guide is mechanics, not design.

Prerequisites

Starting from nothing (no sibling checkouts on disk yet)? Run ../bootstrapWorkspace.sh from the SAPI workspace root first - it gh repo clones every subproject that's missing (this root isn't itself a git repo; each subproject is its own independent GitHub repo, see root CLAUDE.md), then builds the native C/C++ chain + Python sim wheels for your host platform by delegating to setupLocalTestEnv.sh build below. Everything past this point assumes the checkouts already exist.

One-time, per toolchain you intend to use (skip whichever you don't need - build_toolchains.sh skips gracefully, with a message, for anything not set up):

cd ../SAPIMacOSToolchain && ./setup.sh # native macOS (Xcode/AppleClang)
cd ../LinuxMacOSToolchain && ./setup.sh # cross-compile Linux glibc/musl, 4 arches
cd ../FreeRTOSToolchain && ./setup.sh # bare-metal ARM (framework only, see below)

QNXMacOSToolchain is scaffold-only (needs a licensed SDP you provide yourself) - not part of this guide.

Step 1 - build the binaries

Quick local dev loop (one platform, the one you're on)

cd ../safeAPIRBC2oo2GP
cmake -S . -B build -DSAFEAPI_WARNINGS_AS_ERRORS=OFF
cmake --build build -j
cmake --install build --prefix .
ctest --test-dir build --output-on-failure

This is what you want while iterating on source - add_subdirectory() resolves Framework/Backend/SA/GA against sibling checkouts directly, no toolchain files, no installers. Produces dist/<platform>/{bin/{safeapi_example,safeapi_gateway_c},lib/libsafeapi_rbc2oo2_ga.{dylib,so}}.

Every platform, or a specific subset

cd etc/scripts
./build.sh platforms # list known targets + which are ready right now
./build.sh all # every configured target
./build.sh platform macos,linux-x86_64-glibc # just these two

(build.sh wraps build_toolchains.sh - build_toolchains.sh -t <targets> directly works too, same thing.) Targets: macos, linux-{aarch64,arm,x86_64,i686}-{glibc,musl}, freertos. FreeRTOS only builds the framework (no POSIX backend for bare-metal, out of scope for the app) - every other target builds Framework and GP/Backend/GA/SA together. Output lands the same place as the quick-loop path, just once per target under each project's own dist/<platform>/.

Optional: compiler-diversity builds (ADR-008)

For genuinely different-compiler A/B channels (gcc vs clang) on the SAME platform:

cd etc/scripts
./build_channels.sh # both channels
./build_channels.sh -c A # channel A only

Produces safeAPIRBC2oo2GP/dist/<platform>/{A/bin/safeapi_A2oo2,B/bin/safeapi_B2oo2,Gateway/bin/safeapi_gateway_c}.

Step 2 - package it (safeAPIRBC2oo2SA)

Shortcut: build.sh package (config-driven) / build.sh package -a (everything that's built) from etc/scripts/ wraps Step 2 too - it just delegates to safeAPIRBC2oo2SA/build_installers.sh below, so you don't have to cd there yourself. build.sh release chains Step 1 (all) and Step 2 (package -a) into one command - source checkout straight to every installer build_toolchains.sh can produce.

packaging.conf decides, per role (A, B, Gateway), which already-built platform+channel combination goes into the package - this is the thing to edit, not the packaging script itself:

cd ../safeAPIRBC2oo2SA
$EDITOR etc/config/packaging.conf
ROLE_A_TARGET=linux-x86_64-glibc
ROLE_A_CHANNEL= # empty = plain build; "A" or "B" = a build_channels.sh output
ROLE_B_TARGET=linux-x86_64-glibc
ROLE_B_CHANNEL=
ROLE_GATEWAY_TARGET=linux-x86_64-glibc

A and B may target genuinely different platforms too (hardware/OS diversity, not just compiler diversity) - just point ROLE_A_TARGET/ ROLE_B_TARGET at different ids. Then:

./build_installers.sh # uses etc/config/packaging.conf
./build_installers.sh -c /path/to/other.conf # or an explicit override
./build_installers.sh -a # ALL possible installers instead - see below

Errors out (doesn't silently skip) naming exactly which role/target/ channel binary is missing and which script to run first if Step 1 wasn't done for something the config references. Produces one installer per DISTINCT target referenced by the config, under dist/install/:

-a is the alternative to hand-editing packaging.conf: it sweeps every known target and packages a plain, same-platform installer for whichever ones you've actually built in Step 1, silently skipping (a one-line message, not an error) anything you haven't - useful right after a full build.sh all when you just want "one installer per platform I built," rather than the config's deliberate, strict, per-role selection.

dist/install/safeapi-rbc2oo2-<target>-installer.sh

Step 3 - verify the package

Don't trust a package you haven't run. This is the same command safeAPIRBC2oo2SA/Dockerfile runs automatically inside every image build (see below) - do it by hand too when packaging outside Docker:

./dist/install/safeapi-rbc2oo2-<target>-installer.sh --prefix=/tmp/sapi-install-test
/tmp/sapi-install-test/bin/safeapi_A A WEST # or whichever role/site you built

The installer's own postcheck already runs every packaged binary with no args and checks for a usage: line (proves the dynamic linker resolved against the target host's libc and against the bundled libsafeapi_rbc2oo2_ga) - a clean installer run with no error: lines is itself a real functional check, not just "the files exist."

Clean / distclean

cd ../safeAPIRBC2oo2TestEnv/etc/scripts
./build.sh clean # rm build/ across Framework/Backend/GP/GA/SA - keeps dist/
./build.sh distclean # rm build/ AND dist/ - full reset

Alternative: Docker (build + package + install-verify, one command)

cd .. # SAPI workspace root
docker build -f safeAPIRBC2oo2SA/Dockerfile -t safeapi-rbc2oo2 .

Does everything above in one shot for whichever glibc arch the build is actually running as (native, or buildx/QEMU target): builds GP (which pulls in Framework/Backend/GA), generates a Docker-specific packaging.conf, packages, and runs the installer inside the build - a broken install fails docker build itself, not just a later docker run. Or via Compose, which does this for all six RBC containers plus the five sim images at once:

cd ../safeAPIRBC2oo2TestEnv
docker compose build # or: docker compose up --build

Shortcut: etc/scripts/setupDockerTestEnv.sh wraps this whole page (Step 1's build.sh release + this native-platform image build) into one command - see that script's own header. Or, if you only want the image build step on its own without Step 1: builds all 4 native-platform images (rbc2oo2/train/il/ctc) via docker compose build under the hood, one representative service per image (the six RBC role services all share the same safeapi-rbc2oo2 image, so building all of them would just be redundant, layer-cached rebuilds):

./setupDockerTestEnv.sh # everything: build.sh release + all 4 images + product structure
./setupDockerTestEnv.sh -i rbc2oo2,ctc # just these two images (still runs build.sh release first)

For cross-architecture images (buildx + QEMU, not just this host's own platform) see etc/scripts/build_multiarch.sh instead - a separate, slower, opt-in script, not part of build.sh.

Starting a subset (Compose profiles)

Every service is tagged with one of five group profiles: west (c-west/a-west/b-west), east (c-east/a-east/b-east), trainsims (train-west/train-east), ilsims (il-west/il-east), ctcsims (ctc). --profile is a top-level docker compose flag - it goes before the subcommand, not after:

docker compose --profile west up --build # just the WEST RBC group
docker compose --profile trainsims up --build # just the train sims

etc/scripts/start_containers.sh/stop_containers.sh take a --profile too (./start_containers.sh --profile west) and place it correctly for you. With no --profile at all, both scripts default to all five groups (the full 11-container topology, unchanged from before profiles existed)

  • a bare docker compose up/down with no profile flags now resolves to an empty service set (Compose's own rule once every service has a profile), so anything driving the full stack needs to activate all five explicitly; start_containers.sh/stop_containers.sh/runDockerTests.sh/ robot/__init__.robot all already do this.

Cheat sheet

Goal Command
From scratch: clone every repo + build ../bootstrapWorkspace.sh (from the SAPI workspace root)
Iterate on source, one platform cmake --build build in safeAPIRBC2oo2GP
List known platforms + readiness safeAPIRBC2oo2TestEnv/etc/scripts/build.sh platforms
Build every platform build.sh all
Build specific platform(s) build.sh platform <t1,t2,...>
Compiler-diverse A/B (same platform) build_channels.sh
Package per packaging.conf build_installers.sh (or build.sh package)
Package everything that's built build_installers.sh -a (or build.sh package -a)
Source to installers, one shot build.sh release
Verify a package run the generated .sh with --prefix=, then launch a binary
Reset build state build.sh clean / build.sh distclean
One-shot build+package+verify docker build -f safeAPIRBC2oo2SA/Dockerfile ...
Source to Docker test env, one shot setupDockerTestEnv.sh (build.sh release + Docker images + product structure)
Build native-platform Docker image(s) only docker compose build <service> (see setupDockerTestEnv.sh's own service map)
Cross-architecture Docker image(s) build_multiarch.sh
Full 11-container stack docker compose up --build from this project
Just one group (west/east/trainsims/ilsims/ctcsims) docker compose --profile <group> up --build
Run Robot suites against Docker runDockerTests.sh (--full adds fault_injection/)
Run Robot rbc_scenario against a local (non-Docker) env runLocalTests.sh (starts/stops the env itself)