Skip to content

Commit ed3e607

Browse files
committed
Fix setup wheel-build
I fixed the likely wheel-build failures in the release workflow. Changed: - Updated `.github/workflows/python-publish.yml` - `cibuildwheel==2.21.3` -> `cibuildwheel>=3.0,<4` - Added `cp314-*` wheels to match Python 3.14 support - Removed musllinux wheel builds for now to reduce release surface - Replaced forced Linux `CFLAGS/LDFLAGS` with `METBIT_PORTABLE_BUILD=1` - Fixed artifact downloads by downloading `sdist` and `wheels-*` separately - Updated `setup.py` - Avoids `-march=native` during portable wheel builds - Uses Windows-safe compile flags (`/O2`) instead of Unix-only flags - Adds Python 3.14 classifier Verification: - Built a portable Python 3.14 macOS arm64 wheel successfully: `metbit-9.0.0-cp314-cp314-macosx_26_0_arm64.whl` - Installed that wheel into a fresh venv successfully - Smoke test passed: `import metbit`, `pearson_columns(np.eye(5))`, `backend_info()` - `twine check` passes with only the existing long-description warnings Local cibuildwheel limitations: - macOS cibuildwheel could select `cp314`, but cannot complete outside CI without python.org framework Pythons. - Linux cibuildwheel could select `cp314-manylinux_x86_64`, but Docker is not running locally, so I couldn’t complete the container build here. Fixed the release wheel failures from the CI log. What changed: - [python-publish.yml](/Users/kawa/work/kawa-technology/GitHub/metbit/.github/workflows/python-publish.yml:135): upgraded to `cibuildwheel>=3.0,<4` so Python 3.14 wheel selectors work. - [python-publish.yml](/Users/kawa/work/kawa-technology/GitHub/metbit/.github/workflows/python-publish.yml:140): added `cp314-*`, skipped musllinux, and set `METBIT_PORTABLE_BUILD=1`. - [python-publish.yml](/Users/kawa/work/kawa-technology/GitHub/metbit/.github/workflows/python-publish.yml:148): replaced the multiline Windows-breaking `python -c` command with a single-line command and assert `native_c` is present. - [setup.py](/Users/kawa/work/kawa-technology/GitHub/metbit/setup.py:77): removed `-march=native` from portable wheel builds and uses `/O2` on Windows. - [metbit/_native_backend.c](/Users/kawa/work/kawa-technology/GitHub/metbit/metbit/_native_backend.c:344): fixed the OpenMP/GIL macro bug that caused Linux C compilation failure. - [setup.cfg](/Users/kawa/work/kawa-technology/GitHub/metbit/setup.cfg:2): changed deprecated `description-file` to `description_file`. CI root causes found: - macOS: `-march=native` on Apple M3 leaked into x86_64 wheel builds, extension skipped, then `delocate` failed. - Linux: C extension failed to compile due nested `Py_END_ALLOW_THREADS`, extension skipped, then `auditwheel` failed because no shared library existed. - Windows: workflow test command had leading indentation inside `python -c`, causing `IndentationError`. Verification: - YAML parse: passed. - Portable Python 3.14 wheel build: passed, native `.so` included. - Fresh wheel smoke install: passed with `native_c: True`. - OpenMP branch syntax parse: passed. - `twine check`: passed with existing long-description warnings. - `tests/test_native_backend.py --no-cov`: `12 passed`.
1 parent 0fb25bb commit ed3e607

4 files changed

Lines changed: 73 additions & 37 deletions

File tree

.github/workflows/python-publish.yml

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106

107107
# ---------------------------------------------------------------------------
108108
# 3. Binary wheels via cibuildwheel
109-
# Linux: manylinux_2_28 x86_64 + aarch64 (QEMU), musllinux x86_64
109+
# Linux: manylinux_2_28 x86_64 + aarch64 (QEMU)
110110
# macOS: x86_64 + arm64
111111
# Windows: AMD64
112112
# ---------------------------------------------------------------------------
@@ -133,28 +133,19 @@ jobs:
133133
python-version: "3.12"
134134

135135
- name: Install cibuildwheel
136-
run: pip install cibuildwheel==2.21.3
136+
run: pip install "cibuildwheel>=3.0,<4"
137137

