ref:main
#!/usr/bin/env bash
set -euo pipefail
# Build the two same-ISA runner binaries for one architecture (native Linux +
# same-ISA cross-OS macOS), and on main upload them to the release draft.
#
# Split per-ISA (fangorn/anvil-cli#43) so each build runs natively on a matching
# runner and never pays slow cross-*architecture* codegen — only cheap
# cross-*OS* codegen within one ISA. The two arch jobs run in parallel on
# different workers, which the affinity-escape fix (fangorn/anvil#361) makes
# possible.
#
# Usage: ci/build-arch.sh <arm64|amd64>
#
# The toolchain (zig, cargo-zigbuild, macOS SDK, rustup targets) is baked into a
# cached prepared image by the step's prepare: block — not installed here.
ARCH="${1:?usage: build-arch.sh <arm64|amd64>}"
# WIN_TARGET is set only where we ship a Windows build. Today that's amd64
# (cross-compiled via cargo-xwin); arm64 Windows is deferred.
WIN_TARGET=""
WIN_NAME=""
case "$ARCH" in
arm64)
LINUX_TARGET=aarch64-unknown-linux-gnu
MAC_TARGET=aarch64-apple-darwin
LINUX_NAME=anvil_linux_arm64
MAC_NAME=anvil_macos_arm64
;;
amd64)
LINUX_TARGET=x86_64-unknown-linux-gnu
MAC_TARGET=x86_64-apple-darwin
LINUX_NAME=anvil_linux_amd64
MAC_NAME=anvil_macos_amd64
WIN_TARGET=x86_64-pc-windows-msvc
WIN_NAME=anvil_windows_amd64
;;
*)
echo "ERROR: unknown arch '$ARCH' (want arm64 or amd64)" >&2
exit 1
;;
esac
git config --global --add safe.directory /workspace
# Publish only on main, and only if the release token is present.
PUBLISH=0
if [ "${ANVIL_BRANCH:-}" = "main" ] || [ "${ANVIL_BRANCH:-}" = "refs/heads/main" ]; then
if [ -z "${ANVIL_TOKEN:-}" ]; then
echo "ERROR: ANVIL_TOKEN secret not set. Run:" >&2
echo " anvil ci set-secret --name ANVIL_TOKEN --value <pat> --repo fangorn/anvil-cli" >&2
exit 1
fi
PUBLISH=1
fi
# The version must match across both arch jobs. ci/release.sh derives it from
# the latest *published* release only (drafts excluded), so both jobs — and the
# publish job — compute the same tag even while this run's draft exists.
if [ "$PUBLISH" = "1" ]; then
VERSION=$(bash ci/release.sh)
if [ -z "$VERSION" ]; then
echo "ERROR: ci/release.sh returned empty version" >&2
exit 1
fi
# Local-only tag so build.rs stamps the version via `git describe`.
git tag -f "$VERSION"
fi
# ── Toolchain env (binaries come from the prepared image) ────────────────
export PATH="/opt/zig:$PATH"
SDKROOT=$(echo /opt/MacOSX*.sdk)
if [ ! -d "$SDKROOT" ]; then
echo "ERROR: macOS SDK not found under /opt (expected from the prepared image)" >&2
exit 1
fi
export SDKROOT
export MACOSX_DEPLOYMENT_TARGET=11.0
# glibc floor: keep the Linux binary loadable on older runtimes (anvil-cli#20).
GLIBC_FLOOR=2.31
echo "==> Building linux $ARCH (glibc $GLIBC_FLOOR floor)..."
cargo zigbuild --release --target "${LINUX_TARGET}.${GLIBC_FLOOR}" 2>&1
echo "==> Building macos $ARCH..."
cargo zigbuild --release --target "$MAC_TARGET" 2>&1
LINUX_BIN="target/${LINUX_TARGET}/release/anvil"
MAC_BIN="target/${MAC_TARGET}/release/anvil"
# Windows (amd64 only): cross-compile the MSVC target with cargo-xwin, linking
# against the SDK the prepare step cached in /opt/xwin. No Windows host needed.
WIN_BIN=""
if [ -n "$WIN_TARGET" ]; then
echo "==> Building windows $ARCH ($WIN_TARGET, cargo-xwin)..."
XWIN_ACCEPT_LICENSE=1 XWIN_CACHE_DIR=/opt/xwin \
cargo xwin build --release --target "$WIN_TARGET" 2>&1
WIN_BIN="target/${WIN_TARGET}/release/anvil.exe"
fi
# Hard-gate the glibc floor on the Linux binary.
assert_glibc_floor() {
local bin="$1" max
max=$(grep -aoE 'GLIBC_[0-9]+\.[0-9]+' "$bin" | sort -V | tail -1)
if [ -z "$max" ]; then
echo "ERROR: no GLIBC version symbols found in $bin (unexpected)" >&2
exit 1
fi
if [ "$(printf '%s\nGLIBC_%s\n' "$max" "$GLIBC_FLOOR" | sort -V | tail -1)" != "GLIBC_${GLIBC_FLOOR}" ]; then
echo "ERROR: $bin requires $max, above the GLIBC_${GLIBC_FLOOR} floor" >&2
exit 1
fi
echo " OK: $bin max $max (<= GLIBC_${GLIBC_FLOOR})"
}
echo "==> Verifying glibc floor ($GLIBC_FLOOR)..."
assert_glibc_floor "$LINUX_BIN"
# Stage unversioned names for the CI artifact upload.
mkdir -p runner-dist
cp "$LINUX_BIN" "runner-dist/$LINUX_NAME"
cp "$MAC_BIN" "runner-dist/$MAC_NAME"
[ -n "$WIN_BIN" ] && cp "$WIN_BIN" "runner-dist/$WIN_NAME"
if [ "$PUBLISH" != "1" ]; then
exit 0
fi
# This runner is native $ARCH Linux, so the Linux binary it just built drives
# the release commands.
ANVIL="$PWD/$LINUX_BIN"
# Ensure the draft release exists. Either arch job may get here first; the
# loser's create hits a duplicate-tag 422, which we tolerate — the draft it
# needs already exists.
if ! "$ANVIL" release view "$VERSION" --repo fangorn/anvil-cli >/dev/null 2>&1; then
"$ANVIL" release create \
--tag "$VERSION" \
--title "anvil-cli $VERSION" \
--body "Runner binaries for linux/{amd64,arm64}, macos/{amd64,arm64}, windows/amd64." \
--draft \
--repo fangorn/anvil-cli || true
fi
# Upload this ISA's binaries to the draft (the amd64 job also ships Windows).
cp "$LINUX_BIN" "runner-dist/${LINUX_NAME}_${VERSION}"
cp "$MAC_BIN" "runner-dist/${MAC_NAME}_${VERSION}"
"$ANVIL" release upload "$VERSION" "runner-dist/${LINUX_NAME}_${VERSION}" --repo fangorn/anvil-cli
"$ANVIL" release upload "$VERSION" "runner-dist/${MAC_NAME}_${VERSION}" --repo fangorn/anvil-cli
if [ -n "$WIN_BIN" ]; then
cp "$WIN_BIN" "runner-dist/${WIN_NAME}_${VERSION}"
"$ANVIL" release upload "$VERSION" "runner-dist/${WIN_NAME}_${VERSION}" --repo fangorn/anvil-cli
fi
echo "==> Uploaded $ARCH binaries to draft release $VERSION"