Skip to content

Commit 8843eb1

Browse files
authored
abi3 compatible wheels (#109)
* make wheels abi3 compatible for linux * nanobind supports 3.12+ * fix wheel ABI build * add abi3 testing * fix wheel testing * correctly extract deps * add missing script * simply deps install * use -r * use uv * skip due to cross-path * use build[uv] * don't use build[uv] * don't install uv
1 parent 3375a45 commit 8843eb1

File tree

6 files changed

+79
-13
lines changed

6 files changed

+79
-13
lines changed

.github/workflows/build-and-deploy.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,41 @@ jobs:
3232
name: tetgen-wheels-${{ matrix.os }}
3333
path: ./wheelhouse/*.whl
3434

35+
test_abi3:
36+
name: Test ABI3 wheels
37+
needs: build_wheels
38+
runs-on: ${{ matrix.os }}
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
python-version: ['3.13'] # , '3.14'] # skip 3.14 testing until vtk 9.6 is released
43+
os: [ubuntu-24.04, windows-2025, macos-latest, macos-15-intel, ubuntu-24.04-arm]
44+
steps:
45+
- uses: actions/checkout@v6
46+
- uses: actions/setup-python@v6
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
- uses: actions/download-artifact@v7
50+
with:
51+
pattern: tetgen-wheels-${{ matrix.os }}
52+
path: wheels/
53+
- name: Find ABI3 wheel
54+
id: find_wheel
55+
shell: bash
56+
run: |
57+
WHEEL=$(ls wheels/*abi3*.whl | head -n 1)
58+
echo "wheel=$WHEEL" >> "$GITHUB_OUTPUT"
59+
- name: Install wheel
60+
run: pip install "${{ steps.find_wheel.outputs.wheel }}"
61+
- name: Install test dependencies from source
62+
run: |
63+
pip install tomli
64+
python tools/extract-deps.py
65+
pip install -r requirements-tests.txt
66+
- name: Run tests
67+
run: pytest
68+
69+
3570
build_sdist:
3671
name: Build source distribution
3772
runs-on: ubuntu-latest

pyproject.toml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dependencies = [
2323
description = "Python interface to tetgen"
2424
name = "tetgen"
2525
readme = "README.rst"
26-
requires-python = ">=3.10"
26+
requires-python = ">=3.10,<3.15"
2727
version = "0.9.dev0"
2828

2929
[project.optional-dependencies]
@@ -61,11 +61,18 @@ Homepage = 'https://github.com/pyvista/tetgen'
6161

6262
[tool.cibuildwheel]
6363
archs = ["auto64"] # 64-bit only
64-
build = "cp310-* cp311-* cp312-* cp313-*" # " cp314*" # skip 3.14 until vtk 9.6 is released
65-
skip = " cp314t-* *musllinux*" # disable musl-based wheels and free threading
64+
before-build = "pip install abi3audit"
65+
build = "cp310-* cp311-* cp312-*" # 3.12+ are abi3 wheels
66+
skip = "*musllinux*"
6667
test-command = "pytest {project}/tests -vv"
6768
test-requires = "pytest pyvista meshio pymeshfix"
6869

70+
[tool.cibuildwheel.linux]
71+
repair-wheel-command = [
72+
"auditwheel repair -w {dest_dir} {wheel}",
73+
"bash tools/audit_wheel.sh {wheel}"
74+
]
75+
6976
[tool.cibuildwheel.macos]
7077
archs = ["native"]
7178

@@ -83,3 +90,6 @@ junit_family = "legacy"
8390
[tool.ruff]
8491
indent-width = 4
8592
line-length = 99
93+
94+
[tool.scikit-build]
95+
wheel.py-api = "cp312"

src/tetgen/pytetgen.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
MTR_POINTDATA_KEY = "target_size"
2424

2525
invalid_input = TypeError(
26-
"Invalid input. First argument must be either a pyvista.PolyData object or vertex array, followed by a face arrays and optionally a face marker array."
26+
"Invalid input. First argument must be either a pyvista.PolyData object or a vertex array and a face array and optionally a face marker array."
2727
)
2828

2929

@@ -193,22 +193,22 @@ def store_mesh(mesh: "PolyData") -> None:
193193
faces = mesh._connectivity_array.reshape(-1, 3).astype(np.int32, copy=False)
194194
self._tetgen.load_mesh(points, faces)
195195

196-
if "PolyData" in str(type(arg0)): # check without importing
197-
from pyvista.core.pointset import PolyData
198-
199-
if not isinstance(arg0, PolyData):
200-
raise TypeError(f"Unknown type {type(arg0)}. Expected a `pyvista.PolyData`.")
201-
mesh: PolyData = arg0
202-
203-
store_mesh(mesh)
204-
elif isinstance(arg0, (np.ndarray, list)):
196+
if isinstance(arg0, (np.ndarray, list)):
205197
points = np.asarray(arg0, dtype=np.float64)
206198
if isinstance(arg1, (np.ndarray, list)) and isinstance(arg2, (np.ndarray, list)):
207199
self._load_arrays(points, arg1, arg2)
208200
elif isinstance(arg1, (np.ndarray, list)):
209201
self._load_arrays(points, arg1)
210202
else:
211203
raise invalid_input
204+
elif "PolyData" in str(type(arg0)): # check without importing
205+
from pyvista.core.pointset import PolyData
206+
207+
if not isinstance(arg0, PolyData):
208+
raise TypeError(f"Unknown type {type(arg0)}. Expected a `pyvista.PolyData`.")
209+
mesh: PolyData = arg0
210+
211+
store_mesh(mesh)
212212
elif isinstance(arg0, (str, Path)):
213213
try:
214214
import pyvista.core as pv

tests/test_tetgen.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23

34
import numpy as np
45
import pytest
@@ -73,6 +74,9 @@ def test_attributes() -> None:
7374
assert isinstance(tet.edges, np.ndarray)
7475

7576

77+
@pytest.mark.skipif(
78+
sys.platform.startswith("win"), reason="Skip on Windows due to cross-drive relpath issue"
79+
)
7680
def test_numpy_tetrahedralize(tmpdir):
7781
v = np.array(
7882
[

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 12 ]; then
6+
echo "Not checking abi3audit for Python $PY_MINOR < 3.12"
7+
exit 0
8+
fi
9+
abi3audit --strict --report --verbose "$1"

tools/extract-deps.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Create a requirements_testing.txt file directly from a pyproject.toml."""
2+
3+
import tomli
4+
5+
data = tomli.load(open("pyproject.toml", "rb"))
6+
deps = data["project"]["optional-dependencies"]["tests"]
7+
with open("requirements-tests.txt", "w") as f:
8+
f.write("\n".join(deps))

0 commit comments

Comments
 (0)