Skip to content

fix: repair Windows wheels with delvewheel --ignore-existing - #395

Merged
lgray merged 2 commits into
mainfrom
windows-delvewheel-patch
Jul 27, 2026
Merged

fix: repair Windows wheels with delvewheel --ignore-existing#395
lgray merged 2 commits into
mainfrom
windows-delvewheel-patch

Conversation

@lgray

@lgray lgray commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

cibuildwheel 4.1 runs delvewheel repair by default on Windows, and it fails with

FileNotFoundError: Unable to find library: fastjet.dll

Root cause

Our build installs the fastjet DLLs inside the wheel at fastjet/bin/ (and fastjet/__init__.py adds that directory via os.add_dll_directory). delvewheel resolves dependencies only from --add-path and then PATH — it never looks inside the wheel unless --ignore-existing is passed. In delvewheel 1.13.0, _wheel_repair.py only populates the in-wheel search list under that flag:

https://github.com/adang1345/delvewheel/blob/v1.13.0/delvewheel/_wheel_repair.py#L256-L266 → consumed by find_library() in _dll_utils.py, whose only other search location is PATH.

So --add-path cannot reach these DLLs: they exist only inside the built wheel and in scikit-build's temp build tree, never under {project}. The wheels.yml run of the previous commit shows cibuildwheel expanding it to --add-path D:.\bin (and the folded-scalar quoting turned the whole command into a single quoted token, so it failed before delvewheel even started). Moving the DLLs next to _ext.pyd would not help either — same code path.

Fix

[tool.cibuildwheel.windows]
repair-wheel-command = "delvewheel repair --ignore-existing -w {dest_dir} -v {wheel}"

Put in pyproject.toml rather than a workflow env var because the job that actually failed was ci.yml's test_wheels, not wheels.yml (the failing run logs the default repair_command: delvewheel repair -w {dest_dir} -v {wheel}). pyproject covers both workflows plus local cibuildwheel runs. The CIBW_REPAIR_WHEEL_COMMAND_WINDOWS env block is dropped.

Verification

Run locally with delvewheel 1.13.0 (the version CI installs) against the published fastjet-3.5.1.3-cp314-cp314-win_amd64.whl, which has the same layout as the failing build:

invocation result
delvewheel repair -w out -v <whl> FileNotFoundError: Unable to find library: fastjet.dll — CI failure reproduced
delvewheel repair --ignore-existing -w out -v <whl> fastjet.dll now resolved from fastjet/bin; stops at msvcp140.dll, which macOS has no copy of
... --ignore-existing --add-path <dir containing msvcp140.dll> (simulates a Windows PATH) succeeds

In the successful run, fastjet/bin/*.dll are left in place and un-mangled, fastjet.libs/msvcp140.dll is vendored, and fastjet/__init__.py gets delvewheel's add_dll_directory(fastjet.libs) patch inserted above the existing bin hook and above import fastjet._ext. Wheel size 2544651 -> 2688643 bytes.

Side benefit: the MSVC C++ runtime is now bundled, so the wheel no longer relies on a system VC++ redistributable being installed.

One thing to watch in this run: win_arm64 needs an arm64 msvcp140.dll on PATH on the windows-11-arm runners. If that turns out to be missing, adding --exclude msvcp140.dll restores the pre-4.1 behaviour of bundling nothing extra.

CI result

Run 30314452394 is green. Both Windows wheel jobs repair as expected:

+ delvewheel repair --ignore-existing -w ... -v ...fastjet-...-cp314-cp314-win_amd64.whl
External dependencies to copy into the wheel are
{'msvcp140.dll'}
copying DLLs into fastjet.libs
patching fastjet\__init__.py
fixed wheel written to ...

Identical output on win_arm64 — the arm64 msvcp140.dll concern above did not materialise, so no --exclude is needed. The x64 wheel job then ran the test suite against the repaired wheel: 92 passed, 8 warnings in 10.41s.

Note: because this commit reverts wheels.yml to its state on main, the PR's net diff no longer touches that file, so the wheels workflow (which is paths-filtered on it) will not run on this PR. Happy to kick it off via workflow_dispatch if you want the full 3.10-3.14t matrix exercised before merge.

lgray added 2 commits July 27, 2026 19:12
cibuildwheel 4.1 runs delvewheel by default on Windows. Our build installs
libfastjet & friends into fastjet/bin inside the wheel, but delvewheel only
searches --add-path and PATH for dependencies -- it never looks inside the
wheel unless --ignore-existing is given (delvewheel 1.13.0,
_wheel_repair.py sets _wheel_dirs = None otherwise). Hence
"FileNotFoundError: Unable to find library: fastjet.dll".

Set repair-wheel-command in pyproject.toml rather than a workflow env var so
that ci.yml's test_wheels job, wheels.yml and local cibuildwheel runs all get
it. Verified against the published 3.5.1.3 win_amd64 wheel: default flags
reproduce the failure, --ignore-existing leaves fastjet/bin untouched and
vendors msvcp140.dll into fastjet.libs.

Assisted-by: ClaudeCode:claude-opus-5
@lgray lgray changed the title fix: add {project}\bin to delvewheel path fix: repair Windows wheels with delvewheel --ignore-existing Jul 27, 2026
@lgray
lgray enabled auto-merge (squash) July 27, 2026 23:45
@lgray
lgray merged commit 367dfb2 into main Jul 27, 2026
29 checks passed
@lgray
lgray deleted the windows-delvewheel-patch branch July 27, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant