Skip to content

Cross Compiling for Other Boards

vladyslav-kinzerskiy edited this page Jun 15, 2026 · 1 revision

Cross-Compiling for Other Boards

Theia cross-compiles its C++ runtime (the supervisor) to other Linux targets — Raspberry Pi 4, BeagleBone AI-64, any Debian/aarch64 board — and packs the result as a .deb. This page explains the moving parts and walks through targeting a board the project doesn't ship a config for.

The in-repo --config=rpi4 is an example target, not special. The same mechanism cross-compiles to any board once you supply its toolchain and rootfs.

A note on the naming (--config=rpi4 vs --config=linux)

These two config names are convenient shorthands, but they conflate three independent concerns. Read them as:

Shorthand Really means
--config=linux host build — build on and for this machine (typically host-amd64)
--config=rpi4 cross build — build on the host, for a different target (target-aarch64) using an exchangeable rootfs + toolchain

A cross-compile is the composition of three exchangeable pieces — the "linux" and "rpi4" labels just bundle a particular choice of each:

Piece What it is rpi4 example
1. Platform the target CPU + OS constraints Bazel selects on //rules/config:rpi4 = cpu:aarch64, os:linux
2. Toolchain the cross-compiler (gcc triple) aarch64-linux-gnu-gcc (apt: gcc-aarch64-linux-gnu)
3. Sysroot (rootfs) the target's libraries + headers to link against a Debian bookworm aarch64 rootfs at third_party/sysroot/rpi4

Same CPU family → same platform + toolchain; only the sysroot changes. That is the key to retargeting: an aarch64 board built on a different Debian release reuses pieces 1 and 2 and swaps only piece 3.

What the runtime deb contains

Just the supervisor binary (/opt/theia/bin/supervisor) — the runtime fabric an app system runs under. It is headless (no GUI), so desktop environments on the board (XFCE, GNOME, …) are irrelevant to the build.


Worked example: a .deb for the BeagleBone AI-64 (Debian 13.5)

The BeagleBone AI-64 (TI TDA4VM / J721E, Cortex-A72) is aarch64 — the same CPU family as the Pi 4. So it reuses the rpi4 platform + toolchain unchanged; the only difference is the rootfs: Debian 13.5 (trixie) instead of the Pi's bookworm.

target:   BeagleBone AI-64 (Cortex-A72, aarch64)
OS:       Debian 13.5 "trixie" (the XFCE image — desktop is irrelevant here)
reuse:    platform //rules/config:rpi4  +  aarch64-linux-gnu toolchain
change:   the sysroot → a trixie aarch64 rootfs

1. Build a trixie aarch64 sysroot

third_party/sysroot/setup_rpi4.sh bootstraps a bookworm rootfs. Make a trixie one the same way — debootstrap the trixie suite with the libs the runtime links, plus nanopb (the runtime/protos #include <pb.h> and link libprotobuf-nanopb.a, which isn't reliably in the Debian arm64 archive):

SR=$HOME/sysroots/bbai64-trixie
sudo debootstrap --arch=arm64 --foreign --variant=minbase \
  --include=libc6-dev,libstdc++-13-dev,libprotobuf-dev,libgrpc++-dev,libabsl-dev \
  trixie "$SR" http://deb.debian.org/debian
sudo cp /usr/bin/qemu-aarch64-static "$SR/usr/bin/"
sudo chroot "$SR" /debootstrap/debootstrap --second-stage

# stage nanopb (3 tiny .c → aarch64 lib + headers)
np=$(mktemp -d); curl -fsSL https://github.com/nanopb/nanopb/archive/refs/tags/0.4.7.tar.gz | tar xz -C "$np"
( cd "$np"/nanopb-0.4.7 && for c in pb_common pb_encode pb_decode; do
    aarch64-linux-gnu-gcc -c -O2 -fPIC -I. $c.c -o $c.o; done
  aarch64-linux-gnu-ar rcs libprotobuf-nanopb.a pb_*.o )
sudo cp "$np"/nanopb-0.4.7/pb*.h "$SR/usr/include/"
sudo cp "$np"/nanopb-0.4.7/libprotobuf-nanopb.a "$SR/usr/lib/aarch64-linux-gnu/"

(Trixie ships newer libs than bookworm — libstdc++6/libprotobuf sonames differ. That's fine: cross-linking against the trixie rootfs binds the trixie sonames, which is exactly what the board runs.)

2. Cross-compile against that sysroot

The @rpi4_sysroot repo rule reads THEIA_RPI4_SYSROOT — point it at the trixie rootfs and the same toolchain links against it:

THEIA_RPI4_SYSROOT="$SR" bazel build //platform/supervisor/main:supervisor --config=rpi4
file bazel-bin/platform/supervisor/main/supervisor      # → ELF ARM aarch64
qemu-aarch64-static -L "$SR" bazel-bin/platform/supervisor/main/supervisor  # smoke-run

3. Pack the .deb

THEIA_RPI4_SYSROOT="$SR" ./.github/scripts/build-rpi4-runtime-deb.sh
# → dist/debian/theia-runtime/theia-runtime_0.1.0_arm64.deb

Edit the script's Depends: to the trixie sonames (e.g. the newer libstdc++6 / libprotobuf versions the binary links — dpkg-shlibdeps on the board, or read them from the rootfs), then dpkg -i on the BeagleBone.

Targeting a different CPU family (Pi Zero, Jetson, RISC-V…)

A non-aarch64 board needs all three pieces, not just the rootfs:

  1. Platform — add a //rules/config:<board> with the right cpu: constraint (copy the rpi4 stanza in rules/config/BUILD.bazel).
  2. Toolchain — add a toolchains/<triple>/ mirroring toolchains/aarch64_linux_gnu/ with that board's cross-gcc triple (e.g. arm-linux-gnueabihf for armv7, riscv64-linux-gnu for RISC-V), and register_toolchains it in MODULE.bazel.
  3. Sysroot — a Debian rootfs for that arch, as above.

Then bazel build … --platforms=//rules/config:<board>.

Status / scope

  • Runtime only. This cross-compiles the supervisor. The ARA services (com/per/sm/ucm/log/shwa) additionally need gRPC + etcd cross-built against the target rootfs — a larger lift, not covered here.
  • Not in CI. The release pipeline ships amd64 (22.04/24.04) debs; board debs are a user-driven cross-compile (the rootfs is large + board-specific). This page is the recipe.
  • No AF_TIPC under qemu. qemu-aarch64-static runs the binary but doesn't emulate TIPC sockets — the full supervisor↔FC smoke needs the real board.

See also: Deployment + Serialization, Tutorial.