Build and publish wheels workflow (ariebovenberg/whenever)
The Build and publish wheels workflow from ariebovenberg/whenever, explained and optimized by Latchkey.
CI health: F - at risk
Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Build and publish wheels workflow from the ariebovenberg/whenever repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Build and publish wheels
on:
push:
tags:
- "*"
workflow_dispatch:
permissions:
contents: read
jobs:
check-tag:
name: Check tag correctness
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
steps:
- uses: actions/checkout@v7
- run: |
version=$(grep -E '^version\s*=' pyproject.toml | sed -E 's/^version\s*=\s*"([^"]+)".*/\1/')
tag="${GITHUB_REF_NAME}"
echo "Detected version in pyproject.toml: $version"
echo "Detected tag name: $tag"
# Compare them
if [ "$version" != "$tag" ]; then
echo "❌ Tag ($tag) does not match version in pyproject.toml ($version)"
exit 1
fi
echo "✅ Tag matches version. Proceeding with release..."
binary:
needs: [check-tag]
name: build on ${{ matrix.os }} (${{ matrix.target }}) (${{ matrix.manylinux || 'auto' }})
strategy:
fail-fast: false
matrix:
include:
# manylinux targets
- os: linux
target: x86_64
- os: linux
target: x86
- os: linux
target: aarch64
- os: linux
target: armv7
- os: linux
target: ppc64le
- os: linux
target: s390x
# musllinux targets
- os: linux
target: x86_64
manylinux: musllinux_1_2
- os: linux
target: x86
manylinux: musllinux_1_2
- os: linux
target: aarch64
manylinux: musllinux_1_2
- os: linux
target: armv7
manylinux: musllinux_1_2
# windows
- os: windows
target: x86_64
- os: windows
python-architecture: x86
target: x86
# macos
- os: macos
target: x86_64
- os: macos
target: aarch64
runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Generate third-party license information
run: |
cargo install cargo-3pl --version 0.1.3
cargo 3pl > LICENSE-THIRD-PARTY
- name: Sync Python environment
run: uv sync --locked --group dev-extra --no-install-project
- name: build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --strip --out dist --interpreter '3.10 3.11 3.12 3.13 3.14 3.14t'
manylinux: ${{ matrix.manylinux || 'auto' }}
rust-toolchain: "1.93"
- run: uv run twine check --strict dist/*
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-binary-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'auto' }}
path: dist/*
pure-python:
needs: [check-tag]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- run: uv sync --locked --all-groups --no-install-project
- name: Build sdist and pure-Python wheel
run: |
uv build
env:
WHENEVER_NO_BUILD_RUST_EXT: 1
- run: uv run twine check --strict dist/*
- name: Upload pure Python wheels and sdist
uses: actions/upload-artifact@v7
with:
name: wheels-pure-python
path: dist/*
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [binary, pure-python]
environment: Release
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
- name: Sanity check wheel count
run: |
COUNT=$(find wheels-* -maxdepth 1 -type f | wc -l)
EXPECTED=86
if [ "$COUNT" -ne "$EXPECTED" ]; then
echo "Expected exactly $EXPECTED wheels, found $COUNT"
exit 1
fi
- name: Gather wheels
run: |
mkdir -p dist
mv wheels-*/* dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Build and publish wheels on: push: tags: - "*" workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: check-tag: timeout-minutes: 30 name: Check tag correctness runs-on: latchkey-small if: "startsWith(github.ref, 'refs/tags/')" steps: - uses: actions/checkout@v7 - run: | version=$(grep -E '^version\s*=' pyproject.toml | sed -E 's/^version\s*=\s*"([^"]+)".*/\1/') tag="${GITHUB_REF_NAME}" echo "Detected version in pyproject.toml: $version" echo "Detected tag name: $tag" # Compare them if [ "$version" != "$tag" ]; then echo "❌ Tag ($tag) does not match version in pyproject.toml ($version)" exit 1 fi echo "✅ Tag matches version. Proceeding with release..." binary: timeout-minutes: 30 needs: [check-tag] name: build on ${{ matrix.os }} (${{ matrix.target }}) (${{ matrix.manylinux || 'auto' }}) strategy: fail-fast: false matrix: include: # manylinux targets - os: linux target: x86_64 - os: linux target: x86 - os: linux target: aarch64 - os: linux target: armv7 - os: linux target: ppc64le - os: linux target: s390x # musllinux targets - os: linux target: x86_64 manylinux: musllinux_1_2 - os: linux target: x86 manylinux: musllinux_1_2 - os: linux target: aarch64 manylinux: musllinux_1_2 - os: linux target: armv7 manylinux: musllinux_1_2 # windows - os: windows target: x86_64 - os: windows python-architecture: x86 target: x86 # macos - os: macos target: x86_64 - os: macos target: aarch64 runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest steps: - uses: actions/checkout@v7 - uses: astral-sh/setup-uv@v7 - uses: actions/setup-python@v6 with: cache: 'pip' python-version: '3.13' - name: Generate third-party license information run: | cargo install cargo-3pl --version 0.1.3 cargo 3pl > LICENSE-THIRD-PARTY - name: Sync Python environment run: uv sync --locked --group dev-extra --no-install-project - name: build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --strip --out dist --interpreter '3.10 3.11 3.12 3.13 3.14 3.14t' manylinux: ${{ matrix.manylinux || 'auto' }} rust-toolchain: "1.93" - run: uv run twine check --strict dist/* - name: Upload wheels uses: actions/upload-artifact@v7 with: name: wheels-binary-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'auto' }} path: dist/* pure-python: timeout-minutes: 30 needs: [check-tag] runs-on: latchkey-small steps: - uses: actions/checkout@v7 - uses: astral-sh/setup-uv@v7 - uses: actions/setup-python@v6 with: cache: 'pip' python-version: '3.13' - run: uv sync --locked --all-groups --no-install-project - name: Build sdist and pure-Python wheel run: | uv build env: WHENEVER_NO_BUILD_RUST_EXT: 1 - run: uv run twine check --strict dist/* - name: Upload pure Python wheels and sdist uses: actions/upload-artifact@v7 with: name: wheels-pure-python path: dist/* release: timeout-minutes: 30 name: Release runs-on: latchkey-small if: "startsWith(github.ref, 'refs/tags/')" needs: [binary, pure-python] environment: Release permissions: id-token: write steps: - uses: actions/download-artifact@v8 - name: Sanity check wheel count run: | COUNT=$(find wheels-* -maxdepth 1 -type f | wc -l) EXPECTED=86 if [ "$COUNT" -ne "$EXPECTED" ]; then echo "Expected exactly $EXPECTED wheels, found $COUNT" exit 1 fi - name: Gather wheels run: | mkdir -p dist mv wheels-*/* dist - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
3 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.