yams/scripts/local-ci/cross-compile-smoke.sh
trvon c9fbee0d50
ci(cross): add compile-only cross-compile smoke
Detects mingw-w64/linux-gnu cross compilers and compiles a portability
probe with -fsyntax-only to catch narrow-vs-wide API and std::jthread
issues before GitHub CI. Skips cleanly when no toolchain is present;
--self-test proves dispatch and failure propagation with a fake compiler.
2026-08-12 20:02:23 -06:00

115 lines
2.9 KiB
Bash

#!/usr/bin/env bash
# yams/scripts/local-ci/cross-compile-smoke.sh
#
# Compile-only cross-compile smoke lane. Guards on toolchain availability: skips
# cleanly when no Windows/Linux cross compiler is installed. When one is present,
# compiles a small portability probe with -fsyntax-only to catch narrow-vs-wide
# API mismatches, missing std::jthread, and libstdc++ system_clock range
# differences before they reach GitHub CI.
#
# --self-test Prove dispatch and failure propagation with a fake cross compiler.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
PROBE="${SCRIPT_DIR}/portability_probe.cpp"
SELF_TEST=0
while [ "$#" -gt 0 ]; do
case "$1" in
--self-test)
SELF_TEST=1
shift
;;
-h | --help)
echo "usage: cross-compile-smoke.sh [--self-test]"
exit 0
;;
*)
echo "unknown argument: $1" >&2
exit 2
;;
esac
done
detect_cross_compilers() {
local cxx
for cxx in x86_64-w64-mingw32-g++ i686-w64-mingw32-g++ aarch64-linux-gnu-g++ x86_64-linux-gnu-g++; do
if command -v "$cxx" >/dev/null 2>&1; then
printf '%s\n' "$cxx"
fi
done
}
compile_with() {
local cxx="$1"
echo "[cross-smoke] compiling probe with ${cxx}"
if "${cxx}" -std=c++20 -fsyntax-only -I "${REPO_ROOT}/include" "${PROBE}"; then
echo "[cross-smoke] ${cxx}: ok"
return 0
fi
echo "[cross-smoke] ${cxx}: compile failed" >&2
return 1
}
scan() {
local compilers failures=0
compilers="$(detect_cross_compilers)"
if [ -z "${compilers}" ]; then
echo "[cross-smoke] no cross compiler (mingw-w64 / linux-gnu) found; skipping"
return 0
fi
for cxx in ${compilers}; do
if ! compile_with "${cxx}"; then
failures=$((failures + 1))
fi
done
return "${failures}"
}
self_test() {
local tmpdir fake args_log
tmpdir="$(mktemp -d)"
args_log="${tmpdir}/args.log"
fake="${tmpdir}/x86_64-w64-mingw32-g++"
cat >"${fake}" <<'FAKE'
#!/usr/bin/env bash
printf '%s\n' "$@" >>"${YAMS_CROSS_SMOKE_ARGS_LOG:?}"
exit "${YAMS_CROSS_SMOKE_EXIT:-0}"
FAKE
chmod +x "${fake}"
export PATH="${tmpdir}:${PATH}"
export YAMS_CROSS_SMOKE_ARGS_LOG="${args_log}"
# Dispatch: the fake compiler is detected and receives the expected flags.
if ! YAMS_CROSS_SMOKE_EXIT=0 scan; then
echo "[cross-smoke] self-test: dispatch FAILED" >&2
rm -rf "${tmpdir}"
return 1
fi
for expected in "-fsyntax-only" "-std=c++20" "${REPO_ROOT}/include" "${PROBE}"; do
if ! grep -qF -- "${expected}" "${args_log}"; then
echo "[cross-smoke] self-test: missing expected arg ${expected}" >&2
rm -rf "${tmpdir}"
return 1
fi
done
# Failure propagation: a failing cross compiler fails the lane.
if YAMS_CROSS_SMOKE_EXIT=1 scan; then
echo "[cross-smoke] self-test: failure propagation FAILED" >&2
rm -rf "${tmpdir}"
return 1
fi
rm -rf "${tmpdir}"
echo "[cross-smoke] self-test: dispatch + failure propagation ok"
return 0
}
if [ "${SELF_TEST}" -eq 1 ]; then
self_test
else
scan
fi