Skip to content

Commit a7f9ba2

Browse files
Technologicatclaude
andcommitted
CI: Windows diagnostic glob fix + isolate PyInit via importlib spec
Previous diagnostic run executed fine under bash this time, and its output pinpointed a silly mistake: I was globbing `wlsqm/**/*.pyd` but meson-python's editable install puts the compiled .pyd files under `build/<tag>/wlsqm/...`, not under the source tree. The editable loader redirects imports there. Result: my pefile and WinDLL probes both ran on an empty list and produced no output, and the Python-level probe fell back to re-triggering wlsqm/__init__.py which always fails at the same point. This commit: 1. Globs `build/**/*.pyd` so the probes actually see the compiled extensions. 2. Replaces the Python-level import probe with an importlib `spec_from_file_location` probe that loads each .pyd directly by filesystem path, in dependency order. That way PyInit runs for each module individually without going through wlsqm/__init__.py (whose first line that uses OpenMP is what fails). The first module whose PyInit raises is the one we actually care about. 3. Derives the target .pyd for each fully-qualified name by matching subdir + filename stem instead of hardcoding the ABI tag, so the same script works for every Python version in the test matrix. With these three probes (pefile imports table, WinDLL LoadLibrary-only, spec-load PyInit-only), the next CI run should clearly answer whether the failure is (a) Windows loader cannot resolve a direct DLL import of one of our .pyd files, or (b) one of the .pyd PyInit functions raises during its Cython cross-module cimport chain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8743e40 commit a7f9ba2

1 file changed

Lines changed: 50 additions & 18 deletions

File tree

.github/workflows/ci.yml

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,17 @@ jobs:
8585
run: |
8686
python -m pip install -q pefile
8787
python - <<'PY'
88-
import ctypes, glob, os, sys, traceback
88+
import ctypes, glob, importlib.util, os, sys, traceback
8989
import pefile
9090
9191
print('python:', sys.version)
9292
print('prefix:', sys.prefix)
9393
94-
pyds = sorted(glob.glob('wlsqm/**/*.pyd', recursive=True))
94+
# meson-python editable install drops the compiled .pyd files
95+
# under build/<tag>/wlsqm/..., not under the source tree. The
96+
# editable loader redirects imports there, so that is where we
97+
# have to look.
98+
pyds = sorted(glob.glob('build/**/*.pyd', recursive=True))
9599
print('=== built .pyd files ===')
96100
for p in pyds:
97101
print(' ', p)
@@ -111,7 +115,7 @@ jobs:
111115
print(f' {p} -> pefile error: {e}')
112116
113117
print()
114-
print('=== ctypes.WinDLL probe per .pyd (DLL-level load only, no PyInit) ===')
118+
print('=== ctypes.WinDLL probe per .pyd (DLL-level LoadLibrary only, no PyInit) ===')
115119
for p in pyds:
116120
try:
117121
ctypes.WinDLL(os.path.abspath(p))
@@ -120,24 +124,52 @@ jobs:
120124
print(f' WinDLL FAIL: {p} -- {e}')
121125
122126
print()
123-
print('=== Python-level import probe in dependency order ===')
124-
mods = [
125-
'wlsqm.fitter.defs',
126-
'wlsqm.fitter.infra',
127-
'wlsqm.fitter.polyeval',
128-
'wlsqm.utils.ptrwrap',
129-
'wlsqm.utils.lapackdrivers',
130-
'wlsqm.fitter.interp',
131-
'wlsqm.fitter.impl',
132-
'wlsqm.fitter.simple',
133-
'wlsqm.fitter.expert',
127+
print('=== importlib spec-load probe (runs PyInit for each .pyd individually) ===')
128+
# spec_from_file_location loads a .pyd by filesystem path and
129+
# runs its PyInit without going through wlsqm/__init__.py. This
130+
# isolates which module's PyInit is the first to raise.
131+
#
132+
# Extensions that `cimport` other wlsqm modules will still
133+
# trigger Python-level imports during PyInit, and those
134+
# DO go through the package machinery — but we load in
135+
# dependency order below, so by the time simple.pyx's PyInit
136+
# runs, its cimported deps are already in sys.modules.
137+
load_order = [
138+
('wlsqm.fitter.defs', 'fitter/defs'),
139+
('wlsqm.fitter.infra', 'fitter/infra'),
140+
('wlsqm.fitter.polyeval', 'fitter/polyeval'),
141+
('wlsqm.utils.ptrwrap', 'utils/ptrwrap'),
142+
('wlsqm.utils.lapackdrivers', 'utils/lapackdrivers'),
143+
('wlsqm.fitter.interp', 'fitter/interp'),
144+
('wlsqm.fitter.impl', 'fitter/impl'),
145+
('wlsqm.fitter.simple', 'fitter/simple'),
146+
('wlsqm.fitter.expert', 'fitter/expert'),
134147
]
135-
for name in mods:
148+
def find_pyd(subpath):
149+
# subpath is like 'fitter/defs' or 'utils/lapackdrivers'.
150+
# Match by subdir + filename stem; the ABI tag varies with
151+
# the Python version in the test matrix, so we don't hardcode it.
152+
subdir, stem = subpath.split('/')
153+
for p in pyds:
154+
norm = p.replace('\\', '/')
155+
if f'/{subdir}/' in norm and os.path.basename(norm).split('.')[0] == stem:
156+
return p
157+
return None
158+
159+
for fqname, subpath in load_order:
160+
p = find_pyd(subpath)
161+
if p is None:
162+
print(f' not found : {fqname}')
163+
continue
136164
try:
137-
__import__(name)
138-
print(f' import OK : {name}')
165+
spec = importlib.util.spec_from_file_location(fqname, p)
166+
mod = importlib.util.module_from_spec(spec)
167+
sys.modules[fqname] = mod
168+
spec.loader.exec_module(mod)
169+
print(f' PyInit OK : {fqname} ({p})')
139170
except Exception as e:
140-
print(f' import FAIL: {name} -- {type(e).__name__}: {e}')
171+
print(f' PyInit FAIL: {fqname} ({p})')
172+
print(f' {type(e).__name__}: {e}')
141173
traceback.print_exc()
142174
break
143175
PY

0 commit comments

Comments
 (0)