Skip to content

Commit dbf7828

Browse files
committed
ci: build via docker compose + headless sim smoke test
Build job was broken (referenced deleted docker/Dockerfile.agents and fed Dockerfile.o3de an ignored ROS2_IMAGE build-arg). Replace the hand-maintained build-push-action chain with 'docker compose build/push ros2 o3de' — the same ros2->o3de graph devs build locally, so it can't drift. Per-branch tags via AMM_IMG_SUFFIX (empty locally). Add a second job (needs: build-and-push) that boots the freshly-built o3de image headless with O3DE's null renderer (--rhi=null, no GPU/display) and passes iff /clock ticks — proving the image boots, loads the level, steps physics, and publishes over ROS 2. Camera/rendered sensors need a DRI3 display and are out of scope. Logic lives in scripts/sim_smoke.sh (pixi task: sim-smoke).
1 parent f3d5e56 commit dbf7828

4 files changed

Lines changed: 99 additions & 33 deletions

File tree

.github/workflows/build_and_push_docker.yaml

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ on:
77
- development
88
workflow_dispatch:
99

10+
# Development builds get a -development image suffix so they never clobber main's
11+
# :latest. Empty on every other branch. Consumed by docker/compose.yaml image names.
12+
env:
13+
SUFFIX: ${{ github.ref_name == 'development' && '-development' || '' }}
14+
1015
jobs:
1116
build-and-push:
1217
runs-on: self-hosted
@@ -20,36 +25,31 @@ jobs:
2025
username: ${{ secrets.DOCKERHUB_USERNAME }}
2126
password: ${{ secrets.DOCKERHUB_TOKEN }}
2227

23-
- name: Set up Docker Buildx
24-
uses: docker/setup-buildx-action@v3
25-
26-
- name: Build and push ROS 2 image
27-
uses: docker/build-push-action@v6
28-
with:
29-
context: .
30-
file: docker/Dockerfile.ros2
31-
push: true
32-
tags: |
33-
robotecai/mobile-manipulator-demo-ros2${{ github.ref_name == 'development' && '-development' || '' }}:latest
28+
# Build the ros2 -> o3de chain straight from docker/compose.yaml (the same
29+
# graph devs build locally, via additional_contexts). Scoped to these two:
30+
# `demo` is the heavy GPU+NPU single-PC image, not published here.
31+
- name: Build and push base images (ros2, o3de)
32+
env:
33+
AMM_IMG_SUFFIX: ${{ env.SUFFIX }}
34+
run: |
35+
docker compose -f docker/compose.yaml build ros2 o3de
36+
docker compose -f docker/compose.yaml push ros2 o3de
3437
35-
- name: Build and push Agents image
36-
uses: docker/build-push-action@v6
38+
# Headless sim liveness gate. Boots O3DE with the null renderer (no GPU/display)
39+
# and passes iff /clock ticks — proves the image actually boots, loads the level,
40+
# steps physics, and publishes over ROS 2. See scripts/sim_smoke.sh.
41+
smoke-test:
42+
needs: build-and-push
43+
runs-on: self-hosted
44+
steps:
45+
- name: Log in to Docker Hub
46+
uses: docker/login-action@v3
3747
with:
38-
context: .
39-
file: docker/Dockerfile.agents
40-
push: true
41-
build-args: |
42-
ROS2_IMAGE=robotecai/mobile-manipulator-demo-ros2${{ github.ref_name == 'development' && '-development' || '' }}:latest
43-
tags: |
44-
robotecai/mobile-manipulator-demo-agents${{ github.ref_name == 'development' && '-development' || '' }}:latest
48+
username: ${{ secrets.DOCKERHUB_USERNAME }}
49+
password: ${{ secrets.DOCKERHUB_TOKEN }}
4550

46-
- name: Build and push O3DE image
47-
uses: docker/build-push-action@v6
48-
with:
49-
context: .
50-
file: docker/Dockerfile.o3de
51-
push: true
52-
build-args: |
53-
ROS2_IMAGE=robotecai/mobile-manipulator-demo-ros2${{ github.ref_name == 'development' && '-development' || '' }}:latest
54-
tags: |
55-
robotecai/mobile-manipulator-demo-o3de${{ github.ref_name == 'development' && '-development' || '' }}:latest
51+
- name: Headless sim smoke test (/clock)
52+
run: |
53+
IMAGE="robotecai/agentic-mobile-manipulator-o3de${SUFFIX}:latest"
54+
docker pull "$IMAGE"
55+
docker run --rm --entrypoint pixi "$IMAGE" run sim-smoke

docker/compose.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@
2828
services:
2929
# ── Base: RoboStack ROS 2 + built workspace ────────────────────────────────
3030
ros2:
31-
image: robotecai/agentic-mobile-manipulator-ros2:latest
31+
# AMM_IMG_SUFFIX lets CI publish per-branch tags (e.g. -development); empty locally.
32+
image: robotecai/agentic-mobile-manipulator-ros2${AMM_IMG_SUFFIX:-}:latest
3233
build:
3334
context: ..
3435
dockerfile: docker/Dockerfile.ros2
3536

3637
# ── Base: + O3DE SDK and built simulation ──────────────────────────────────
3738
o3de:
38-
image: robotecai/agentic-mobile-manipulator-o3de:latest
39+
image: robotecai/agentic-mobile-manipulator-o3de${AMM_IMG_SUFFIX:-}:latest
3940
build:
4041
context: ..
4142
dockerfile: docker/Dockerfile.o3de
@@ -44,7 +45,7 @@ services:
4445

