Skip to content

Commit 43911e7

Browse files
Technologicatclaude
andcommitted
CI: fix wheel test-command shadowing + conda-forge libomp for macOS
Previous run produced a green test matrix (all 12 jobs) and sdist, but all three build-wheels jobs failed. Two independent root causes: 1. Linux and Windows wheels BUILT fine, but cibuildwheel's test-command failed with `ModuleNotFoundError: No module named 'wlsqm.fitter.defs'`. Root cause: pytest's default `prepend` import mode walks up the package tree from each test file looking for a `__init__.py`-less ancestor, and inserts that ancestor into sys.path. With `tests/__init__.py` present, pytest treated `tests/` as a package, walked up to `/project`, and inserted `/project` into sys.path. Python then resolved `import wlsqm` to `/project/wlsqm/` (source tree, no compiled .so files) instead of the installed wheel at `site-packages/wlsqm/`. The source-tree `__init__.py` does `from .fitter.simple import *` which fails because `defs.pyx` isn't a compiled module. Fix: delete `tests/__init__.py` and change the three test files that had `from .conftest import ...` to plain `from conftest import ...`. Without `tests/__init__.py`, pytest stops the walk at `tests/` and inserts `tests/` (not `/project`) into sys.path, which makes `conftest` importable by bare name and leaves the installed wheel visible at `site-packages/wlsqm/`. The test job is unaffected because meson-python's editable loader intercepts `import wlsqm` regardless of sys.path order. Verified locally: `pytest tests/` still 57/57 green. 2. macOS wheel failed at compile time with: `fatal error: 'omp.h' file not found` Root cause: Cython's `cimport openmp` in simple.pyx, impl.pyx, expert.pyx, and lapackdrivers.pyx unconditionally emits `#include <omp.h>` in the generated C. Meson's `dependency('openmp', required: false)` fallback only controls whether `-fopenmp` is passed — it does not prevent Cython from emitting the header include. Apple Clang ships no `omp.h`, so the previous commit's "drop libomp entirely, fall back to serial" approach was not actually a viable baseline on macOS. Fix: install conda-forge's `llvm-openmp` via a standalone micromamba in cibuildwheel's macOS before-all. conda-forge ships llvm-openmp rebuilt with a conservative deployment target (11.0 on arm64, matching cibuildwheel's default), so delocate-wheel is happy to bundle the resulting `libomp.dylib` into the wheel without the "Library dependencies do not satisfy target MacOS version" refusal. CPPFLAGS / LDFLAGS / PKG_CONFIG_PATH / DYLD_FALLBACK_LIBRARY_PATH are set in `[tool.cibuildwheel.macos].environment` so that meson discovers libomp during the build and delocate finds it at bundling time. Same pattern as scipy and numpy's macOS wheel CI. Also add `.claude/` to .gitignore (Claude Code internals: the scheduled_tasks lockfile was about to sneak into `git add -A`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4211688 commit 43911e7

File tree

6 files changed

+26
-18
lines changed

6 files changed

+26
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ dist/
1717
# Cython HTML annotations (generated by `cython -a`)
1818
wlsqm/fitter/*.html
1919
wlsqm/utils/*.html
20+
.claude/

pyproject.toml

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,29 @@ skip = "*-win32 *-manylinux_i686 *-musllinux*"
119119
test-requires = ["pytest", "numpy", "scipy"]
120120
test-command = "pytest {project}/tests -v"
121121

122-
# macOS wheels ship without OpenMP. We could `brew install libomp` in a
123-
# `[tool.cibuildwheel.macos] before-all` step and let delocate-wheel bundle
124-
# libomp.dylib into the wheel, but Homebrew's current libomp (22.x) has a
125-
# minimum macOS target (15.0) newer than cibuildwheel's default arm64 target
126-
# (11.0). delocate-wheel correctly refuses to bundle it because the resulting
127-
# wheel would lie about its compatibility.
122+
# macOS: ship parallel wheels by installing conda-forge's `llvm-openmp`
123+
# via a standalone micromamba in `before-all`. Apple Clang does not bundle
124+
# an OpenMP runtime, and Homebrew's current libomp (22.x) has a minimum
125+
# macOS target of 15.0, so `delocate-wheel` refuses to bundle it (the
126+
# wheel's default cibuildwheel arm64 target is 11.0 and delocate does not
127+
# let a wheel lie about its compatibility). conda-forge's llvm-openmp is
128+
# rebuilt with a conservative deployment target that matches our wheel
129+
# target, so delocate is happy to bundle the .dylib.
128130
#
129-
# We could either (a) bump MACOSX_DEPLOYMENT_TARGET to 15.0, shrinking the
130-
# supported macOS range by ~4 years, or (b) build libomp from source with a
131-
# lower target (complex CI). Instead, we lean on meson's
132-
# `dependency('openmp', required: false)` fallback: without libomp on the
133-
# build host, meson builds the extension in serial mode. Cython's `prange`
134-
# degrades to plain `range`, tests still pass, and macOS wheels are slower
135-
# but portable all the way back to macOS 11. Users who want parallel fits
136-
# on macOS can install from source: `brew install libomp` and then
137-
# `pip install --no-binary wlsqm wlsqm`. README.md documents this.
131+
# This is the same pattern scipy and numpy use on their macOS wheel CI.
132+
#
133+
# A serial-mode macOS build is NOT a usable fallback here despite meson's
134+
# `dependency('openmp', required: false)`, because Cython's `cimport openmp`
135+
# in simple/impl/expert/lapackdrivers unconditionally emits `#include <omp.h>`
136+
# in the generated C. Without libomp headers at build time, Apple Clang
137+
# errors out on the missing omp.h before meson's fallback path can take
138+
# effect. So we need libomp headers at build time regardless.
139+
[tool.cibuildwheel.macos]
140+
before-all = '''
141+
curl -Ls https://micro.mamba.pm/api/micromamba/osx-arm64/latest | tar -xvj -C /tmp bin/micromamba
142+
/tmp/bin/micromamba create -y -p /tmp/libomp-env -c conda-forge llvm-openmp
143+
'''
144+
environment = { CPPFLAGS = "-Xpreprocessor -fopenmp -I/tmp/libomp-env/include", LDFLAGS = "-L/tmp/libomp-env/lib -Wl,-rpath,/tmp/libomp-env/lib -lomp", DYLD_FALLBACK_LIBRARY_PATH = "/tmp/libomp-env/lib", PKG_CONFIG_PATH = "/tmp/libomp-env/lib/pkgconfig" }
138145

139146
[dependency-groups]
140147
dev = [

tests/__init__.py

Whitespace-only changes.

tests/test_expert.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import wlsqm
1818

19-
from .conftest import poly2d_order2, poly3d_order2
19+
from conftest import poly2d_order2, poly3d_order2
2020

2121

2222
def _make_expert_2d(ncases, nk_per_case, order=2, algorithm=None, ntasks=1):

tests/test_interp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import wlsqm
1313

14-
from .conftest import poly1d_order2, poly2d_order2, poly3d_order2
14+
from conftest import poly1d_order2, poly2d_order2, poly3d_order2
1515

1616

1717
def test_interp_2d_function_value_matches_analytical(rng):

tests/test_simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import wlsqm
1212

13-
from .conftest import ( # noqa: E402
13+
from conftest import ( # noqa: E402
1414
poly1d_order2,
1515
poly2d_order2,
1616
poly2d_order3,

0 commit comments

Comments
 (0)