Skip to content

Commit

Permalink
Optionally build a pure wheel
Browse files Browse the repository at this point in the history
Add a new --pure-wheel command line option to also build pure Python
wheel. If not selected, build will fail with a wheel that does not
contain platform-specific code once built.
Also add a new CIBW_PURE_WHEEL environment variable for this option.



Reference: pypa#1021
Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
  • Loading branch information
pombredanne committed Feb 11, 2022
1 parent b52698d commit 97c2760
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 6 deletions.
9 changes: 9 additions & 0 deletions cibuildwheel/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def main() -> None:
help="Enable pre-release Python versions if available.",
)

parser.add_argument(
"--pure-wheel",
action="store_true",
help="""
Build pure Python wheel. If not selected, build will fail with a
wheel that does not contain platform-specific code.
""",
)

args = parser.parse_args(namespace=CommandLineArguments())

if args.platform != "auto":
Expand Down
5 changes: 3 additions & 2 deletions cibuildwheel/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,11 @@ def build_on_docker(
docker.call(["rm", "-rf", repaired_wheel_dir])
docker.call(["mkdir", "-p", repaired_wheel_dir])

if built_wheel.name.endswith("none-any.whl"):
is_pure_python = built_wheel.name.endswith("none-any.whl")
if not build_options.globals.pure_wheel and is_pure_python:
raise NonPlatformWheelError()

if build_options.repair_command:
if not is_pure_python and build_options.repair_command:
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
Expand Down
5 changes: 3 additions & 2 deletions cibuildwheel/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,11 @@ def build(options: Options, tmp_path: Path) -> None:

repaired_wheel_dir.mkdir()

if built_wheel.name.endswith("none-any.whl"):
is_pure_python = built_wheel.name.endswith("none-any.whl")
if not build_options.globals.pure_wheel and is_pure_python:
raise NonPlatformWheelError()

if build_options.repair_command:
if not is_pure_python and build_options.repair_command:
log.step("Repairing wheel...")

if config_is_universal2:
Expand Down
5 changes: 5 additions & 0 deletions cibuildwheel/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class CommandLineArguments:
print_build_identifiers: bool
allow_empty: bool
prerelease_pythons: bool
pure_wheel: bool


class GlobalOptions(NamedTuple):
Expand All @@ -55,6 +56,7 @@ class GlobalOptions(NamedTuple):
build_selector: BuildSelector
test_selector: TestSelector
architectures: Set[Architecture]
pure_wheel: bool


class BuildOptions(NamedTuple):
Expand Down Expand Up @@ -356,6 +358,8 @@ def package_requires_python_str(self) -> Optional[str]:
def globals(self) -> GlobalOptions:
args = self.command_line_arguments
package_dir = Path(args.package_dir)
pure_wheel = args.pure_wheel or strtobool(os.environ.get("CIBW_PURE_WHEEL", "0"))

output_dir = Path(
args.output_dir
if args.output_dir is not None
Expand Down Expand Up @@ -394,6 +398,7 @@ def globals(self) -> GlobalOptions:
build_selector=build_selector,
test_selector=test_selector,
architectures=architectures,
pure_wheel=pure_wheel,
)

def build_options(self, identifier: Optional[str]) -> BuildOptions:
Expand Down
5 changes: 3 additions & 2 deletions cibuildwheel/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,11 @@ def build(options: Options, tmp_path: Path) -> None:
# repair the wheel
repaired_wheel_dir.mkdir()

if built_wheel.name.endswith("none-any.whl"):
is_pure_python = built_wheel.name.endswith("none-any.whl")
if not build_options.globals.pure_wheel and is_pure_python:
raise NonPlatformWheelError()

if build_options.repair_command:
if not is_pure_python and build_options.repair_command:
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
Expand Down
48 changes: 48 additions & 0 deletions test/test_pure_wheel_built.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from test import test_projects

from . import utils

pure_python_project = test_projects.TestProject()
pure_python_project.files[
"setup.py"
] = """
from setuptools import Extension, setup
setup(
name="spam",
py_modules=['spam'],
version="0.1.0",
)
"""

pure_python_project.files[
"spam.py"
] = """
def a_function():
pass
"""


def test(tmp_path, capfd):
# this test checks that if a pure wheel is generated, the build should
# pass with the pure wheel option.
project_dir = tmp_path / "project"
pure_python_project.generate(project_dir)

env = {
"CIBW_PURE_WHEEL": "yes",
# this shouldn't depend on the version of python, so build only CPython 3.6
"CIBW_BUILD": "cp36-*",
}

actual_wheels = utils.cibuildwheel_run(project_dir, add_env=env)
print("produced wheels:", actual_wheels)

captured = capfd.readouterr()
print("out", captured.out)
print("err", captured.err)
assert "Build failed because a pure Python wheel was generated" not in captured.err

# check that the expected wheels are produced
expected_wheels = ["spam-0.1.0-py3-none-any.whl"]
assert set(actual_wheels) == set(expected_wheels)

0 comments on commit 97c2760

Please sign in to comment.