4546
# ── The demo: + agents, local inference engines; runs the whole stack ───────
4647
demo:
47-
image: robotecai/agentic-mobile-manipulator:latest
48+
image: robotecai/agentic-mobile-manipulator${AMM_IMG_SUFFIX:-}:latest
4849
build:
4950
context: ..
5051
dockerfile: docker/Dockerfile.demo

pixi.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ description = "Launch O3DE simulation in its own tmux session"
162162
cmd = "$DEMO_ROOT/sim/build/linux/bin/profile/MobileManipulatorDemo.GameLauncher -bg_ConnectToAssetProcessor=0"
163163
description = "Launch O3DE simulation in the foreground (no tmux)"
164164

165+
[feature.sim.tasks.sim-smoke]
166+
cmd = "bash scripts/sim_smoke.sh"
167+
description = "Headless sim smoke test: boot with --rhi=null, pass if /clock ticks (no GPU/display)"
168+
165169
[feature.sim.tasks.editor]
166170
cmd = "$O3DE_ENGINE_PATH/bin/Linux/profile/Default/Editor --project-path $DEMO_ROOT/sim"
167171
description = "Launch O3DE Editor from SDK (project must be built first)"

scripts/sim_smoke.sh

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env bash
2+
# Headless smoke test for the O3DE sim.
3+
#
4+
# Boots the GameLauncher with the null renderer (--rhi=null) — no GPU, no X
5+
# display needed — and passes if the simulation is actually *ticking*, proven by
6+
# catching a message on /clock within a timeout. That single check exercises the
7+
# whole cold path: binary runs, assets + level load, PhysX ticks, and the ROS 2
8+
# bridge publishes.
9+
#
10+
# NOT checked: camera / rendered sensors. Those need the Atom (Vulkan) renderer,
11+
# which headless needs a DRI3-capable display for — Xvfb doesn't provide DRI3, so
12+
# the GPU+Vulkan path wedges on swapchain creation. rhi=null has no renderer at
13+
# all, so camera topics are advertised but never publish. Verifying rendered
14+
# output headless is a separate, unsolved problem; keep it out of the fast gate.
15+
#
16+
# Run: pixi run sim-smoke
17+
# In Docker: docker run --rm --entrypoint pixi <o3de-image> run sim-smoke
18+
# Tune: SIM_SMOKE_TIMEOUT=120 pixi run sim-smoke
19+
# SIM_SMOKE_TOPIC=/joint_states SIM_SMOKE_TYPE=sensor_msgs/msg/JointState pixi run sim-smoke
20+
set -uo pipefail
21+
22+
# Hermetic: only ever see our own sim on loopback, so a neighbouring sim on a
23+
# shared CI runner can't cause a false pass.
24+
export ROS_LOCALHOST_ONLY=1
25+
26+
LAUNCHER="${DEMO_ROOT:-$PWD}/sim/build/linux/bin/profile/MobileManipulatorDemo.GameLauncher"
27+
TIMEOUT="${SIM_SMOKE_TIMEOUT:-90}" # boot + first message; rhi=null is faster than Vulkan
28+
TOPIC="${SIM_SMOKE_TOPIC:-/clock}"
29+
# Type must be explicit: with no publisher up yet, `ros2 topic echo` can't infer
30+
# the type from the graph and bails instantly instead of waiting.
31+
TYPE="${SIM_SMOKE_TYPE:-rosgraph_msgs/msg/Clock}"
32+
LOG="$(mktemp)"
33+
34+
[ -x "$LAUNCHER" ] || { echo "[FAIL] launcher not built: $LAUNCHER"; exit 1; }
35+
36+
# GameLauncher ignores SIGINT (won't stop on Ctrl+C), so hard-kill on exit.
37+
# Kill the captured PID, plus a pkill by full command line as a fallback (the
38+
# comm name isn't reliably set during early boot). pkill -f is safe: this shell's
39+
# own argv is just "bash scripts/sim_smoke.sh", so it can't match itself.
40+
LAUNCHER_PID=""
41+
cleanup() {
42+
[ -n "$LAUNCHER_PID" ] && kill -9 "$LAUNCHER_PID" 2>/dev/null
43+
pkill -9 -f 'MobileManipulatorDemo.GameLauncher' 2>/dev/null
44+
}
45+
trap cleanup EXIT
46+
47+
echo "[smoke] launching sim headless (--rhi=null)"
48+
env -u DISPLAY "$LAUNCHER" -bg_ConnectToAssetProcessor=0 --rhi=null >"$LOG" 2>&1 &
49+
LAUNCHER_PID=$!
50+
51+
# /clock is published BEST_EFFORT; a default (reliable) subscriber gets nothing.
52+
echo "[smoke] waiting up to ${TIMEOUT}s for a message on ${TOPIC} (${TYPE})"
53+
if timeout "$TIMEOUT" ros2 topic echo --once --qos-reliability best_effort "$TOPIC" "$TYPE" >/dev/null 2>&1; then
54+
echo "[PASS] ${TOPIC} is publishing — sim ticks headless"
55+
exit 0
56+
fi
57+
58+
echo "[FAIL] no message on ${TOPIC} within ${TIMEOUT}s"
59+
echo "----- last 30 log lines -----"
60+
tail -30 "$LOG"
61+
exit 1

0 commit comments

Comments
 (0)