Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/workflows/cibuildwheel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Build wheels, sdist, and upload to PyPI

# code adapted from antio and openmeeg
# TODO: once numpy 1 support is dropped, enable these jobs; set to only run on
# tagged releases, and only if the test.yml workflow is successful

name: Build
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }}
cancel-in-progress: true
on: # yamllint disable-line rule:truthy
workflow_dispatch:
release:
types: [published]

jobs:
build_wheels:
if: false # disabeling until we can drop support for numpy 1
name: Wheels ${{ matrix.os }} ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel] # windows-11 windows-11-arm
arch: [native]
# TODO: Someday this can be enabled, but need to add emulation and it's slow,
# can use docker/setup-qemu-action
# include:
# - os: ubuntu-latest
# arch: aarch64
fail-fast: false

steps:
- uses: actions/checkout@v5
- uses: pypa/cibuildwheel@v3.2.0
with:
output-dir: wheelhouse
env:
CIBW_ARCHS: ${{ matrix.arch }}
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl

sdist:
timeout-minutes: 10
name: Create sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv build --sdist
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-sdist
path: ./dist/*.tar.gz

check:
needs: [build_wheels, sdist]
timeout-minutes: 10
name: run twine check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v5
with:
pattern: cibw-wheels-*
merge-multiple: true
path: dist
- run: ls -alt . && ls -alt dist/
- uses: astral-sh/setup-uv@v6
with:
activate-environment: true
- run: uv pip install twine -q --upgrade
- run: twine check --strict dist/*

publish:
if: ${{ github.repository == 'freesurfer/surfa' && github.event_name == 'release' }}
needs: [check]
name: publish PyPI
runs-on: ubuntu-latest
permissions:
id-token: write
environment:
name: pypi
url: https://pypi.org/p/surfa
timeout-minutes: 10
steps:
- uses: actions/download-artifact@v5
with:
pattern: cibw-wheels-*
merge-multiple: true
path: dist
- uses: pypa/gh-action-pypi-publish@release/v1
- uses: softprops/action-gh-release@v2
with:
files: dist/*
tag_name: ${{ github.event.release.tag_name }}
43 changes: 33 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
name: Package Test

on: [push]
on:
push:
pull_request:

jobs:
build-linux:
runs-on: ubuntu-latest
test:
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 5
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel]
python-version: ['3.8', '3.9', '3.10', '3.11']

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python 3.8
uses: actions/setup-python@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: ${{ matrix.python-version }}

- name: Display python and system info
run: |
python -c "import sys; print(f'python {sys.version}')"
python -c "import platform; print(f'platform {platform.machine()}')"

- name: Install package
run: pip install .
# need the -e flag so pytest can report coverage
run: pip install -e .

- name: Import test
# simple test for now checking whether general import works
run : python -c "import surfa; print('surfa imported successfully')"

- name: Run tests
working-directory: test
#working-directory: test
# simple test for now checking whether general import works
# we should soon get some actual pytest scripts running
run: python -c 'import surfa'
run: |
pip install pytest pytest-cov
if [ -d "test" ]; then
pytest --cov=surfa --cov-report=term -v
else
echo "No test directory, skipping"
fi
shell: bash
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ surfa.egg-info
docs/.deps
docs/build
docs/reference/api
/wheelhouse

.coverage
26 changes: 26 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,28 @@
[build-system]
requires = ['setuptools', 'wheel', 'Cython>=3.0', 'numpy']

[tool.cibuildwheel]
build = ["cp3{8,9,10,11}-*"]
archs = "native"
before-build = "pip install abi3audit"
test-command = "python -c \"import surfa; print(surfa.__version__)\""

[tool.cibuildwheel.linux]
repair-wheel-command = [
"auditwheel repair -w {dest_dir} {wheel}",
"bash tools/audit_wheel.sh {wheel}",
]

[tool.cibuildwheel.macos]
archs = ["native"]
repair-wheel-command = [
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
"bash tools/audit_wheel.sh {wheel}",
]

[tool.cibuildwheel.windows]
before-build = "pip install delvewheel abi3audit"
repair-wheel-command = [
"delvewheel repair -w {dest_dir} {wheel}",
"bash tools/audit_wheel.sh {wheel}",
]
32 changes: 27 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@

import re
import pathlib
import platform
import sys

from setuptools import setup
from setuptools import dist
from setuptools.extension import Extension
from wheel.bdist_wheel import bdist_wheel


# https://github.com/joerick/python-abi3-package-sample/blob/main/setup.py
class bdist_wheel_abi3(bdist_wheel): # noqa: D101
def get_tag(self): # noqa: D102
python, abi, plat = super().get_tag()

if python.startswith("cp"):
return "cp311", "abi3", plat

return python, abi, plat


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

# configure c extensions
ext_opts = dict(extra_compile_args=['-O3', '-std=c99'])
ext_opts = dict(
extra_compile_args=['-O3', '-std=c99'],
define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')],
)
macros = []
setup_opts = {}
if sys.version_info.minor >= 11 and platform.python_implementation() == "CPython":
# Can create an abi3 wheel (typed memoryviews first available in 3.11)!
ext_opts["define_macros"].append(("Py_LIMITED_API", "0x030B0000"))
ext_opts["py_limited_api"] = True
setup_opts["cmdclass"] = {"bdist_wheel": bdist_wheel_abi3}
extensions = [
Extension('surfa.image.interp', [f'surfa/image/interp.pyx'], **ext_opts),
Extension('surfa.mesh.intersection', [f'surfa/mesh/intersection.pyx'], **ext_opts),
]

from Cython.Build import cythonize
extensions = cythonize(extensions, compiler_directives={'language_level' : '3'})

# since we interface the c stuff with numpy, it's another hard
# requirement at build-time
import numpy as np
Expand Down Expand Up @@ -72,7 +93,7 @@
author='Andrew Hoopes',
author_email='freesurfer@nmr.mgh.harvard.edu',
url='https://github.com/freesurfer/surfa',
python_requires='>=3.6',
python_requires='>=3.8',
packages=packages,
ext_modules=extensions,
include_dirs=include_dirs,
Expand All @@ -84,4 +105,5 @@
'Natural Language :: English',
'Topic :: Scientific/Engineering',
],
**setup_opts,
)
9 changes: 9 additions & 0 deletions tools/audit_wheel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash -eo pipefail
set -x

PY_MINOR=$(python -c "import sys; print(sys.version_info.minor)")
if [ "$PY_MINOR" -lt 11 ]; then
echo "Not checking abi3audit for Python $PY_MINOR < 3.11"
exit 0
fi
abi3audit --strict --report --verbose "$1"
Loading