Skip to content

Build release wheel #17

Build release wheel

Build release wheel #17

Workflow file for this run

name: Build release wheel
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Existing tag/release to attach the wheel to, for example v2.0.0"
required: true
type: string
python-version:
description: "CPython version(s) to build"
required: true
default: "all"
type: choice
options:
- "all"
- "3.12"
- "3.13"
- "3.14"
publish-to-pypi:
description: "Also publish the built wheel to PyPI"
required: true
default: false
type: boolean
jobs:
resolve:
name: Resolve tag and build matrix
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release.outputs.tag }}
matrix: ${{ steps.release.outputs.matrix }}
steps:
- name: Resolve release tag and matrix
id: release
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
INPUT_PYTHON_VERSION: ${{ inputs.python-version }}
run: |
if [ "$EVENT_NAME" = "release" ]; then
tag="$RELEASE_TAG"
selected="all"
else
tag="$INPUT_TAG"
selected="$INPUT_PYTHON_VERSION"
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
if [ "$selected" = "all" ]; then
echo 'matrix={"python-version":["3.12","3.13","3.14"]}' >> "$GITHUB_OUTPUT"
else
echo "matrix={\"python-version\":[\"$selected\"]}" >> "$GITHUB_OUTPUT"
fi
- name: Check out release source
uses: actions/checkout@v7
with:
ref: ${{ steps.release.outputs.tag }}
- name: Verify tag matches package version
env:
TAG: ${{ steps.release.outputs.tag }}
run: |
tag_version="${TAG#v}"
pkg_version=$(python3 -c "exec(open('senpy/_version.py').read()); print(__version__)")
if [ "$tag_version" != "$pkg_version" ]; then
echo "::error::Tag '$TAG' does not match senpy/_version.py ($pkg_version)." \
"Bump _version.py (and the version test) before releasing."
exit 1
fi
echo "Tag $TAG matches package version $pkg_version"
build-wheel:
name: Build wheel (cp${{ matrix.python-version }})
needs: [resolve]
# 22.04 (glibc 2.35) keeps the manylinux baseline older than latest.
runs-on: ubuntu-22.04
timeout-minutes: 120
permissions:
contents: write
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.resolve.outputs.matrix) }}
env:
CMAKE_BUILD_PARALLEL_LEVEL: "2"
CMAKE_INSTALL_PARALLEL_LEVEL: "2"
MAKEFLAGS: "-j2"
NINJAFLAGS: "-j2"
MAX_JOBS: "2"
steps:
- name: Check out release source
uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.tag }}
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Add swap for native build
run: |
sudo swapoff /swapfile || true
sudo rm -f /swapfile
sudo fallocate -l 12G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
free -h
- name: Install build tooling
run: |
python -m pip install --upgrade pip
python -m pip install build wheel auditwheel patchelf
- name: Build wheel
run: python -m build --wheel --outdir ../dist
working-directory: senpy
- name: Repair wheel for manylinux
run: |
cd dist
for whl in *.whl; do
auditwheel repair "$whl" -w repaired
rm "$whl"
done
mv repaired/*.whl .
rmdir repaired
cd ..
- name: List wheel
run: ls -lh dist
- name: Verify wheel is AVX-512-free
run: python scripts/verify_wheel_isa.py dist/*.whl
- name: Smoke test wheel
run: |
python -m pip install dist/*.whl
cd "$(mktemp -d)"
python -c "import pathlib, senpy; assert pathlib.Path(senpy.__file__).resolve().is_relative_to(pathlib.Path('${{ github.workspace }}').resolve()) is False; print('senpy imported successfully')"
- name: Verify release exists
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: gh release view "$TAG"
- name: Upload wheel to release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: gh release upload "$TAG" dist/*.whl --clobber
- name: Upload wheel artifact
uses: actions/upload-artifact@v7
with:
name: pypi-wheel-${{ matrix.python-version }}
path: dist/*.whl
if-no-files-found: error
build-wheel-macos:
name: Build macOS arm64 wheel (cp${{ matrix.python-version }})
needs: [resolve]
runs-on: macos-15
timeout-minutes: 120
permissions:
contents: write
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.resolve.outputs.matrix) }}
steps:
- name: Check out release source
uses: actions/checkout@v7
with:
ref: ${{ needs.resolve.outputs.tag }}
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
# The wheel bundles Homebrew's libomp, so it can only claim the macOS
# version that libomp was built for. setup-python's interpreters are
# universal2, which would compile _core for x86_64 too; libomp is
# arm64-only, so pin the extension and the wheel tag to arm64.
- name: Install libomp and set deployment target
run: |
brew install libomp
prefix="$(brew --prefix libomp)"
minos="$(otool -l "$prefix/lib/libomp.dylib" | awk '/LC_BUILD_VERSION/{f=1} f&&/minos/{print $2; exit}')"
echo "LIBOMP_PREFIX=$prefix" >> "$GITHUB_ENV"
echo "MACOSX_DEPLOYMENT_TARGET=$minos" >> "$GITHUB_ENV"
echo "ARCHFLAGS=-arch arm64" >> "$GITHUB_ENV"
echo "_PYTHON_HOST_PLATFORM=macosx-$minos-arm64" >> "$GITHUB_ENV"
echo "libomp at $prefix, macOS deployment target $minos"
- name: Install build tooling
run: |
python -m pip install --upgrade pip
python -m pip install build wheel delocate
- name: Build wheel
run: python -m build --wheel --outdir ../dist
working-directory: senpy
- name: Bundle native libraries into wheel
run: |
cd dist
for whl in *.whl; do
delocate-wheel --require-archs arm64 -w repaired -v "$whl"
rm "$whl"
done
mv repaired/*.whl .
rmdir repaired
delocate-listdeps --all *.whl
cd ..
- name: List wheel
run: ls -lh dist
# Remove Homebrew's libomp so the smoke test proves the wheel is self-contained.
- name: Smoke test wheel
run: |
brew uninstall --ignore-dependencies libomp
python -m pip install dist/*.whl
cp senpy/example.py "$RUNNER_TEMP/example.py"
cd "$RUNNER_TEMP"
python -c "import pathlib, senpy; assert pathlib.Path(senpy.__file__).resolve().is_relative_to(pathlib.Path('${{ github.workspace }}').resolve()) is False; print('senpy imported successfully')"
python example.py
- name: Verify release exists
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: gh release view "$TAG"
- name: Upload wheel to release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: gh release upload "$TAG" dist/*.whl --clobber
- name: Upload wheel artifact
uses: actions/upload-artifact@v7
with:
name: pypi-wheel-macos-${{ matrix.python-version }}
path: dist/*.whl
if-no-files-found: error
publish-pypi:
name: Publish to PyPI
needs: [resolve, build-wheel, build-wheel-macos]
# On release events always publish; on manual runs only when requested.
if: github.event_name == 'release' || inputs.publish-to-pypi
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/arcascope-senpy
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v8
with:
pattern: pypi-wheel-*
path: dist
merge-multiple: true
- name: List distributions
run: ls -lh dist
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1