yams/scripts/local-ci/pre-push-ci-gate.sh
trvon e1abefaf6f
ci(gate): distinguish transient infra from real failures
Retry setup.sh dependency resolution up to 3 times on Conan network
timeouts with backoff; report a distinct infra-retry (exit 77) when the
network failure persists, while real compile/test failures still fail
hard. Prevents a transient center2.conan.io timeout masquerading as a
code failure.
2026-08-12 20:09:40 -06:00

172 lines
4.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# yams/scripts/local-ci/pre-push-ci-gate.sh
#
# Blocking pre-push CI gate. Runs host sanitizer builds (ASan+UBSan and TSan)
# before push proceeds. Use --self-test to prove dispatch/host wiring without
# running the expensive build/test work.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
LOG_ROOT_DEFAULT="${REPO_ROOT}/build/local-ci/pre-push-ci-gate"
LOG_ROOT="${LOG_ROOT_DEFAULT}"
SELF_TEST=0
DRY_RUN=0
ASAN_ONLY=0
TSAN_ONLY=0
RUN_TAG="$(date -u +%Y%m%dT%H%M%SZ)-$$"
usage() {
cat <<'USAGE'
Usage: pre-push-ci-gate.sh [options]
Runs the blocking pre-push CI gate:
1. Native macOS ASan+UBSan Debug build + unit/integration tests.
2. Native macOS TSan Debug build + unit/integration tests.
Options:
--self-test Verify gate dispatch without expensive builds.
--dry-run Print commands without executing them.
--asan-only Run only the ASan+UBSan host lane.
--tsan-only Run only the TSan host lane.
--log-root DIR Log directory (default: build/local-ci/pre-push-ci-gate)
-h, --help Show this help
USAGE
}
log() { printf '\033[1;34m[pre-push-ci]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; }
while [ "$#" -gt 0 ]; do
case "$1" in
--self-test)
SELF_TEST=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--asan-only)
ASAN_ONLY=1
shift
;;
--tsan-only)
TSAN_ONLY=1
shift
;;
--log-root)
LOG_ROOT="${2:-}"
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
usage
exit 2
;;
esac
done
if [ "${ASAN_ONLY}" -eq 1 ] && [ "${TSAN_ONLY}" -eq 1 ]; then
fail "--asan-only and --tsan-only are mutually exclusive"
exit 2
fi
mkdir -p "${LOG_ROOT}"
SUMMARY="${LOG_ROOT}/summary-${RUN_TAG}.md"
: >"${SUMMARY}"
FAILURES=()
record_summary() { printf '%s\n' "$*" >>"${SUMMARY}"; }
run_step() {
local name="$1"
shift
local log_file="${LOG_ROOT}/${RUN_TAG}-${name//[^A-Za-z0-9_.-]/_}.log"
log "${name}"
record_summary "## ${name}"
record_summary ""
record_summary '```sh'
printf '%q ' "$@" >>"${SUMMARY}"
record_summary ""
record_summary '```'
if [ "${DRY_RUN}" -eq 1 ]; then
echo "DRY-RUN: $*" | tee "${log_file}"
record_summary "- Result: dry-run"
record_summary "- Log: ${log_file#"${REPO_ROOT}"/}"
record_summary ""
return 0
fi
set +e
"$@" > >(tee "${log_file}") 2> >(tee -a "${log_file}" >&2)
local rc=$?
set -e
if [ "${rc}" -eq 0 ]; then
ok "${name}"
record_summary "- Result: pass"
elif [ "${rc}" -eq 77 ]; then
fail "${name} infra-retry (${rc}); log: ${log_file}"
record_summary "- Result: infra-retry (transient network failure)"
FAILURES+=("${name} infra-retry (${rc})")
else
fail "${name} failed (${rc}); log: ${log_file}"
record_summary "- Result: fail (${rc})"
FAILURES+=("${name} (${rc})")
fi
record_summary "- Log: ${log_file#"${REPO_ROOT}"/}"
record_summary ""
}
record_summary "# YAMS pre-push CI gate"
record_summary ""
record_summary "- Started: ${RUN_TAG}"
record_summary "- Host: $(uname -s)/$(uname -m)"
record_summary "- Self-test: ${SELF_TEST}"
record_summary ""
asan_profile="asan"
tsan_profile="tsan"
if [ "${SELF_TEST}" -eq 1 ]; then
asan_profile="self-test"
tsan_profile="self-test"
fi
# Host-agnostic compile-only cross-compile smoke. Skips cleanly when no cross
# toolchain is installed; compiles a portability probe otherwise.
if [ "${SELF_TEST}" -eq 1 ]; then
run_step "cross-compile-smoke" bash "${SCRIPT_DIR}/cross-compile-smoke.sh" --self-test
else
run_step "cross-compile-smoke" bash "${SCRIPT_DIR}/cross-compile-smoke.sh"
fi
if [ "$(uname -s)" != "Darwin" ]; then
fail "host sanitizer pre-push gate currently requires a Darwin host"
FAILURES+=("host-sanitizer-unavailable (2)")
else
if [ "${TSAN_ONLY}" -ne 1 ]; then
run_step "native-macos-${asan_profile}" bash "${SCRIPT_DIR}/native-macos-lane.sh" --profile "${asan_profile}" --log-root "${LOG_ROOT}/asan"
fi
if [ "${ASAN_ONLY}" -ne 1 ]; then
run_step "native-macos-${tsan_profile}" bash "${SCRIPT_DIR}/native-macos-lane.sh" --profile "${tsan_profile}" --log-root "${LOG_ROOT}/tsan"
fi
fi
if [ "${#FAILURES[@]}" -gt 0 ]; then
record_summary "## Failures"
for failure in "${FAILURES[@]}"; do
record_summary "- ${failure}"
done
fail "${#FAILURES[@]} step(s) failed. Summary: ${SUMMARY}"
exit 1
fi
record_summary "## Result"
record_summary "Pre-push CI gate passed."
ok "summary: ${SUMMARY#"${REPO_ROOT}"/}"