ARM32 Support Verification (piwheels) #191
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # .github/workflows/piwheels-arm32-verification.yml | |
| name: ARM32 Support Verification (piwheels) | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to check (e.g., 2.0.3)' | |
| required: false | |
| default: 'latest' | |
| schedule: | |
| - cron: '0 3 * * *' # Daily at 3 AM UTC | |
| jobs: | |
| verify-piwheels: | |
| name: Verify ARM32 on piwheels | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Wait for piwheels (if triggered by release) | |
| if: github.event_name == 'release' | |
| run: | | |
| echo "⏳ Waiting 24 hours for piwheels to build ARM32 wheels..." | |
| sleep 86400 | |
| - name: Get latest version | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ github.event.inputs.version }}" != "latest" ]; then | |
| VERSION="${{ github.event.inputs.version }}" | |
| elif [ "${{ github.event_name }}" == "release" ]; then | |
| VERSION="${{ github.event.release.tag_name }}" | |
| VERSION="${VERSION#v}" | |
| else | |
| VERSION=$(curl -s https://pypi.org/pypi/omnipkg/json | python3 -c "import sys, json; print(json.load(sys.stdin)['info']['version'])") | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Checking version: $VERSION" | |
| - name: Scrape piwheels simple index | |
| id: scrape | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| echo "🔍 Checking piwheels simple index for omnipkg $VERSION..." | |
| # Use the simple index — same source pip uses, plain HTML, reliable | |
| SIMPLE_HTML=$(curl -s "https://www.piwheels.org/simple/omnipkg/") | |
| if [ -z "$SIMPLE_HTML" ]; then | |
| echo "❌ Could not fetch piwheels simple index" | |
| echo "builds_found=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Extract all wheel filenames for this version from the simple index. | |
| # piwheels wheels are tagged like: | |
| # omnipkg-2.5.0-cp311-cp311-linux_armv6l.whl | |
| # omnipkg-2.5.0-cp313-cp313-linux_armv7l.whl | |
| # omnipkg-2.5.0-py3-none-any.whl (pure Python fallback, rare) | |
| # We want any wheel matching the version, on any cpython, on any armv* arch. | |
| WHEELS=$(echo "$SIMPLE_HTML" | grep -oP "omnipkg-${VERSION}-[^\"']+\.whl" | sort -u) | |
| if [ -z "$WHEELS" ]; then | |
| echo "❌ No wheels found for version $VERSION on piwheels" | |
| echo "builds_found=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Found wheels:" | |
| echo "$WHEELS" | |
| echo "builds_found=true" >> $GITHUB_OUTPUT | |
| # --- Detect which Python versions have a successful build --- | |
| # Map CPython ABI tags to Debian release / Python version labels. | |
| # piwheels uses cp39 for Bullseye, cp311 for Bookworm, cp313 for Trixie. | |
| HAS_39=false | |
| HAS_311=false | |
| HAS_313=false | |
| echo "$WHEELS" | grep -q "cp39" && HAS_39=true | |
| echo "$WHEELS" | grep -q "cp311" && HAS_311=true | |
| echo "$WHEELS" | grep -q "cp313" && HAS_313=true | |
| # Build a human-readable list of confirmed Python versions | |
| PYTHON_VERSIONS="" | |
| $HAS_39 && PYTHON_VERSIONS="${PYTHON_VERSIONS}3.9 (Bullseye), " | |
| $HAS_311 && PYTHON_VERSIONS="${PYTHON_VERSIONS}3.11 (Bookworm), " | |
| $HAS_313 && PYTHON_VERSIONS="${PYTHON_VERSIONS}3.13 (Trixie), " | |
| PYTHON_VERSIONS="${PYTHON_VERSIONS%, }" # strip trailing ", " | |
| echo "python_versions=$PYTHON_VERSIONS" >> $GITHUB_OUTPUT | |
| echo "has_cp39=$HAS_39" >> $GITHUB_OUTPUT | |
| echo "has_cp311=$HAS_311" >> $GITHUB_OUTPUT | |
| echo "has_cp313=$HAS_313" >> $GITHUB_OUTPUT | |
| echo "🐍 Python versions with ARM32 wheels: $PYTHON_VERSIONS" | |
| # Pick a representative wheel URL for the README badge | |
| # Prefer armv7l > armv6l, prefer cp311 as most common Pi target | |
| BEST_WHEEL="" | |
| for ABI in cp311 cp313 cp39; do | |
| CANDIDATE=$(echo "$WHEELS" | grep "${ABI}-${ABI}-linux_armv7l" | head -1) | |
| if [ -z "$CANDIDATE" ]; then | |
| CANDIDATE=$(echo "$WHEELS" | grep "${ABI}-${ABI}-linux_armv6l" | head -1) | |
| fi | |
| if [ -n "$CANDIDATE" ]; then | |
| BEST_WHEEL="$CANDIDATE" | |
| break | |
| fi | |
| done | |
| # Fall back to whatever we found if the above loop found nothing | |
| [ -z "$BEST_WHEEL" ] && BEST_WHEEL=$(echo "$WHEELS" | head -1) | |
| WHEEL_URL="https://www.piwheels.org/simple/omnipkg/${BEST_WHEEL}" | |
| echo "wheel_file=$BEST_WHEEL" >> $GITHUB_OUTPUT | |
| echo "wheel_url=$WHEEL_URL" >> $GITHUB_OUTPUT | |
| echo "install_command=pip3 install omnipkg==${VERSION}" >> $GITHUB_OUTPUT | |
| echo "✅ Representative wheel: $WHEEL_URL" | |
| - name: Update README badges section | |
| if: steps.scrape.outputs.builds_found == 'true' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| PYTHON_VERS="${{ steps.scrape.outputs.python_versions }}" | |
| INSTALL_CMD="${{ steps.scrape.outputs.install_command }}" | |
| WHEEL_URL="${{ steps.scrape.outputs.wheel_url }}" | |
| cat > .github/piwheels-stats.md <<'PIWHEELS_BLOCK' | |
| <!-- PIWHEELS_STATS_START --> | |
| ## 🥧 ARM32 Support (Raspberry Pi) | |
| [](https://www.piwheels.org/project/omnipkg/) | |
| **Latest Version:** `VERSION_PLACEHOLDER` | **Python:** PYTHON_VERS_PLACEHOLDER | [View on piwheels](https://www.piwheels.org/project/omnipkg/) | |
| ```bash | |
| # Install on Raspberry Pi (ARM32) | |
| INSTALL_CMD_PLACEHOLDER | |
| ``` | |
| **Verified Platforms:** | |
| - 🍓 Raspberry Pi (armv6/armv7) - Bullseye (Debian 11), Bookworm (Debian 12), Trixie (Debian 13) | |
| - 📦 Wheel: [`WHEEL_URL_PLACEHOLDER`](WHEEL_URL_PLACEHOLDER) | |
| <!-- PIWHEELS_STATS_END --> | |
| PIWHEELS_BLOCK | |
| sed -i "s|VERSION_PLACEHOLDER|${VERSION}|g" .github/piwheels-stats.md | |
| sed -i "s|PYTHON_VERS_PLACEHOLDER|${PYTHON_VERS}|g" .github/piwheels-stats.md | |
| sed -i "s|INSTALL_CMD_PLACEHOLDER|${INSTALL_CMD}|g" .github/piwheels-stats.md | |
| sed -i "s|WHEEL_URL_PLACEHOLDER|${WHEEL_URL}|g" .github/piwheels-stats.md | |
| if grep -q "<!-- PIWHEELS_STATS_START -->" README.md; then | |
| perl -i -p0e 's/<!-- PIWHEELS_STATS_START -->.*?<!-- PIWHEELS_STATS_END -->//gs' README.md | |
| sed -i '/#### 🥧 piwheels (for Raspberry Pi)/r .github/piwheels-stats.md' README.md | |
| fi | |
| - name: Generate platform support matrix | |
| if: steps.scrape.outputs.builds_found == 'true' | |
| run: | | |
| cat > .github/platform-matrix.json <<MATRIX_JSON | |
| { | |
| "arm32": { | |
| "verified": true, | |
| "version": "${{ steps.version.outputs.version }}", | |
| "python_versions": "${{ steps.scrape.outputs.python_versions }}", | |
| "cp39_bullseye": ${{ steps.scrape.outputs.has_cp39 }}, | |
| "cp311_bookworm": ${{ steps.scrape.outputs.has_cp311 }}, | |
| "cp313_trixie": ${{ steps.scrape.outputs.has_cp313 }}, | |
| "install_command": "${{ steps.scrape.outputs.install_command }}", | |
| "platforms": ["Raspberry Pi", "Raspbian Bullseye", "Raspbian Bookworm", "Raspbian Trixie"] | |
| } | |
| } | |
| MATRIX_JSON | |
| echo "✅ Platform matrix generated" | |
| - name: Commit changes | |
| if: steps.scrape.outputs.builds_found == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add README.md .github/piwheels-stats.md .github/platform-matrix.json || true | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Auto-update piwheels ARM32 stats for v${{ steps.version.outputs.version }} [skip ci]" | |
| git pull --rebase origin main | |
| git push | |
| else | |
| echo "No changes to commit" | |
| fi | |
| - name: Create verification summary | |
| if: steps.scrape.outputs.builds_found == 'true' | |
| run: | | |
| HAS_39="${{ steps.scrape.outputs.has_cp39 }}" | |
| HAS_311="${{ steps.scrape.outputs.has_cp311 }}" | |
| HAS_313="${{ steps.scrape.outputs.has_cp313 }}" | |
| tick() { [ "$1" == "true" ] && echo "✅" || echo "❌"; } | |
| { | |
| echo "## ARM32 Wheel Check — v${{ steps.version.outputs.version }}" | |
| echo "" | |
| echo "| Distro | Python | ARM32 wheel |" | |
| echo "|--------|--------|-------------|" | |
| echo "| Bullseye (Debian 11) | 3.9 | $(tick $HAS_39) |" | |
| echo "| Bookworm (Debian 12) | 3.11 | $(tick $HAS_311) |" | |
| echo "| Trixie (Debian 13) | 3.13 | $(tick $HAS_313) |" | |
| echo "" | |
| echo "**Python Versions:** ${{ steps.scrape.outputs.python_versions }}" | |
| echo "" | |
| echo "**Install Command:**" | |
| echo "\`\`\`bash" | |
| echo "${{ steps.scrape.outputs.install_command }}" | |
| echo "\`\`\`" | |
| echo "" | |
| echo "**Representative wheel:** [${{ steps.scrape.outputs.wheel_file }}](${{ steps.scrape.outputs.wheel_url }})" | |
| } >> $GITHUB_STEP_SUMMARY | |
| - name: Fail if NO wheels exist at all | |
| if: steps.scrape.outputs.builds_found == 'false' | |
| run: | | |
| echo "❌ No ARM32 wheels found for omnipkg ${{ steps.version.outputs.version }} on piwheels" | |
| exit 1 |