Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1c7088c
Add a default repair wheel command for Windows
agriyakhetarpal May 1, 2026
bfd3970
Install `delvewheel` into Windows build tools
agriyakhetarpal May 1, 2026
a2a857f
Add delvewheel to constraints file
agriyakhetarpal May 1, 2026
81374bb
Regenerate constraints
agriyakhetarpal May 1, 2026
151e4a6
Docs
agriyakhetarpal May 1, 2026
2488482
Suggest how to disable Windows wheel repair
agriyakhetarpal May 1, 2026
222abf5
Add tests
agriyakhetarpal May 1, 2026
4bd8aa7
Run `delvewheel` with verbose mode as default
agriyakhetarpal May 1, 2026
8cfdbf7
Add note about telling delvewheel where to look
agriyakhetarpal May 1, 2026
e9a8272
Partially revert 81374bb8fc43acefed76cb13d761a9e6cf6afa58
agriyakhetarpal May 1, 2026
7d430af
Add `pip` and `uv` cases for build installations
agriyakhetarpal May 1, 2026
d63bfad
Ignore `python-native.dll` for GraalPy
agriyakhetarpal May 1, 2026
233e1ab
Ignore MSVC DLLs, try Windows amd64 graalpy happy
agriyakhetarpal May 1, 2026
ad4913f
Fix last remaining Windows test failure (famous last words?)
agriyakhetarpal May 2, 2026
d3d1230
Merge main
agriyakhetarpal May 6, 2026
2dc4c0f
Update constraints
agriyakhetarpal May 6, 2026
382c962
Drop workaround unneeded with delvewheel v1.12.1
agriyakhetarpal May 6, 2026
1fba476
Update docs/options.md
agriyakhetarpal May 7, 2026
3187f89
Drop `test_delvewheel_default_on_windows`
agriyakhetarpal May 8, 2026
17d38ba
Partially revert "Update constraints"
agriyakhetarpal May 8, 2026
9251d60
Add a test case when repair command is `""`
agriyakhetarpal May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion cibuildwheel/platforms/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,22 @@ def setup_python(

log.step("Installing build tools...")
match build_frontend:
case "pip":
call(
"pip",
"install",
"--upgrade",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
case "build":
call(
"pip",
"install",
"--upgrade",
"build[virtualenv]",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
Expand All @@ -340,9 +350,25 @@ def setup_python(
where_python,
"--upgrade",
"build[virtualenv]",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
case "uv":
assert uv_path is not None
call(
uv_path,
"pip",
"install",
"--python",
where_python,
"--upgrade",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
case _:
assert_never(build_frontend)

if python_libs_base:
# Set up the environment for various backends to enable cross-compilation
Expand Down Expand Up @@ -540,8 +566,30 @@ def build(options: Options, tmp_path: Path) -> None:

if build_options.repair_command:
log.step("Repairing wheel...")
repair_command = build_options.repair_command
if (
config.identifier.split("-")[0].startswith("gp")
and "delvewheel" in repair_command
):
# GraalPy wheels need several DLLs excluded, that delvewheel excludes
# for CPython/PyPy via wheel-tag patterns, but not so for GraalPy:
# 1. python-native.dll: this is GraalPy's runtime library (analogous to
# CPython's python3X.dll) and shouldn't be bundled into the wheel.
# See: https://github.com/oracle/graalpython/blob/eab533503e96441c64e2427a8b81b5b38117c899/scripts/wheelbuilder/repair_wheels.py#L66-L82
# 2. vcruntime140.dll / vcruntime140_1.dll: delvewheel's tag-based table
# excludes them but it only covers CPython/PyPy wheel tags, so for
# GraalPy these must be excluded explicitly to avoid a file-access
# error when the DLL is already loaded by the host Python process.
repair_command = repair_command.replace(
"delvewheel repair",
"delvewheel repair"
" --exclude python-native.dll"
" --exclude vcruntime140.dll"
" --exclude vcruntime140_1.dll",
1,
)
repair_command_prepared = prepare_command(
build_options.repair_command,
repair_command,
wheel=built_wheel,
dest_dir=repaired_wheel_dir,
package=build_options.package_dir,
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python310.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -20,6 +22,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python311.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -18,6 +20,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python312.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -18,6 +20,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python313.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -18,6 +20,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python314.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -18,6 +20,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python38.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.2.2.post1
# via -r cibuildwheel/resources/constraints.in
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.10.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.16.1
Expand All @@ -20,6 +22,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==25.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.6
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints-python39.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.19.1
Expand All @@ -20,6 +22,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.4.0
Expand Down
1 change: 1 addition & 0 deletions cibuildwheel/resources/constraints.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pip
build
delocate
delvewheel
virtualenv
4 changes: 4 additions & 0 deletions cibuildwheel/resources/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ build==1.4.3
# via -r cibuildwheel/resources/constraints.in
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.25.2
Expand All @@ -18,6 +20,8 @@ packaging==26.0
# via
# build
# delocate
pefile==2024.8.26
# via delvewheel
pip==26.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
Expand Down
1 change: 1 addition & 0 deletions cibuildwheel/resources/defaults.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"

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

[tool.cibuildwheel.android]

Expand Down
38 changes: 22 additions & 16 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ Default:

- on Linux: `'auditwheel repair -w {dest_dir} {wheel}'`
- on macOS: `'delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}'`
- on Windows: `'delvewheel repair -w {dest_dir} -v {wheel}'`
- on Android: There is no default command, but cibuildwheel will add `libc++` to the
wheel if anything links against it. Setting a command will replace this behavior.
- on Pyodide: You can use `pyodide auditwheel repair --libdir /path/to/libraries --output-dir {dest_dir} {wheel}` command to repair the wheel.
Expand All @@ -933,29 +934,35 @@ The command is run in a shell, so you can run multiple commands like `cmd1 && cm
Platform-specific environment variables are also available:<br/>
`CIBW_REPAIR_WHEEL_COMMAND_MACOS` | `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS` | `CIBW_REPAIR_WHEEL_COMMAND_LINUX` | `CIBW_REPAIR_WHEEL_COMMAND_ANDROID` | `CIBW_REPAIR_WHEEL_COMMAND_IOS` | `CIBW_REPAIR_WHEEL_COMMAND_PYODIDE`

!!! tip
cibuildwheel doesn't yet ship a default repair command for Windows.
!!! note "Windows: telling delvewheel where to find DLLs"
On Windows, delvewheel searches the wheel itself and the directories on `PATH` for external DLL dependencies. If your DLLs are already discoverable via `PATH`, (say, installed by a package manager that adds itself and the relevant directories to `PATH`), the default repair command should be sufficient.
Comment thread
agriyakhetarpal marked this conversation as resolved.
Outdated

**If that's an issue for you, check out [delvewheel]** - a new package that aims to do the same as auditwheel or delocate for Windows.
If your project links against DLLs in a custom location – such as a [vcpkg](https://vcpkg.io/) or [Conan](https://conan.io/) install tree, or a manually built library directory, you may pass `--add-path` to tell delvewheel where to look. The flag can be used multiple times for more than one directory:

Because delvewheel is still relatively early-stage, cibuildwheel does not yet run it by default. However, we'd recommend giving it a try! See the examples below for usage.
```toml
[tool.cibuildwheel.windows]
repair-wheel-command = "delvewheel repair --add-path C:/vcpkg/installed/x64-windows/bin --add-path C:/mylibs/bin -w {dest_dir} -v {wheel}"
```

[Delvewheel]: https://github.com/adang1345/delvewheel
You can also reference environment variables expanded by the shell at build time, for example if the path is set during `before-build`:

```yaml
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair --add-path %VCPKG_INSTALLED_DIR%\\x64-windows\\bin -w {dest_dir} -v {wheel}"
```

#### Examples

!!! tab examples "pyproject.toml"

```toml
# Use delvewheel on windows
[tool.cibuildwheel.windows]
before-build = "pip install delvewheel"
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"

# Don't repair macOS wheels
[tool.cibuildwheel.macos]
repair-wheel-command = ""

# Don't repair Windows wheels
[tool.cibuildwheel.windows]
repair-wheel-command = ""

# Pass the `--lib-sdir .` flag to auditwheel on Linux
[tool.cibuildwheel.linux]
repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
Expand All @@ -980,7 +987,7 @@ Platform-specific environment variables are also available:<br/>
]
[tool.cibuildwheel.windows]
repair-wheel-command = [
"copy {wheel} {dest_dir}",
"delvewheel repair -w {dest_dir} -v {wheel}",
"pipx run abi3audit --strict --report {wheel}",
]
```
Expand All @@ -991,13 +998,12 @@ Platform-specific environment variables are also available:<br/>
!!! tab examples "Environment variables"

```yaml
# Use delvewheel on windows
CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel"
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}"

# Don't repair macOS wheels
CIBW_REPAIR_WHEEL_COMMAND_MACOS: ""

# Don't repair Windows wheels
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: ""

# Pass the `--lib-sdir .` flag to auditwheel on Linux
CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"

Expand All @@ -1014,7 +1020,7 @@ Platform-specific environment variables are also available:<br/>
delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} &&
pipx run abi3audit --strict --report {wheel}
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >
copy {wheel} {dest_dir} &&
delvewheel repair -w {dest_dir} -v {wheel} &&
pipx run abi3audit --strict --report {wheel}
```

Expand Down
17 changes: 17 additions & 0 deletions test/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@
basic_project = test_projects.new_c_project()


def test_delvewheel_default_on_windows(tmp_path: Path) -> None:
Comment thread
agriyakhetarpal marked this conversation as resolved.
Outdated
if utils.get_platform() != "windows":
pytest.skip("This test is only relevant to Windows")

skip_if_no_msvc()

project_dir = tmp_path / "project"
basic_project.generate(project_dir)

actual_wheels = utils.cibuildwheel_run(project_dir, single_python=True)

assert len(actual_wheels) == 1
assert "none-any" not in actual_wheels[0]
tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
assert actual_wheels[0].startswith(f"spam-0.1.0-{tag}-{tag}-win_")


Comment thread
agriyakhetarpal marked this conversation as resolved.
Outdated
def skip_if_no_msvc(arm64: bool = False) -> None:
programfiles = os.getenv("PROGRAMFILES(X86)", "") or os.getenv("PROGRAMFILES", "")
if not programfiles:
Expand Down
4 changes: 3 additions & 1 deletion unit_test/main_tests/main_options_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ def get_default_repair_command(platform: str) -> str:
return "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
elif platform in {"windows", "pyodide"}:
elif platform == "windows":
return "delvewheel repair -w {dest_dir} -v {wheel}"
elif platform == "pyodide":
return ""
else:
msg = f"Unknown platform: {platform!r}"
Expand Down
Loading