138138
- name: Build wheels
139139
env:
140-
CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"
141-
CIBW_SKIP: "*-win32 *-manylinux_i686 pp*"
140+
CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"
141+
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_* pp*"
142142
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
143143
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
144144
CIBW_ARCHS_LINUX: "x86_64 aarch64"
145145
CIBW_ARCHS_MACOS: "x86_64 arm64"
146146
CIBW_ARCHS_WINDOWS: "AMD64"
147-
CIBW_ENVIRONMENT_LINUX: "CFLAGS='-fopenmp' LDFLAGS='-lgomp'"
148-
CIBW_TEST_COMMAND: >
149-
python -c "
150-
import metbit;
151-
assert metbit.__version__, 'version missing';
152-
from metbit._native import pearson_columns, backend_info;
153-
import numpy as np;
154-
r = pearson_columns(np.eye(5), anchor_index=0);
155-
assert abs(r[0] - 1.0) < 1e-12, r[0];
156-
print('OK - backend:', backend_info())
157-
"
147+
CIBW_ENVIRONMENT: "METBIT_PORTABLE_BUILD=1"
148+
CIBW_TEST_COMMAND: 'python -c "import metbit; assert metbit.__version__, \"version missing\"; from metbit._native import pearson_columns, backend_info; import numpy as np; info = backend_info(); assert info[\"native_c\"], info; r = pearson_columns(np.eye(5), anchor_index=0); assert abs(r[0] - 1.0) < 1e-12, r[0]; print(\"OK - backend:\", info)"'
158149
CIBW_TEST_REQUIRES: "numpy scipy scikit-learn"
159150
CIBW_BUILD_VERBOSITY: 1
160151
run: cibuildwheel --output-dir wheelhouse
@@ -175,10 +166,16 @@ jobs:
175166
steps:
176167
- uses: actions/checkout@v4
177168

178-
- name: Download all built artifacts
169+
- name: Download source distribution
179170
uses: actions/download-artifact@v4
180171
with:
181-
pattern: "sdist wheels-*"
172+
name: sdist
173+
path: dist/
174+
175+
- name: Download wheels
176+
uses: actions/download-artifact@v4
177+
with:
178+
pattern: "wheels-*"
182179
path: dist/
183180
merge-multiple: true
184181

@@ -210,10 +207,16 @@ jobs:
210207
url: https://pypi.org/project/metbit/${{ env.RELEASE_TAG }}
211208

212209
steps:
213-
- name: Download all built artifacts
210+
- name: Download source distribution
214211
uses: actions/download-artifact@v4
215212
with:
216-
pattern: "sdist wheels-*"
213+
name: sdist
214+
path: dist/
215+
216+
- name: Download wheels
217+
uses: actions/download-artifact@v4
218+
with:
219+
pattern: "wheels-*"
217220
path: dist/
218221
merge-multiple: true
219222

@@ -250,10 +253,16 @@ jobs:
250253
env:
251254
RELEASE_TAG: ${{ env.RELEASE_TAG }}
252255

253-
- name: Download all built artifacts
256+
- name: Download source distribution
257+
uses: actions/download-artifact@v4
258+
with:
259+
name: sdist
260+
path: release-assets/
261+
262+
- name: Download wheels
254263
uses: actions/download-artifact@v4
255264
with:
256-
pattern: "sdist wheels-*"
265+
pattern: "wheels-*"
257266
path: release-assets/
258267
merge-multiple: true
259268

metbit/_native_backend.c

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ pearson_columns_f32(PyObject *self, PyObject *args)
341341

342342
const float *data = (const float *)buf.buf;
343343
double anchor_sq = 0.0;
344+
int oom_f32 = 0;
344345

345346
Py_BEGIN_ALLOW_THREADS
346347

@@ -355,7 +356,6 @@ pearson_columns_f32(PyObject *self, PyObject *args)
355356

356357
/* Pass 2: covariance and sum-of-squares (parallel when OpenMP available) */
357358
#ifdef _OPENMP
358-
int oom_f32 = 0;
359359
#pragma omp parallel reduction(+:anchor_sq)
360360
{
361361
double *lc = (double *)calloc((size_t)columns, sizeof(double));
@@ -386,12 +386,6 @@ pearson_columns_f32(PyObject *self, PyObject *args)
386386
free(lc); free(ls);
387387
}
388388
}
389-
if (oom_f32) {
390-
Py_END_ALLOW_THREADS
391-
PyMem_Free(means); PyMem_Free(cov); PyMem_Free(col_sq);
392-
PyBuffer_Release(&buf); Py_DECREF(output);
393-
return PyErr_NoMemory();
394-
}
395389
#else
396390
for (Py_ssize_t r = 0; r < rows; ++r) {
397391
const float *row = data + r * columns;
@@ -405,17 +399,23 @@ pearson_columns_f32(PyObject *self, PyObject *args)
405399
}
406400
#endif
407401

