#!/usr/bin/env bash # Phase 0 aggregator — runs every check in .github/workflows/ci.yml locally and # reports a single pass/fail summary. Use this as the "did I set everything up # right?" one-liner for new teammates. # # Pieces run, in order: # 1. Rule R2 boundary (scripts/check_pdfium_boundary.sh) # 2. Engine (cmake configure + build + ctest) # 3. Gateway (pip install -e [dev] + ruff + pytest, in gateway/) # 4. WASM hello-world (cmake configure + build + node hello.test.mjs) # # Skips (not failures) are reported when a toolchain isn't present — e.g. no # Emscripten on PATH skips WASM. Each piece runs independently; a failure in # one does not abort later pieces. # # Usage: # ./scripts/test_phase0.sh # auto-pick preset by OS # ./scripts/test_phase0.sh linux-asan # override preset # PHASE0_BINARY_DIR=/tmp/build ./scripts/test_phase0.sh # SKIP_WASM=1 ./scripts/test_phase0.sh # # Compatible with bash 3.2 (macOS default) — uses parallel indexed arrays # instead of associative arrays. set -uo pipefail # NOT -e: we want to collect failures, not abort on first REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${REPO_ROOT}" PRESET="${1:-}" if [[ -z "${PRESET}" ]]; then case "$(uname -s)" in Darwin*) PRESET=macos-debug ;; *) PRESET=linux-debug ;; esac fi BINARY_DIR="${PHASE0_BINARY_DIR:-}" STEPS=() STATUSES=() LAST_STATUS=PASS record() { STEPS+=("$1") STATUSES+=("$2") LAST_STATUS="$2" } run_step() { local name="$1"; shift echo echo ">> [${name}]" if "$@"; then record "${name}" PASS echo " [${name}] OK" else local rc=$? record "${name}" FAIL echo " [${name}] FAILED (exit ${rc})" fi } skip_step() { local name="$1" local reason="$2" echo echo ">> [${name}] SKIPPED: ${reason}" record "${name}" SKIP } echo "Phase 0 aggregator — preset='${PRESET}'${BINARY_DIR:+ binaryDir='${BINARY_DIR}'}" # --- 1. Rule R2 boundary ----------------------------------------------------- run_step 'R2 boundary' bash "${REPO_ROOT}/scripts/check_pdfium_boundary.sh" # --- 2. Engine --------------------------------------------------------------- engine_configure() { if [[ -n "${BINARY_DIR}" ]]; then cmake --preset "${PRESET}" -B "${BINARY_DIR}"; else cmake --preset "${PRESET}"; fi } engine_build() { if [[ -n "${BINARY_DIR}" ]]; then cmake --build "${BINARY_DIR}"; else cmake --build --preset "${PRESET}"; fi } engine_test() { if [[ -n "${BINARY_DIR}" ]]; then ctest --test-dir "${BINARY_DIR}" --output-on-failure; else ctest --preset "${PRESET}"; fi } if [[ "${SKIP_ENGINE:-0}" == "1" ]]; then skip_step 'engine' 'requested via SKIP_ENGINE=1' elif [[ -z "${VCPKG_ROOT:-}" ]]; then skip_step 'engine' 'VCPKG_ROOT not set — run scripts/bootstrap.sh first (and re-source your shell rc)' else run_step 'engine configure' engine_configure if [[ "${LAST_STATUS}" == PASS ]]; then run_step 'engine build' engine_build if [[ "${LAST_STATUS}" == PASS ]]; then run_step 'engine test' engine_test else skip_step 'engine test' 'build failed' fi else skip_step 'engine build' 'configure failed' skip_step 'engine test' 'configure failed' fi fi # --- 3. Gateway -------------------------------------------------------------- PY="" if command -v python3 >/dev/null 2>&1; then PY="$(command -v python3)"; elif command -v python >/dev/null 2>&1; then PY="$(command -v python)"; fi if [[ "${SKIP_GATEWAY:-0}" == "1" ]]; then skip_step 'gateway' 'requested via SKIP_GATEWAY=1' elif [[ -z "${PY}" ]]; then skip_step 'gateway' 'python/python3 not on PATH' else pushd gateway >/dev/null run_step 'gateway install' "${PY}" -m pip install --quiet -e ".[dev]" if [[ "${LAST_STATUS}" == PASS ]]; then run_step 'gateway lint' "${PY}" -m ruff check . run_step 'gateway format' "${PY}" -m ruff format --check . run_step 'gateway pytest' "${PY}" -m pytest fi popd >/dev/null fi # --- 4. WASM hello-world ----------------------------------------------------- if [[ "${SKIP_WASM:-0}" == "1" ]]; then skip_step 'wasm' 'requested via SKIP_WASM=1' elif ! command -v emcc >/dev/null 2>&1; then skip_step 'wasm' 'emcc not on PATH — source emsdk_env.sh first' elif ! command -v node >/dev/null 2>&1; then skip_step 'wasm' 'node not on PATH' else run_step 'wasm configure' cmake --preset wasm if [[ "${LAST_STATUS}" == PASS ]]; then run_step 'wasm build' cmake --build --preset wasm if [[ "${LAST_STATUS}" == PASS ]]; then run_step 'wasm test' node wasm/hello.test.mjs else skip_step 'wasm test' 'build failed' fi else skip_step 'wasm build' 'configure failed' skip_step 'wasm test' 'configure failed' fi fi # --- Summary ---------------------------------------------------------------- echo echo '================ Phase 0 summary ================' pad=0 for s in "${STEPS[@]}"; do if (( ${#s} > pad )); then pad=${#s}; fi done any_fail=0 for i in "${!STEPS[@]}"; do printf " %-${pad}s %s\n" "${STEPS[$i]}" "${STATUSES[$i]}" if [[ "${STATUSES[$i]}" == FAIL ]]; then any_fail=1; fi done echo '=================================================' if [[ "${any_fail}" -ne 0 ]]; then echo 'Phase 0: FAIL' exit 1 fi echo 'Phase 0: PASS' exit 0