Skip to content

Commit 280762c

Browse files
Technologicatclaude
andcommitted
CI: beef up Windows DLL diagnostic with pefile + per-.pyd probes
First-pass diagnostic showed `vcomp140.dll` is reachable via ctypes.CDLL, so the failure is not at the obvious OpenMP-runtime layer. `dumpbin` was not on the pwsh PATH on the runner, so that arm produced no output. This replaces the dumpbin pass with a pefile-driven probe (pefile is a pure-Python PE parser, installed one-off via pip). For each built .pyd: - list its DLL-level import table (what Windows' loader will try to resolve when LoadLibrary runs), so we know the real shopping list; - attempt `ctypes.WinDLL(pyd)` — which calls LoadLibrary WITHOUT running PyInit, i.e. only exercises the DLL-level import step. A WinDLL failure narrows the problem to Windows' loader; a WinDLL success + Python-import failure narrows it to Cython's cross-module cimport chain that runs inside PyInit; - attempt Python import of every wlsqm module in dependency order and report which one is the first to raise. Together these three probes should pinpoint the exact module and layer (Windows loader vs. Cython init) on the next CI run. Context: commit d82044b (David Caron, Jun 2018) got wlsqm to compile under MSVC — /openmp, drop -lm, popcount.h shim, zero/zero NaN — but probably nobody ever ran the tests on Windows past that point, so this may be the first end-to-end Windows import since the codebase was written. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 51cdfb6 commit 280762c

File tree

1 file changed

+66
-10
lines changed

1 file changed

+66
-10
lines changed

.github/workflows/ci.yml

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,76 @@ jobs:
6969
- name: Install wlsqm
7070
run: pip install --no-build-isolation -e .
7171

72-
# Diagnose Windows DLL-load failures: if importing simple fails, most
73-
# of the time it is vcomp*.dll (MSVC OpenMP runtime) that cannot be
74-
# found on the DLL search path. This step probes it explicitly and
75-
# dumps the DELAY-LOAD dependencies of simple.pyd so the CI log
76-
# pinpoints the missing dependency on the first try.
72+
# Diagnose Windows DLL-load failures: first run said vcomp140 is
73+
# resolvable via ctypes, so the problem is deeper in the chain. Use
74+
# pefile to dump the DLL-level import table of every built .pyd, then
75+
# probe each .pyd with ctypes.WinDLL (which only exercises the Windows
76+
# loader — PyInit never runs, so a WinDLL failure means Windows could
77+
# not resolve a direct DLL import, whereas a WinDLL success + Python
78+
# import failure means the Cython cimport chain in PyInit is what
79+
# fails).
7780
- name: Diagnose extension DLL dependencies (Windows)
7881
if: runner.os == 'Windows'
7982
shell: pwsh
8083
run: |
81-
python -c "import sys; print('python:', sys.version); print('prefix:', sys.prefix)"
82-
python -c "import ctypes; [ctypes.CDLL(name) and print(name, 'OK') for name in ('vcomp140.dll', 'vcomp140d.dll', 'vcruntime140.dll', 'msvcp140.dll')]" 2>&1 | Tee-Object -Append -FilePath dll-probe.log
83-
Get-ChildItem -Recurse -Filter '*.pyd' wlsqm | ForEach-Object { "=== $($_.FullName) ==="; dumpbin /DEPENDENTS $_.FullName 2>&1 }
84-
python -v -c "import wlsqm.fitter.defs" 2>&1 | Select-String -Pattern 'simple|defs|infra|omp|vcomp' | Select-Object -First 40
85-
python -v -c "from wlsqm.fitter import simple" 2>&1 | Select-String -Pattern 'simple|omp|vcomp|DLL' | Select-Object -First 60 || $true
84+
python -m pip install -q pefile
85+
python - <<'PY'
86+
import ctypes, glob, os, sys, traceback
87+
import pefile
88+
89+
print('python:', sys.version)
90+
print('prefix:', sys.prefix)
91+
92+
pyds = sorted(glob.glob('wlsqm/**/*.pyd', recursive=True))
93+
print('=== built .pyd files ===')
94+
for p in pyds:
95+
print(' ', p)
96+
97+
print()
98+
print('=== DLL-level imports per .pyd (pefile) ===')
99+
for p in pyds:
100+
try:
101+
pe = pefile.PE(p, fast_load=True)
102+
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']])
103+
imports = [e.dll.decode(errors='replace') for e in getattr(pe, 'DIRECTORY_ENTRY_IMPORT', [])]
104+
print(f' {p} ->')
105+
for name in imports:
106+
print(f' {name}')
107+
pe.close()
108+
except Exception as e:
109+
print(f' {p} -> pefile error: {e}')
110+
111+
print()
112+
print('=== ctypes.WinDLL probe per .pyd (DLL-level load only, no PyInit) ===')
113+
for p in pyds:
114+
try:
115+
ctypes.WinDLL(os.path.abspath(p))
116+
print(f' WinDLL OK : {p}')
117+
except OSError as e:
118+
print(f' WinDLL FAIL: {p} -- {e}')
119+
120+
print()
121+
print('=== Python-level import probe in dependency order ===')
122+
mods = [
123+
'wlsqm.fitter.defs',
124+
'wlsqm.fitter.infra',
125+
'wlsqm.fitter.polyeval',
126+
'wlsqm.utils.ptrwrap',
127+
'wlsqm.utils.lapackdrivers',
128+
'wlsqm.fitter.interp',
129+
'wlsqm.fitter.impl',
130+
'wlsqm.fitter.simple',
131+
'wlsqm.fitter.expert',
132+
]
133+
for name in mods:
134+
try:
135+
__import__(name)
136+
print(f' import OK : {name}')
137+
except Exception as e:
138+
print(f' import FAIL: {name} -- {type(e).__name__}: {e}')
139+
traceback.print_exc()
140+
break
141+
PY
86142
continue-on-error: true
87143

88144
- name: Run tests

0 commit comments

Comments
 (0)