diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index 29665b2ee..07d768b2e 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -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, ) @@ -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 diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index 2c5a63897..8d24e818b 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -20,6 +22,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index cd2a64268..9f48e79b5 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -18,6 +20,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/constraints-python312.txt b/cibuildwheel/resources/constraints-python312.txt index cd2a64268..9f48e79b5 100644 --- a/cibuildwheel/resources/constraints-python312.txt +++ b/cibuildwheel/resources/constraints-python312.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -18,6 +20,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/constraints-python313.txt b/cibuildwheel/resources/constraints-python313.txt index cd2a64268..9f48e79b5 100644 --- a/cibuildwheel/resources/constraints-python313.txt +++ b/cibuildwheel/resources/constraints-python313.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -18,6 +20,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/constraints-python314.txt b/cibuildwheel/resources/constraints-python314.txt index cd2a64268..9f48e79b5 100644 --- a/cibuildwheel/resources/constraints-python314.txt +++ b/cibuildwheel/resources/constraints-python314.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -18,6 +20,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index e53d14813..ad0395a0b 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -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 @@ -20,6 +22,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==25.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.3.6 diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index c98a2d2a4..8bf098fa7 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -6,6 +6,8 @@ build==1.4.4 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.19.1 @@ -20,6 +22,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.4.0 diff --git a/cibuildwheel/resources/constraints.in b/cibuildwheel/resources/constraints.in index 50bfabb6e..8f40eb610 100644 --- a/cibuildwheel/resources/constraints.in +++ b/cibuildwheel/resources/constraints.in @@ -1,4 +1,5 @@ pip build delocate +delvewheel virtualenv diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index cd2a64268..9f48e79b5 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -6,6 +6,8 @@ build==1.5.0 # via -r cibuildwheel/resources/constraints.in delocate==0.13.0 # via -r cibuildwheel/resources/constraints.in +delvewheel==1.12.1 + # via -r cibuildwheel/resources/constraints.in distlib==0.4.0 # via virtualenv filelock==3.29.0 @@ -18,6 +20,8 @@ packaging==26.2 # via # build # delocate +pefile==2024.8.26 + # via delvewheel pip==26.0.1 # via -r cibuildwheel/resources/constraints.in platformdirs==4.9.6 diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 78895bf9a..2807a1a54 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -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] diff --git a/docs/options.md b/docs/options.md index 7e916cad1..ddaaf0641 100644 --- a/docs/options.md +++ b/docs/options.md @@ -922,6 +922,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. @@ -946,29 +947,35 @@ The command is run in a shell, so you can run multiple commands like `cmd1 && cm Platform-specific environment variables are also available:
`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 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. - **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}" @@ -993,7 +1000,7 @@ Platform-specific environment variables are also available:
] [tool.cibuildwheel.windows] repair-wheel-command = [ - "copy {wheel} {dest_dir}", + "delvewheel repair -w {dest_dir} -v {wheel}", "pipx run abi3audit --strict --report {wheel}", ] ``` @@ -1004,13 +1011,12 @@ Platform-specific environment variables are also available:
!!! 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}" @@ -1027,7 +1033,7 @@ Platform-specific environment variables are also available:
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} ``` diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index 8e3794d6b..a6d20c0ec 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -288,14 +288,16 @@ 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}" raise ValueError(msg) -@pytest.mark.parametrize("repair_command", [None, "repair", "repair -w {dest_dir} {wheel}"]) +@pytest.mark.parametrize("repair_command", [None, "", "repair", "repair -w {dest_dir} {wheel}"]) @pytest.mark.parametrize("platform_specific", [False, True]) def test_repair_command( repair_command: str | None, @@ -315,7 +317,9 @@ def test_repair_command( build_options = intercepted_build_args.args[0].build_options(identifier=None) - expected_repair = repair_command or get_default_repair_command(platform) + expected_repair = ( + get_default_repair_command(platform) if repair_command is None else repair_command + ) assert build_options.repair_command == expected_repair