Repository navigation
Build release wheel #17
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
| 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 |