Skip to content

Commit 5a168aa

Browse files
authored
Merge pull request #66 from larsoner/cbw
ENH: Add cibuildwheel and automated PyPI release
2 parents 119af70 + bc64811 commit 5a168aa

6 files changed

Lines changed: 195 additions & 15 deletions

File tree

.github/workflows/cibuildwheel.yml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Build wheels, sdist, and upload to PyPI
2+
3+
# code adapted from antio and openmeeg
4+
# TODO: once numpy 1 support is dropped, enable these jobs; set to only run on
5+
# tagged releases, and only if the test.yml workflow is successful
6+
7+
name: Build
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }}
10+
cancel-in-progress: true
11+
on: # yamllint disable-line rule:truthy
12+
workflow_dispatch:
13+
release:
14+
types: [published]
15+
16+
jobs:
17+
build_wheels:
18+
if: false # disabeling until we can drop support for numpy 1
19+
name: Wheels ${{ matrix.os }} ${{ matrix.arch }}
20+
runs-on: ${{ matrix.os }}
21+
strategy:
22+
matrix:
23+
os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel] # windows-11 windows-11-arm
24+
arch: [native]
25+
# TODO: Someday this can be enabled, but need to add emulation and it's slow,
26+
# can use docker/setup-qemu-action
27+
# include:
28+
# - os: ubuntu-latest
29+
# arch: aarch64
30+
fail-fast: false
31+
32+
steps:
33+
- uses: actions/checkout@v5
34+
- uses: pypa/cibuildwheel@v3.2.0
35+
with:
36+
output-dir: wheelhouse
37+
env:
38+
CIBW_ARCHS: ${{ matrix.arch }}
39+
- uses: actions/upload-artifact@v4
40+
with:
41+
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
42+
path: ./wheelhouse/*.whl
43+
44+
sdist:
45+
timeout-minutes: 10
46+
name: Create sdist
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: astral-sh/setup-uv@v6
51+
- run: uv build --sdist
52+
- uses: actions/upload-artifact@v4
53+
with:
54+
name: cibw-wheels-sdist
55+
path: ./dist/*.tar.gz
56+
57+
check:
58+
needs: [build_wheels, sdist]
59+
timeout-minutes: 10
60+
name: run twine check
61+
runs-on: ubuntu-latest
62+
steps:
63+
- uses: actions/checkout@v5
64+
- uses: actions/download-artifact@v5
65+
with:
66+
pattern: cibw-wheels-*
67+
merge-multiple: true
68+
path: dist
69+
- run: ls -alt . && ls -alt dist/
70+
- uses: astral-sh/setup-uv@v6
71+
with:
72+
activate-environment: true
73+
- run: uv pip install twine -q --upgrade
74+
- run: twine check --strict dist/*
75+
76+
publish:
77+
if: ${{ github.repository == 'freesurfer/surfa' && github.event_name == 'release' }}
78+
needs: [check]
79+
name: publish PyPI
80+
runs-on: ubuntu-latest
81+
permissions:
82+
id-token: write
83+
environment:
84+
name: pypi
85+
url: https://pypi.org/p/surfa
86+
timeout-minutes: 10
87+
steps:
88+
- uses: actions/download-artifact@v5
89+
with:
90+
pattern: cibw-wheels-*
91+
merge-multiple: true
92+
path: dist
93+
- uses: pypa/gh-action-pypi-publish@release/v1
94+
- uses: softprops/action-gh-release@v2
95+
with:
96+
files: dist/*
97+
tag_name: ${{ github.event.release.tag_name }}

.github/workflows/test.yml

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
11
name: Package Test
22

3-
on: [push]
3+
on:
4+
push:
5+
pull_request:
46

57
jobs:
6-
build-linux:
7-
runs-on: ubuntu-latest
8+
test:
9+
runs-on: ${{ matrix.os }}
810
strategy:
911
max-parallel: 5
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel]
15+
python-version: ['3.8', '3.9', '3.10', '3.11']
1016

1117
steps:
12-
- uses: actions/checkout@v3
18+
- uses: actions/checkout@v4
1319

14-
- name: Set up Python 3.8
15-
uses: actions/setup-python@v3
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
1622
with:
17-
python-version: 3.8
23+
python-version: ${{ matrix.python-version }}
24+
25+
- name: Display python and system info
26+
run: |
27+
python -c "import sys; print(f'python {sys.version}')"
28+
python -c "import platform; print(f'platform {platform.machine()}')"
1829
1930
- name: Install package
20-
run: pip install .
31+
# need the -e flag so pytest can report coverage
32+
run: pip install -e .
33+
34+
- name: Import test
35+
# simple test for now checking whether general import works
36+
run : python -c "import surfa; print('surfa imported successfully')"
2137

2238
- name: Run tests
23-
working-directory: test
39+
#working-directory: test
2440
# simple test for now checking whether general import works
2541
# we should soon get some actual pytest scripts running
26-
run: python -c 'import surfa'
42+
run: |
43+
pip install pytest pytest-cov
44+
if [ -d "test" ]; then
45+
pytest --cov=surfa --cov-report=term -v
46+
else
47+
echo "No test directory, skipping"
48+
fi
49+
shell: bash

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ surfa.egg-info
1313
docs/.deps
1414
docs/build
1515
docs/reference/api
16+
/wheelhouse
17+
18+
.coverage

pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
[build-system]
22
requires = ['setuptools', 'wheel', 'Cython>=3.0', 'numpy']
3+
4+
[tool.cibuildwheel]
5+
build = ["cp3{8,9,10,11}-*"]
6+
archs = "native"
7+
before-build = "pip install abi3audit"
8+
test-command = "python -c \"import surfa; print(surfa.__version__)\""
9+
10+
[tool.cibuildwheel.linux]
11+
repair-wheel-command = [
12+
"auditwheel repair -w {dest_dir} {wheel}",
13+
"bash tools/audit_wheel.sh {wheel}",
14+
]
15+
16+
[tool.cibuildwheel.macos]
17+
archs = ["native"]
18+
repair-wheel-command = [
19+
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
20+
"bash tools/audit_wheel.sh {wheel}",
21+
]
22+
23+
[tool.cibuildwheel.windows]
24+
before-build = "pip install delvewheel abi3audit"
25+
repair-wheel-command = [
26+
"delvewheel repair -w {dest_dir} {wheel}",
27+
"bash tools/audit_wheel.sh {wheel}",
28+
]

setup.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,24 @@
22

33
import re
44
import pathlib
5+
import platform
6+
import sys
57

68
from setuptools import setup
79
from setuptools import dist
810
from setuptools.extension import Extension
11+
from wheel.bdist_wheel import bdist_wheel
12+
13+
14+
# https://github.com/joerick/python-abi3-package-sample/blob/main/setup.py
15+
class bdist_wheel_abi3(bdist_wheel): # noqa: D101
16+
def get_tag(self): # noqa: D102
17+
python, abi, plat = super().get_tag()
18+
19+
if python.startswith("cp"):
20+
return "cp311", "abi3", plat
21+
22+
return python, abi, plat
923

1024

1125
requirements = [
@@ -30,15 +44,22 @@
3044
base_dir = pathlib.Path(__file__).parent.resolve()
3145

3246
# configure c extensions
33-
ext_opts = dict(extra_compile_args=['-O3', '-std=c99'])
47+
ext_opts = dict(
48+
extra_compile_args=['-O3', '-std=c99'],
49+
define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')],
50+
)
51+
macros = []
52+
setup_opts = {}
53+
if sys.version_info.minor >= 11 and platform.python_implementation() == "CPython":
54+
# Can create an abi3 wheel (typed memoryviews first available in 3.11)!
55+
ext_opts["define_macros"].append(("Py_LIMITED_API", "0x030B0000"))
56+
ext_opts["py_limited_api"] = True
57+
setup_opts["cmdclass"] = {"bdist_wheel": bdist_wheel_abi3}
3458
extensions = [
3559
Extension('surfa.image.interp', [f'surfa/image/interp.pyx'], **ext_opts),
3660
Extension('surfa.mesh.intersection', [f'surfa/mesh/intersection.pyx'], **ext_opts),
3761
]
3862

39-
from Cython.Build import cythonize
40-
extensions = cythonize(extensions, compiler_directives={'language_level' : '3'})
41-
4263
# since we interface the c stuff with numpy, it's another hard
4364
# requirement at build-time
4465
import numpy as np
@@ -72,7 +93,7 @@
7293
author='Andrew Hoopes',
7394
author_email='freesurfer@nmr.mgh.harvard.edu',
7495
url='https://github.com/freesurfer/surfa',
75-
python_requires='>=3.6',
96+
python_requires='>=3.8',
7697
packages=packages,
7798
ext_modules=extensions,
7899
include_dirs=include_dirs,
@@ -84,4 +105,5 @@
84105
'Natural Language :: English',
85106
'Topic :: Scientific/Engineering',
86107
],
108+
**setup_opts,
87109
)

tools/audit_wheel.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash -eo pipefail
2+
set -x
3+
4+
PY_MINOR=$(python -c "import sys; print(sys.version_info.minor)")
5+
if [ "$PY_MINOR" -lt 11 ]; then
6+
echo "Not checking abi3audit for Python $PY_MINOR < 3.11"
7+
exit 0
8+
fi
9+
abi3audit --strict --report --verbose "$1"

0 commit comments

Comments
 (0)