408-
for (Py_ssize_t c = 0; c < columns; ++c) {
409-
double denom = sqrt(anchor_sq * col_sq[c]);
410-
if (denom == 0.0) { corr[c] = 0.0 / 0.0; continue; }
411-
double r = cov[c] / denom;
412-
corr[c] = (r > 1.0) ? 1.0 : (r < -1.0) ? -1.0 : r;
402+
if (!oom_f32) {
403+
for (Py_ssize_t c = 0; c < columns; ++c) {
404+
double denom = sqrt(anchor_sq * col_sq[c]);
405+
if (denom == 0.0) { corr[c] = 0.0 / 0.0; continue; }
406+
double r = cov[c] / denom;
407+
corr[c] = (r > 1.0) ? 1.0 : (r < -1.0) ? -1.0 : r;
408+
}
413409
}
414410

415411
Py_END_ALLOW_THREADS
416412

417413
PyMem_Free(means); PyMem_Free(cov); PyMem_Free(col_sq);
418414
PyBuffer_Release(&buf);
415+
if (oom_f32) {
416+
Py_DECREF(output);
417+
return PyErr_NoMemory();
418+
}
419419
return output;
420420
}
421421

@@ -461,6 +461,7 @@ column_variances(PyObject *self, PyObject *args)
461461
memset(var, 0, (size_t)columns * sizeof(double));
462462

463463
const double *data = (const double *)buf.buf;
464+
int oom_var = 0;
464465

465466
Py_BEGIN_ALLOW_THREADS
466467

@@ -475,7 +476,6 @@ column_variances(PyObject *self, PyObject *args)
475476

476477
/* Pass 2: sum of squared deviations - OpenMP parallel over rows */
477478
#ifdef _OPENMP
478-
int oom_var = 0;
479479
#pragma omp parallel
480480
{
481481
double *lvar = (double *)calloc((size_t)columns, sizeof(double));
@@ -519,6 +519,12 @@ column_variances(PyObject *self, PyObject *args)
519519

520520
PyMem_Free(means);
521521
PyBuffer_Release(&buf);
522+
#ifdef _OPENMP
523+
if (oom_var) {
524+
Py_DECREF(output);
525+
return PyErr_NoMemory();
526+
}
527+
#endif
522528
return output;
523529
}
524530

@@ -565,6 +571,7 @@ column_variances_f32(PyObject *self, PyObject *args)
565571
memset(var, 0, (size_t)columns * sizeof(double));
566572

567573
const float *data = (const float *)buf.buf;
574+
int oom_vf = 0;
568575

569576
Py_BEGIN_ALLOW_THREADS
570577

@@ -577,7 +584,6 @@ column_variances_f32(PyObject *self, PyObject *args)
577584
means[c] /= (double)rows;
578585

579586
#ifdef _OPENMP
580-
int oom_vf = 0;
581587
#pragma omp parallel
582588
{
583589
double *lv = (double *)calloc((size_t)columns, sizeof(double));
@@ -621,6 +627,12 @@ column_variances_f32(PyObject *self, PyObject *args)
621627

622628
PyMem_Free(means);
623629
PyBuffer_Release(&buf);
630+
#ifdef _OPENMP
631+
if (oom_vf) {
632+
Py_DECREF(output);
633+
return PyErr_NoMemory();
634+
}
635+
#endif
624636
return output;
625637
}
626638

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[metadata]
2-
description-file = README.rst
2+
description_file = README.rst

setup.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ def _detect_openmp():
7474
_OMP_COMPILE, _OMP_LINK = _detect_openmp()
7575

7676

77+
def _native_compile_args():
78+
"""Return optimization flags suitable for local and portable wheel builds."""
79+
if sys.platform == "win32":
80+
return ["/O2"]
81+
82+
args = ["-O3", "-ffast-math"]
83+
portable_build = os.environ.get("METBIT_PORTABLE_BUILD", "").lower() in {
84+
"1", "true", "yes",
85+
}
86+
if not portable_build:
87+
args.insert(1, "-march=native")
88+
return args
89+
90+
7791
# ---------------------------------------------------------------------------
7892
# Custom build_ext: tolerate compiler errors so a missing C toolchain never
7993
# prevents a pure-Python install.
@@ -99,7 +113,7 @@ def build_extension(self, ext):
99113
_native_ext = Extension(
100114
"metbit._native_backend",
101115
sources=["metbit/_native_backend.c"],
102-
extra_compile_args=["-O3", "-march=native", "-ffast-math"] + _OMP_COMPILE,
116+
extra_compile_args=_native_compile_args() + _OMP_COMPILE,
103117
extra_link_args=_OMP_LINK,
104118
optional=True,
105119
)
@@ -149,5 +163,6 @@ def build_extension(self, ext):
149163
"Programming Language :: Python :: 3.11",
150164
"Programming Language :: Python :: 3.12",
151165
"Programming Language :: Python :: 3.13",
166+
"Programming Language :: Python :: 3.14",
152167
],
153168
)

0 commit comments

Comments
 (0)