Skip to content

Commit 82de656

Browse files
authored
Add cibuildwheels action (#166)
Builds Python wheels as part of the CI release action. This should automatically build the C software volume control added in #165 for easy installation.
1 parent fbc24f8 commit 82de656

2 files changed

Lines changed: 78 additions & 28 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@ env:
77
PYTHON_VERSION: "3.12"
88

99
jobs:
10-
build:
11-
name: Builds and prepares for publishing releases to PyPI
10+
validate:
11+
name: Validate release tag
1212
runs-on: ubuntu-latest
1313
outputs:
1414
version: ${{ steps.vars.outputs.tag }}
15-
permissions:
16-
id-token: write # Required for PyPI publishing
1715
steps:
18-
- uses: actions/checkout@v5.0.1
1916
- name: Get tag
2017
id: vars
2118
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
@@ -32,48 +29,90 @@ jobs:
3229
exit 1
3330
fi
3431
fi
32+
33+
build-sdist:
34+
name: Build source distribution
35+
needs: ['validate']
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v5.0.1
3539
- name: Set up Python ${{ env.PYTHON_VERSION }}
3640
uses: actions/setup-python@v6.0.0
3741
with:
3842
python-version: ${{ env.PYTHON_VERSION }}
39-
- name: Install build
40-
run: >-
41-
pip install build tomli tomli-w
43+
- name: Install build dependencies
44+
run: pip install build
4245
- name: Set Python project version from tag
43-
shell: python
44-
run: |-
45-
import tomli
46-
import tomli_w
47-
48-
with open("pyproject.toml", "rb") as f:
49-
pyproject = tomli.load(f)
50-
51-
pyproject["project"]["version"] = "${{ steps.vars.outputs.tag }}"
52-
53-
with open("pyproject.toml", "wb") as f:
54-
tomli_w.dump(pyproject, f)
55-
- name: Build python package
56-
run: >-
57-
python3 -m build
46+
run: sed -i 's/^version = "0.0.0"/version = "${{ needs.validate.outputs.version }}"/' pyproject.toml
47+
- name: Build sdist
48+
run: python3 -m build --sdist
49+
- uses: actions/upload-artifact@v5
50+
with:
51+
name: dist-sdist
52+
path: ./dist/*.tar.gz
5853

54+
build-wheels:
55+
name: Build wheels (${{ matrix.os }}, ${{ matrix.archs }})
56+
needs: ['validate']
57+
runs-on: ${{ matrix.os }}
58+
strategy:
59+
matrix:
60+
include:
61+
- os: ubuntu-latest
62+
archs: x86_64
63+
qemu: false
64+
- os: ubuntu-latest
65+
archs: aarch64
66+
qemu: true
67+
- os: macos-latest
68+
archs: x86_64 arm64
69+
qemu: false
70+
- os: windows-latest
71+
archs: AMD64
72+
qemu: false
73+
steps:
74+
- uses: actions/checkout@v5.0.1
75+
- name: Set up Python ${{ env.PYTHON_VERSION }}
76+
uses: actions/setup-python@v6.0.0
77+
with:
78+
python-version: ${{ env.PYTHON_VERSION }}
79+
- name: Set Python project version from tag
80+
shell: python
81+
run: |
82+
import pathlib
83+
p = pathlib.Path("pyproject.toml")
84+
p.write_text(p.read_text().replace('version = "0.0.0"', 'version = "${{ needs.validate.outputs.version }}"', 1))
85+
- name: Set up QEMU
86+
if: matrix.qemu
87+
uses: docker/setup-qemu-action@v3
88+
with:
89+
platforms: arm64,arm
90+
- name: Build wheels
91+
uses: pypa/cibuildwheel@v2.23.3
92+
env:
93+
CIBW_ENVIRONMENT: "SENDSPIN_REQUIRE_C_EXT=1"
94+
CIBW_BUILD: cp312-* cp313-*
95+
CIBW_ARCHS: ${{ matrix.archs }}
96+
CIBW_SKIP: "*-musllinux_*"
5997
- uses: actions/upload-artifact@v5
6098
with:
61-
name: dist
62-
path: ./dist
99+
name: dist-wheels-${{ matrix.os }}-${{ strategy.job-index }}
100+
path: ./wheelhouse/*.whl
63101

64102
publish:
65-
needs: ['build']
103+
needs: ['build-sdist', 'build-wheels']
66104
environment: 'publish'
67105

68-
name: upload release to PyPI
106+
name: Upload release to PyPI
69107
runs-on: ubuntu-latest
70108
permissions:
71109
id-token: write
72110
steps:
73111
- uses: actions/download-artifact@v6
74112
with:
75-
name: dist
113+
pattern: dist-*
76114
path: dist
115+
merge-multiple: true
77116

78117
- name: Publish release to PyPI
79118
uses: pypa/gh-action-pypi-publish@release/v1

‎setup.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
"""Build C extensions for sendspin."""
22

3+
import os
4+
35
from setuptools import Extension, setup
46
from setuptools.command.build_ext import build_ext
57

8+
_REQUIRE_C_EXT = os.environ.get("SENDSPIN_REQUIRE_C_EXT") == "1"
9+
610

711
class OptionalBuildExt(build_ext):
812
"""A build_ext that treats C extensions as optional.
913
1014
If compilation fails (e.g. missing compiler or headers), the error is
1115
reported but does not abort the build, allowing the numpy fallback to be
1216
used at runtime.
17+
18+
Set SENDSPIN_REQUIRE_C_EXT=1 to make compilation failures fatal (used in
19+
CI to ensure wheels always include the C extension).
1320
"""
1421

1522
def run(self) -> None:
1623
try:
1724
super().run()
1825
except Exception as exc:
26+
if _REQUIRE_C_EXT:
27+
raise
1928
print(
2029
"WARNING: Building C extensions for sendspin failed; "
2130
"falling back to numpy implementation. "
@@ -26,6 +35,8 @@ def build_extension(self, ext: Extension) -> None:
2635
try:
2736
super().build_extension(ext)
2837
except Exception as exc:
38+
if _REQUIRE_C_EXT:
39+
raise
2940
print(
3041
f"WARNING: Failed to build extension {ext.name!r}; "
3142
"continuing without it. "

0 commit comments

Comments
 (0)