ref:fd7ad6b30716056809ac1bbda3a2fa69e1070297

fix(ci): pin release binaries to a glibc 2.31 floor via cargo-zigbuild (#20)

Release binaries were built natively on the rust:1.95-trixie image (glibc 2.39) and cross-linked amd64 with trixie's gnu-gcc, baking a GLIBC_2.39 requirement. They then failed to load on older runtimes — every release job on a Debian 12 / glibc 2.36 runner died with: /usr/local/bin/anvil: libc.so.6: version `GLIBC_2.39' not found Build both targets with cargo-zigbuild pinned to a gnu.2.31 glibc floor (covers Debian 11+, Ubuntu 20.04+, RHEL 9). This is the easy case for zigbuild: TLS is rustls + ring (no openssl/aws-lc-rs), build.rs compiles no C. zig cc also cross-links amd64, so the gcc-x86-64-linux-gnu cross-compiler is dropped (xz-utils added for the zig tarball). Adds a hard CI gate: the build fails if either binary references a GLIBC symbol above 2.31 (grepped from the dynamic string table, no binutils), so the floor can't silently regress on a future dep/toolchain bump. zig + cargo-zigbuild versions are pinned together (they're coupled). Verified: bash -n + shellcheck clean; floor-comparison logic unit-checked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SHA: fd7ad6b30716056809ac1bbda3a2fa69e1070297
Author: CI <ci@anvil.test>
Date: 2026-05-29 07:08
Parents: 2f082ee
2 files changed +60 -14
Type
.anvil.yml +1 −1
@@ -62,7 +62,7 @@
# Delegated to a bash script because the runner invokes /bin/sh (dash)
# and we need bash features (pipefail, trap ERR) for publish rollback.
run: |
apt-get update && apt-get install -y bash curl gcc-x86-64-linux-gnu jq 2>&1
apt-get update && apt-get install -y bash curl jq xz-utils 2>&1
exec bash ci/build-runner.sh
depends_on: [test, clippy, fmt]
cache:
ci/build-runner.sh +59 −13
@@ -31,30 +31,76 @@
git tag -f "$VERSION"
fi
# Build arm64 natively (CI runner is aarch64)
echo "==> Building arm64 binary (native)..."
cargo build --release 2>&1
# ── Pinned glibc floor (anvil-cli#20) ───────────────────────────────────
#
# Building natively on the trixie image bakes a GLIBC_2.39 requirement into
# the binary, which then fails to load on older runtimes (Debian 12 bookworm
# / glibc 2.36, Ubuntu 22.04 / 2.35, ...). cargo-zigbuild links against an
# older glibc supplied by zig, so the binaries run on glibc >= GLIBC_FLOOR.
# zig cc also cross-links amd64, so no separate gnu cross-compiler is needed.
# Pinned, mutually-compatible pair (cargo-zigbuild is sensitive to the zig
# version it drives). Bump both together and re-check the glibc floor.
ZIG_VERSION=0.14.0
ZIGBUILD_VERSION=0.22.3
GLIBC_FLOOR=2.31
# Cross-compile amd64
echo "==> Cross-compiling amd64 binary..."
rustup target add x86_64-unknown-linux-gnu 2>&1
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \
cargo build --release --target x86_64-unknown-linux-gnu 2>&1
if ! command -v zig >/dev/null 2>&1; then
echo "==> Installing zig $ZIG_VERSION"
curl -sSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-$(uname -m)-${ZIG_VERSION}.tar.xz" \
-o /tmp/zig.tar.xz
mkdir -p /opt/zig
tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1
export PATH="/opt/zig:$PATH"
fi
command -v cargo-zigbuild >/dev/null 2>&1 || \
cargo install --locked cargo-zigbuild --version "$ZIGBUILD_VERSION" 2>&1
rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu 2>&1
echo "==> Building arm64 (glibc $GLIBC_FLOOR floor)..."
cargo zigbuild --release --target "aarch64-unknown-linux-gnu.${GLIBC_FLOOR}" 2>&1
echo "==> Building amd64 (glibc $GLIBC_FLOOR floor)..."
cargo zigbuild --release --target "x86_64-unknown-linux-gnu.${GLIBC_FLOOR}" 2>&1
ARM64_BIN="target/aarch64-unknown-linux-gnu/release/anvil"
AMD64_BIN="target/x86_64-unknown-linux-gnu/release/anvil"
# Hard-gate the glibc floor: fail the build if either binary references a
# GLIBC symbol newer than the floor. This is the exact failure we're fixing
# (GLIBC_2.39 on a glibc-2.36 runner); without the gate a future dep or
# toolchain bump silently raises it again. The version tags live as ASCII in
# the dynamic string table, so grep finds them without binutils.
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 "$ARM64_BIN"
assert_glibc_floor "$AMD64_BIN"
# Stage for CI artifact upload (unversioned names)
mkdir -p runner-dist
cp target/release/anvil runner-dist/anvil_linux_arm64
cp target/x86_64-unknown-linux-gnu/release/anvil runner-dist/anvil_linux_amd64
cp "$ARM64_BIN" runner-dist/anvil_linux_arm64
cp "$AMD64_BIN" runner-dist/anvil_linux_amd64
if [ "$PUBLISH" != "1" ]; then
exit 0
fi
ANVIL_CLI="$PWD/target/release/anvil"
ANVIL_CLI="$PWD/$ARM64_BIN"
echo "==> Publishing release $VERSION"
cp target/release/anvil "runner-dist/anvil_linux_arm64_${VERSION}"
cp target/x86_64-unknown-linux-gnu/release/anvil "runner-dist/anvil_linux_amd64_${VERSION}"
cp "$ARM64_BIN" "runner-dist/anvil_linux_arm64_${VERSION}"
cp "$AMD64_BIN" "runner-dist/anvil_linux_amd64_${VERSION}"
# Compute SHA256 checksums for every versioned asset — published as
# SHA256SUMS_${VERSION} alongside the binaries so install scripts can