Skip to content

Synchronize CI/CD, Flatbuffers vendoring and wamp-ai and wamp-cicd Submodules between autobahn-python and zlmdb #204

Synchronize CI/CD, Flatbuffers vendoring and wamp-ai and wamp-cicd Submodules between autobahn-python and zlmdb

Synchronize CI/CD, Flatbuffers vendoring and wamp-ai and wamp-cicd Submodules between autobahn-python and zlmdb #204

Workflow file for this run

name: wheels-arm64
on:
# Build wheels on feature branches and PRs (test only)
push:
branches: ["**"]
tags:
- 'v*'
pull_request:
branches: [master]
# Publish to GitHub Releases when merged to master
# Publish to PyPI when tagged
workflow_dispatch:
env:
# Platform target
ARCH: aarch64
jobs:
identifiers:
# GitHub needs to know where .cicd/workflows/identifiers.yml lives at parse time,
# and submodules aren't included in that context! thus the following does NOT work:
# uses: ./.cicd/workflows/identifiers.yml
# we MUST reference the remote repo directly:
uses: wamp-proto/wamp-cicd/.github/workflows/identifiers.yml@main
# IMPORTANT: we still need .cicd as a Git submodule in the using repo though!
# because e.g. identifiers.yml wants to access scripts/sanitize.sh !
build-wheels:
name: Build ARM64 wheels (${{ matrix.target.name }})
needs: identifiers
runs-on: ubuntu-latest
env:
BASE_REPO: ${{ needs.identifiers.outputs.base_repo }}
BASE_BRANCH: ${{ needs.identifiers.outputs.base_branch }}
PR_NUMBER: ${{ needs.identifiers.outputs.pr_number }}
PR_REPO: ${{ needs.identifiers.outputs.pr_repo }}
PR_BRANCH: ${{ needs.identifiers.outputs.pr_branch }}
strategy:
fail-fast: false
matrix:
target:
# ============================================================
# CPython ARM64 wheels (using official PyPA manylinux images)
# ============================================================
# CPython 3.11 - manylinux_2_28_aarch64 (glibc 2.28)
# Modern baseline (Debian 10+, Ubuntu 18.04+, RHEL 8+)
# Note: Using manylinux_2_28 as manylinux2014 segfaults under QEMU
# and manylinux_2_17 doesn't exist for ARM64
- name: "cpython-3.11-manylinux_2_28_aarch64"
base_image: "quay.io/pypa/manylinux_2_28_aarch64"
manylinux_tag: "manylinux_2_28_aarch64"
glibc_version: "2.28"
python_impl: "cpython"
build_type: "official"
python_versions: "cpy311"
# CPython 3.13 - manylinux_2_28_aarch64 (glibc 2.28)
# Modern baseline (Debian 10+, Ubuntu 18.04+, RHEL 8+)
- name: "cpython-3.13-manylinux_2_28_aarch64"
base_image: "quay.io/pypa/manylinux_2_28_aarch64"
manylinux_tag: "manylinux_2_28_aarch64"
glibc_version: "2.28"
python_impl: "cpython"
build_type: "official"
python_versions: "cpy313"
# ============================================================
# PyPy ARM64 wheels (using our custom manylinux images)
# ============================================================
# PyPy 3.11 on Debian 12 (Bookworm) - manylinux_2_36_aarch64
- name: "pypy-3.11-bookworm-manylinux_2_36_aarch64"
dockerfile: "docker/Dockerfile.pypy-bookworm-manylinux-arm64"
manylinux_tag: "manylinux_2_36_aarch64"
glibc_version: "2.36"
python_impl: "pypy"
build_type: "custom"
python_versions: "pypy311"
# PyPy 3.11 on Debian 13 (Trixie) - manylinux_2_38_aarch64
- name: "pypy-3.11-trixie-manylinux_2_38_aarch64"
dockerfile: "docker/Dockerfile.pypy-trixie-manylinux-arm64"
manylinux_tag: "manylinux_2_38_aarch64"
glibc_version: "2.38"
python_impl: "pypy"
build_type: "custom"
python_versions: "pypy311"
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
# ============================================================
# QEMU + Docker Buildx Setup
# ============================================================
- name: Install modern QEMU (tonistiigi/binfmt)
run: |
echo "==> Installing modern QEMU 8.x via tonistiigi/binfmt"
echo "This provides much better stability for PyPy on ARM64 emulation"
docker run --rm --privileged tonistiigi/binfmt --install all
echo "✅ Modern QEMU installed"
- name: Set up QEMU for ARM64 emulation
uses: docker/setup-qemu-action@v3
with:
platforms: linux/arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Verify QEMU setup
run: |
echo "==> QEMU platforms available:"
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker buildx ls
# ============================================================
# Build custom PyPy images (only for build_type == custom)
# ============================================================
- name: Build custom PyPy manylinux image (with retry for QEMU flakiness)
if: matrix.target.build_type == 'custom'
uses: nick-fields/retry@v3
with:
# CFFI compilation under QEMU ARM64 emulation is very slow
# CPython builds can take 20+ minutes due to emulation overhead
timeout_minutes: 30
max_attempts: 3
retry_on: error
warning_on_retry: true
command: |
set -e # Exit immediately if docker build fails
echo "==> Building custom PyPy manylinux image for ${{ matrix.target.name }}"
docker buildx build \
--platform linux/arm64 \
--tag ${{ matrix.target.name }}:latest \
--file ${{ matrix.target.dockerfile }} \
--load \
.
echo "✅ Custom image built: ${{ matrix.target.name }}:latest"
# ============================================================
# Build wheels inside ARM64 containers
# ============================================================
- name: Build ARM64 wheels with NVX extension (with retry for QEMU flakiness)
uses: nick-fields/retry@v3
with:
# CFFI compilation under QEMU ARM64 emulation is very slow
# CPython builds can take 20+ minutes due to emulation overhead
timeout_minutes: 30
max_attempts: 3
retry_on: error
warning_on_retry: true
command: |
set -e # Exit immediately if docker build fails
echo "==> Building ARM64 wheels for ${{ matrix.target.name }}"
echo "Container: ${{ matrix.target.base_image || matrix.target.name }}:latest"
echo "Platform: linux/arm64"
echo "Manylinux tag: ${{ matrix.target.manylinux_tag }}"
echo "glibc: ${{ matrix.target.glibc_version }}"
# Determine which image to use
if [ "${{ matrix.target.build_type }}" = "custom" ]; then
IMAGE="${{ matrix.target.name }}:latest"
else
IMAGE="${{ matrix.target.base_image }}"
fi
# Create wheelhouse directory on host
mkdir -p wheelhouse
# Run build script inside ARM64 container via QEMU
docker run --rm \
--platform linux/arm64 \
-v $PWD:/io \
-w /io \
-e AUTOBAHN_USE_NVX=1 \
-e PYTHON_VERSIONS="${{ matrix.target.python_versions }}" \
-e GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" \
$IMAGE \
/bin/bash /io/.github/scripts/build-arm64-wheel.sh
echo "✅ ARM64 wheels built successfully"
- name: Force file system sync (post-build, pre-validation)
run: |
echo "======================================================================"
echo "==> Forcing File System Sync (QEMU Buffer Flush)"
echo "======================================================================"
echo ""
echo "Flushing all file system buffers to disk to prevent QEMU delayed"
echo "write corruption. This ensures wheels are fully written before"
echo "validation and checksumming."
echo ""
sync
echo "✅ All buffers flushed to disk"
echo ""
- name: Validate wheels integrity
run: |
set -o pipefail
echo "======================================================================"
echo "==> Validating Wheel Integrity (Fail Fast)"
echo "======================================================================"
echo ""
echo "Installing twine for validation..."
# Ensure pip is available for the Python being used
python3 -m ensurepip --upgrade 2>/dev/null || true
# Install both packaging and twine from master for PEP 639 (Core Metadata 2.4) support
# Note: No --break-system-packages needed in containers (isolated environment)
python3 -m pip install git+https://github.com/pypa/packaging.git
python3 -m pip install git+https://github.com/pypa/twine.git
echo ""
echo "==> Validation environment:"
echo "Python: $(python3 --version)"
echo "setuptools: $(python3 -m pip show setuptools | grep '^Version:' || echo 'not installed')"
echo "packaging: $(python3 -m pip show packaging | grep '^Version:' || echo 'not installed')"
echo "twine: $(twine --version)"
echo ""
# Initialize validation output file
VALIDATION_FILE="wheelhouse/VALIDATION.txt"
echo "Wheel Validation Results - Build Time" > "$VALIDATION_FILE"
echo "======================================" >> "$VALIDATION_FILE"
echo "" >> "$VALIDATION_FILE"
echo "Validation Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> "$VALIDATION_FILE"
echo "Python: $(python3 --version)" >> "$VALIDATION_FILE"
echo "twine: $(twine --version)" >> "$VALIDATION_FILE"
echo "" >> "$VALIDATION_FILE"
HAS_ERRORS=0
for wheel in wheelhouse/*.whl; do
if [ ! -f "$wheel" ]; then
echo "⚠️ No wheels found in wheelhouse/"
HAS_ERRORS=1
continue
fi
WHEEL_NAME=$(basename "$wheel")
echo "==> Validating: $WHEEL_NAME"
echo "" >> "$VALIDATION_FILE"
echo "Wheel: $WHEEL_NAME" >> "$VALIDATION_FILE"
echo "---" >> "$VALIDATION_FILE"
# Test 1: Can unzip read the wheel?
echo " [1/3] ZIP integrity test..."
if unzip -t "$wheel" > /dev/null 2>&1; then
echo " ✅ ZIP test PASS"
echo " ZIP test: PASS" >> "$VALIDATION_FILE"
else
echo " ❌ ZIP test FAIL - wheel is corrupted!"
echo " This wheel cannot be unzipped and is unusable."
echo " ZIP test: FAIL - wheel is corrupted!" >> "$VALIDATION_FILE"
HAS_ERRORS=1
fi
# Test 2: Python zipfile module validation
echo " [2/3] Python zipfile test..."
if python3 -m zipfile -t "$wheel" > /dev/null 2>&1; then
echo " ✅ Python zipfile test PASS"
echo " Python zipfile test: PASS" >> "$VALIDATION_FILE"
else
echo " ❌ Python zipfile test FAIL - wheel is corrupted!"
echo " Python zipfile test: FAIL - wheel is corrupted!" >> "$VALIDATION_FILE"
HAS_ERRORS=1
fi
# Test 3: twine check (validates wheel metadata and structure)
echo " [3/3] Twine validation..."
twine check "$wheel" 2>&1 | tee /tmp/twine_output.txt
TWINE_EXIT=${PIPESTATUS[0]}
# Fail on nonzero exit or any error-like output
if [ "$TWINE_EXIT" -eq 0 ] && ! grep -Eqi "ERROR|FAILED|InvalidDistribution" /tmp/twine_output.txt; then
echo " ✅ Twine check PASS"
echo " Twine check: PASS" >> "$VALIDATION_FILE"
else
echo " ❌ Twine check FAIL"
cat /tmp/twine_output.txt
echo " Twine check: FAIL" >> "$VALIDATION_FILE"
cat /tmp/twine_output.txt >> "$VALIDATION_FILE"
HAS_ERRORS=1
fi
rm -f /tmp/twine_output.txt
echo ""
done
if [ $HAS_ERRORS -eq 1 ]; then
echo "" >> "$VALIDATION_FILE"
echo "RESULT: VALIDATION FAILED" >> "$VALIDATION_FILE"
echo "======================================================================"
echo "❌ WHEEL VALIDATION FAILED"
echo "======================================================================"
echo ""
echo "One or more wheels failed integrity checks."
echo "This indicates a build or packaging problem."
echo ""
echo "DO NOT PROCEED - corrupted wheels must NOT become artifacts!"
echo ""
exit 1
else
echo "" >> "$VALIDATION_FILE"
echo "RESULT: ALL VALIDATIONS PASSED" >> "$VALIDATION_FILE"
echo "======================================================================"
echo "✅ All wheels validated successfully"
echo "======================================================================"
echo ""
echo "All integrity checks passed. Wheels are valid and ready for upload."
echo ""
echo "Validation results written to: $VALIDATION_FILE"
fi
- name: Generate SHA256 checksums (chain of custody)
run: |
echo "======================================================================"
echo "==> Generating SHA256 Checksums for Chain of Custody"
echo "======================================================================"
echo ""
echo "OpenSSL version:"
openssl version
echo ""
# Force sync before checksumming to ensure files are on disk
echo "Forcing sync before checksumming..."
sync
echo "✅ Buffers flushed"
echo ""
# Change to wheelhouse directory to generate relative paths (basename only)
cd wheelhouse
CHECKSUM_FILE="CHECKSUMS.sha256"
# Generate checksums for all wheels (using basename only)
echo "Generating checksums for wheels..."
for wheel in *.whl; do
if [ -f "$wheel" ]; then
# Sync before each checksum to ensure file is on disk
sync
openssl sha256 "$wheel" | tee -a "$CHECKSUM_FILE"
fi
done
echo ""
echo "==> Generated checksum file:"
cat "$CHECKSUM_FILE"
echo ""
echo "This file will be uploaded with artifacts to verify integrity"
echo "when artifacts are downloaded by the release workflow."
echo ""
# Return to project root
cd ..
- name: Force file system sync (post-checksum, pre-upload)
run: |
echo "======================================================================"
echo "==> Forcing File System Sync (Post-Checksum)"
echo "======================================================================"
echo ""
echo "Final sync to ensure validation results and checksums are on disk"
echo "before artifact packaging."
echo ""
sync
echo "✅ All buffers flushed to disk"
echo ""
- name: Generate build metadata
run: |
BUILD_INFO=wheelhouse/build-info.txt
echo "ARM64 manylinux Build Information for ${{ matrix.target.name }}" > $BUILD_INFO
echo "================================================================" >> $BUILD_INFO
echo "" >> $BUILD_INFO
echo "Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $BUILD_INFO
echo "Base Image: ${{ matrix.target.base_image || 'custom' }}" >> $BUILD_INFO
echo "Dockerfile: ${{ matrix.target.dockerfile || 'N/A (official image)' }}" >> $BUILD_INFO
echo "Platform: linux/arm64 (aarch64)" >> $BUILD_INFO
echo "Manylinux Tag: ${{ matrix.target.manylinux_tag }}" >> $BUILD_INFO
echo "glibc Version: ${{ matrix.target.glibc_version }}" >> $BUILD_INFO
echo "Python Implementation: ${{ matrix.target.python_impl }}" >> $BUILD_INFO
echo "Build Method: GitHub Actions + QEMU + Docker (ARM64 emulation)" >> $BUILD_INFO
echo "NVX Acceleration: ENABLED (binary wheels with native extensions)" >> $BUILD_INFO
echo "" >> $BUILD_INFO
echo "Wheels Built:" >> $BUILD_INFO
echo "-------------" >> $BUILD_INFO
for whl in wheelhouse/*.whl; do
echo "- $(basename "$whl")" >> $BUILD_INFO
done
echo "" >> $BUILD_INFO
echo "Note: These wheels were built using QEMU emulation on x86_64 runners." >> $BUILD_INFO
echo " They are fully functional native ARM64 binaries." >> $BUILD_INFO
echo ""
echo "==> Generated build-info.txt:"
cat $BUILD_INFO
- name: List built artifacts
run: |
echo "==> Built artifacts for ${{ matrix.target.name }}:"
ls -la wheelhouse/ 2>/dev/null || echo "No wheelhouse/ directory found"
echo ""
echo "==> Build metadata:"
cat wheelhouse/build-info.txt 2>/dev/null || echo "No build info found"
echo ""
echo "==> Wheel inventory:"
find wheelhouse/ -name "*.whl" -exec basename {} \; 2>/dev/null | sort || echo "No wheels found"
# ============================================================================
# Note on paths: This workflow uses Docker containers but NOT the GitHub
# Actions `container:` directive. Instead, it runs `docker run` commands
# from the host, mounting the workspace. The upload step runs directly on
# the host (not inside Docker), so ${{ github.workspace }} works correctly.
#
# However, for consistency and safety, we use a relative path here as well.
# This ensures the workflow remains robust if the architecture changes.
# ============================================================================
- name: Upload wheels and build metadata with cryptographic verification
uses: wamp-proto/wamp-cicd/actions/upload-artifact-verified@main
with:
name: artifacts-arm64-${{ matrix.target.name }}
path: wheelhouse/
retention-days: 30
# GitHub Releases, PyPI, and RTD publishing are now handled by the centralized 'release' workflow