#!/usr/bin/env bash set -euo pipefail COMPONENT="${1:-all}" # Build PROFILE controls the meson buildtype + sanitizer (and the default build # dir, so the variants never clobber each other): # asan (default) debug + AddressSanitizer → xce.build (unit-test CI) # debug debug, NO sanitizer (-O0 -g3 -fno-inline -fno-omit-frame- # pointer) → build.dbg (gdb / core dumps) # release release -O3, no sanitizer → build.release (soak / perf) # # Why `debug` exists (bug-1286): some crashes are heisenbugs that ASAN perturbs # away (different allocator/layout → no SIGSEGV) and that gdb-on-release turns # into a timeout/hang. The non-ASAN -O0 -g3 build reproduces them AND yields a # fully symbolized backtrace under gdb — the only way to root-cause that class. # # Arg parsing is backward-compatible: arg2 may be a profile keyword OR a legacy # build_dir; arg3 is an optional explicit build_dir override. case "${2:-}" in asan|debug|release) PROFILE="$2"; BUILD_DIR="${3:-}" ;; "") PROFILE="asan"; BUILD_DIR="" ;; *) PROFILE="asan"; BUILD_DIR="$2" ;; # legacy: arg2 = build_dir esac if [ -z "${BUILD_DIR:-}" ]; then case "$PROFILE" in asan) BUILD_DIR="xce.build" ;; debug) BUILD_DIR="build.dbg" ;; release) BUILD_DIR="build.release" ;; esac fi # RUN_TESTS: the unit suites are ASAN-oriented (leak detection) and the meson # test harness is fragile on this sandbox's overlay fs (bug-924 fdatasync). Only # the `asan` profile runs them; `debug`/`release` are BUILD-ONLY — their purpose # is a binary to drive under gdb / a soak, not a test run. Run a suite directly # against those binaries when you want it (it is overlay-fs sensitive). case "$PROFILE" in asan) MESON_OPTS=(--buildtype=debug -Db_sanitize=address); RUN_TESTS=1 ;; debug) MESON_OPTS=(--buildtype=debug -Db_sanitize=none); RUN_TESTS=0 ;; release) MESON_OPTS=(--buildtype=release -Db_sanitize=none); RUN_TESTS=0 ;; esac SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$SRC_DIR" usage() { echo "Usage: $0 [component] [profile|build_dir] [build_dir]" echo "Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin" echo "Profiles: asan (default), debug (non-ASAN -O0 -g3, for gdb/cores), release" exit 1 } setup_build() { echo "=== Clean build: $BUILD_DIR (profile=$PROFILE) ===" rm -rf "$BUILD_DIR" meson setup "$BUILD_DIR" "${MESON_OPTS[@]}" } case "$COMPONENT" in lib) setup_build echo "=== Building lib + tests ===" meson compile -C "$BUILD_DIR" xcelib_test if [ "$RUN_TESTS" = 1 ]; then echo "=== Running lib tests ===" meson test -C "$BUILD_DIR" "libxce test" --print-errorlogs fi ;; controller) setup_build echo "=== Building lib + controller + tests ===" meson compile -C "$BUILD_DIR" xce_controller controller_unit_test controller/xce_test if [ "$RUN_TESTS" = 1 ]; then echo "=== Running controller tests ===" meson test -C "$BUILD_DIR" --suite controller --print-errorlogs fi ;; scheduler) setup_build echo "=== Building lib + scheduler + tests ===" meson compile -C "$BUILD_DIR" xce_scheduler sched_test if [ "$RUN_TESTS" = 1 ]; then echo "=== Running scheduler tests ===" "./$BUILD_DIR/scheduler/testsuite/sched_test" fi ;; worker) setup_build echo "=== Building lib + worker ===" meson compile -C "$BUILD_DIR" xcecd xcecd_worker xrun echo "=== Worker built (no unit tests registered) ===" ;; apiserver) setup_build echo "=== Building lib + apiserver + xcecli ===" meson compile -C "$BUILD_DIR" apiserver xcecli if [ "$RUN_TESTS" = 1 ]; then echo "=== Running frontend Go unit tests ===" cd frontend && make test fi ;; xcecli) setup_build echo "=== Building xcecli ===" meson compile -C "$BUILD_DIR" xcecli if [ "$RUN_TESTS" = 1 ]; then echo "=== Running xcecli Go unit tests ===" cd frontend/xcecli && CGO_ENABLED=0 go test ./... -count=1 fi ;; xceadmin) # Python — no meson, dispatched by this case statement alone (design # review correction: xceadmin is deliberately NOT wired into # src/meson.build/meson.options, see .kiro/specs/admin-config-tool/ # design.md, File Structure Plan). Ignores $BUILD_DIR/$MESON_OPTS; # $PROFILE only selects editable vs. pinned pip install below. XCEADMIN_DIR="$SRC_DIR/xceadmin" SCHEMA_DST="$XCEADMIN_DIR/src/xcecommon/schema_files" echo "=== Syncing xceadmin bundled schemas ===" mkdir -p "$SCHEMA_DST/json" cp "$SRC_DIR/schema/controller.capnp" "$SCHEMA_DST/controller.capnp" cp "$SRC_DIR/schema/c.capnp" "$SCHEMA_DST/c.capnp" cp "$SRC_DIR/configs/schema/"*.schema.json "$SCHEMA_DST/json/" cd "$XCEADMIN_DIR" # nix's python3 site-packages lives under the read-only /nix/store, # so `pip install` (editable or pinned) can't target it directly — # it refuses with "externally-managed-environment" (PEP 668). Use a # throwaway venv that inherits the nix-provided pycapnp/pyyaml/ # jsonschema/click/pytest (--system-site-packages) so pip only ever # has to install xceadmin itself, never rebuild those from PyPI. # Recreated on every invocation for a clean, reproducible install — # same philosophy as setup_build's `rm -rf "$BUILD_DIR"` above. VENV_DIR="$XCEADMIN_DIR/.venv" rm -rf "$VENV_DIR" python3 -m venv --system-site-packages "$VENV_DIR" if [ "$PROFILE" = "release" ]; then echo "=== Building xceadmin Python package (pinned, non-editable) ===" "$VENV_DIR/bin/pip" install . --quiet else echo "=== Building xceadmin Python package (editable) ===" "$VENV_DIR/bin/pip" install -e . --quiet fi echo "=== Running xceadmin tests ===" "$VENV_DIR/bin/python" -m pytest tests/ -v echo "=== Done: xceadmin (profile=$PROFILE) ===" exit 0 ;; all) setup_build echo "=== Building all components ===" meson compile -C "$BUILD_DIR" if [ "$RUN_TESTS" = 1 ]; then echo "=== Running all C tests ===" meson test -C "$BUILD_DIR" --print-errorlogs echo "=== Running scheduler test (manual) ===" "./$BUILD_DIR/scheduler/testsuite/sched_test" echo "=== Running frontend Go unit tests ===" cd frontend && make test fi ;; *) usage ;; esac if [ "$RUN_TESTS" != 1 ]; then echo "=== Skipped tests (profile=$PROFILE — build only). Binary in $BUILD_DIR; run a suite directly, e.g. ASAN_OPTIONS=detect_leaks=0 ./$BUILD_DIR/controller/controller_unit_test ===" fi echo "=== Done: $COMPONENT (profile=$PROFILE, dir=$BUILD_DIR) ==="