From e3ff027131daedebf01cd8227c5872d1d83f7c0f Mon Sep 17 00:00:00 2001 From: mayeut Date: Mon, 18 Apr 2022 14:24:53 +0200 Subject: [PATCH 1/3] feature: add support for ABI3 wheels --- cibuildwheel/linux.py | 142 ++++++++++++++++++++---------------- cibuildwheel/macos.py | 153 +++++++++++++++++++++------------------ cibuildwheel/util.py | 41 ++++++++++- cibuildwheel/windows.py | 153 +++++++++++++++++++++------------------ setup.cfg | 2 +- test/test_limited_api.py | 50 +++++++++++++ test/test_projects/c.py | 3 + 7 files changed, 341 insertions(+), 203 deletions(-) create mode 100644 test/test_limited_api.py diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 8478bf092..7be028a1c 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -12,6 +12,7 @@ from .util import ( BuildSelector, NonPlatformWheelError, + find_compatible_abi3_wheel, get_build_verbosity_extra_flags, prepare_command, read_python_configs, @@ -132,6 +133,8 @@ def build_on_docker( ) docker.call(["sh", "-c", before_all_prepared], env=env) + built_wheels: List[PurePath] = [] + for config in platform_configs: log.build_start(config.identifier) build_options = options.build_options(config.identifier) @@ -174,74 +177,83 @@ def build_on_docker( ) sys.exit(1) - if build_options.before_build: - log.step("Running before_build...") - before_build_prepared = prepare_command( - build_options.before_build, - project=container_project_path, - package=container_package_dir, - ) - docker.call(["sh", "-c", before_build_prepared], env=env) - - log.step("Building wheel...") - - temp_dir = PurePath("/tmp/cibuildwheel") - built_wheel_dir = temp_dir / "built_wheel" - docker.call(["rm", "-rf", built_wheel_dir]) - docker.call(["mkdir", "-p", built_wheel_dir]) - - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - - if build_options.build_frontend == "pip": - docker.call( - [ - "python", - "-m", - "pip", - "wheel", - container_package_dir, - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *verbosity_flags, - ], - env=env, - ) - elif build_options.build_frontend == "build": - config_setting = " ".join(verbosity_flags) - docker.call( - [ - "python", - "-m", - "build", - container_package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - f"--config-setting={config_setting}", - ], - env=env, + abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier) + if abi3_wheel: + log.step_end() + print( + f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) + repaired_wheels = [abi3_wheel] else: - assert_never(build_options.build_frontend) - built_wheel = docker.glob(built_wheel_dir, "*.whl")[0] + if build_options.before_build: + log.step("Running before_build...") + before_build_prepared = prepare_command( + build_options.before_build, + project=container_project_path, + package=container_package_dir, + ) + docker.call(["sh", "-c", before_build_prepared], env=env) + + log.step("Building wheel...") + + temp_dir = PurePath("/tmp/cibuildwheel") + built_wheel_dir = temp_dir / "built_wheel" + docker.call(["rm", "-rf", built_wheel_dir]) + docker.call(["mkdir", "-p", built_wheel_dir]) + + verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) + + if build_options.build_frontend == "pip": + docker.call( + [ + "python", + "-m", + "pip", + "wheel", + container_package_dir, + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *verbosity_flags, + ], + env=env, + ) + elif build_options.build_frontend == "build": + config_setting = " ".join(verbosity_flags) + docker.call( + [ + "python", + "-m", + "build", + container_package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + f"--config-setting={config_setting}", + ], + env=env, + ) + else: + assert_never(build_options.build_frontend) - repaired_wheel_dir = temp_dir / "repaired_wheel" - docker.call(["rm", "-rf", repaired_wheel_dir]) - docker.call(["mkdir", "-p", repaired_wheel_dir]) + built_wheel = docker.glob(built_wheel_dir, "*.whl")[0] - if built_wheel.name.endswith("none-any.whl"): - raise NonPlatformWheelError() + repaired_wheel_dir = temp_dir / "repaired_wheel" + docker.call(["rm", "-rf", repaired_wheel_dir]) + docker.call(["mkdir", "-p", repaired_wheel_dir]) - if 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 - ) - docker.call(["sh", "-c", repair_command_prepared], env=env) - else: - docker.call(["mv", built_wheel, repaired_wheel_dir]) + if built_wheel.name.endswith("none-any.whl"): + raise NonPlatformWheelError() - repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl") + if 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 + ) + docker.call(["sh", "-c", repair_command_prepared], env=env) + else: + docker.call(["mv", built_wheel, repaired_wheel_dir]) + + repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl") if build_options.test_command and build_options.test_selector(config.identifier): log.step("Testing wheel...") @@ -292,8 +304,12 @@ def build_on_docker( docker.call(["rm", "-rf", venv_dir]) # move repaired wheels to output - docker.call(["mkdir", "-p", container_output_dir]) - docker.call(["mv", *repaired_wheels, container_output_dir]) + if abi3_wheel is None: + docker.call(["mkdir", "-p", container_output_dir]) + docker.call(["mv", *repaired_wheels, container_output_dir]) + built_wheels.extend( + container_output_dir / repaired_wheel.name for repaired_wheel in repaired_wheels + ) log.build_end() diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index d27703152..aed483ad4 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -23,6 +23,7 @@ call, detect_ci_provider, download, + find_compatible_abi3_wheel, get_build_verbosity_extra_flags, get_pip_version, install_certifi_script, @@ -291,6 +292,8 @@ def build(options: Options, tmp_path: Path) -> None: ) shell(before_all_prepared, env=env) + built_wheels: List[Path] = [] + for config in python_configurations: build_options = options.build_options(config.identifier) log.build_start(config.identifier) @@ -318,84 +321,94 @@ def build(options: Options, tmp_path: Path) -> None: build_options.build_frontend, ) - if build_options.before_build: - log.step("Running before_build...") - before_build_prepared = prepare_command( - build_options.before_build, project=".", package=build_options.package_dir - ) - shell(before_build_prepared, env=env) - - log.step("Building wheel...") - built_wheel_dir.mkdir() - - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - - if build_options.build_frontend == "pip": - # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org - # see https://github.com/pypa/cibuildwheel/pull/369 - call( - "python", - "-m", - "pip", - "wheel", - build_options.package_dir.resolve(), - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *verbosity_flags, - env=env, - ) - elif build_options.build_frontend == "build": - config_setting = " ".join(verbosity_flags) - build_env = env.copy() - if build_options.dependency_constraints: - constraint_path = build_options.dependency_constraints.get_for_python_version( - config.version - ) - build_env["PIP_CONSTRAINT"] = constraint_path.as_uri() - build_env["VIRTUALENV_PIP"] = get_pip_version(env) - call( - "python", - "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - f"--config-setting={config_setting}", - env=build_env, + abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier) + if abi3_wheel: + log.step_end() + print( + f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) + repaired_wheel = abi3_wheel else: - assert_never(build_options.build_frontend) + if build_options.before_build: + log.step("Running before_build...") + before_build_prepared = prepare_command( + build_options.before_build, project=".", package=build_options.package_dir + ) + shell(before_build_prepared, env=env) + + log.step("Building wheel...") + built_wheel_dir.mkdir() + + verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) + + if build_options.build_frontend == "pip": + # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org + # see https://github.com/pypa/cibuildwheel/pull/369 + call( + "python", + "-m", + "pip", + "wheel", + build_options.package_dir.resolve(), + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *verbosity_flags, + env=env, + ) + elif build_options.build_frontend == "build": + config_setting = " ".join(verbosity_flags) + build_env = env.copy() + if build_options.dependency_constraints: + constraint_path = ( + build_options.dependency_constraints.get_for_python_version( + config.version + ) + ) + build_env["PIP_CONSTRAINT"] = constraint_path.as_uri() + build_env["VIRTUALENV_PIP"] = get_pip_version(env) + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + f"--config-setting={config_setting}", + env=build_env, + ) + else: + assert_never(build_options.build_frontend) - built_wheel = next(built_wheel_dir.glob("*.whl")) + built_wheel = next(built_wheel_dir.glob("*.whl")) - repaired_wheel_dir.mkdir() + repaired_wheel_dir.mkdir() - if built_wheel.name.endswith("none-any.whl"): - raise NonPlatformWheelError() + if built_wheel.name.endswith("none-any.whl"): + raise NonPlatformWheelError() - if build_options.repair_command: - log.step("Repairing wheel...") + if build_options.repair_command: + log.step("Repairing wheel...") - if config_is_universal2: - delocate_archs = "x86_64,arm64" - elif config_is_arm64: - delocate_archs = "arm64" + if config_is_universal2: + delocate_archs = "x86_64,arm64" + elif config_is_arm64: + delocate_archs = "arm64" + else: + delocate_archs = "x86_64" + + repair_command_prepared = prepare_command( + build_options.repair_command, + wheel=built_wheel, + dest_dir=repaired_wheel_dir, + delocate_archs=delocate_archs, + ) + shell(repair_command_prepared, env=env) else: - delocate_archs = "x86_64" - - repair_command_prepared = prepare_command( - build_options.repair_command, - wheel=built_wheel, - dest_dir=repaired_wheel_dir, - delocate_archs=delocate_archs, - ) - shell(repair_command_prepared, env=env) - else: - shutil.move(str(built_wheel), repaired_wheel_dir) + shutil.move(str(built_wheel), repaired_wheel_dir) - repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) - log.step_end() + log.step_end() if build_options.test_command and build_options.test_selector(config.identifier): machine_arch = platform.machine() @@ -521,7 +534,9 @@ def build(options: Options, tmp_path: Path) -> None: ) # we're all done here; move it to output (overwrite existing) - shutil.move(str(repaired_wheel), build_options.output_dir) + if abi3_wheel is None: + shutil.move(str(repaired_wheel), build_options.output_dir) + built_wheels.append(build_options.output_dir / repaired_wheel.name) # clean up shutil.rmtree(identifier_tmp_dir) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 5015d3487..1ebffee3e 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -13,7 +13,7 @@ import urllib.request from enum import Enum from functools import lru_cache -from pathlib import Path +from pathlib import Path, PurePath from time import sleep from typing import ( Any, @@ -26,6 +26,7 @@ Optional, Sequence, TextIO, + TypeVar, cast, overload, ) @@ -41,6 +42,7 @@ from filelock import FileLock from packaging.requirements import InvalidRequirement, Requirement from packaging.specifiers import SpecifierSet +from packaging.utils import parse_wheel_filename from packaging.version import Version from platformdirs import user_cache_path @@ -51,6 +53,7 @@ "MANYLINUX_ARCHS", "call", "shell", + "find_compatible_abi3_wheel", "format_safe", "prepare_command", "get_build_verbosity_extra_flags", @@ -566,6 +569,42 @@ def virtualenv( return env +T = TypeVar("T", bound=PurePath) + + +def find_compatible_abi3_wheel(wheels: Sequence[T], identifier: str) -> Optional[T]: + """ + Finds an ABI3 wheel in `wheels` compatible with the Python interpreter + specified by `identifier`. + """ + + interpreter, platform = identifier.split("-") + if not interpreter.startswith("cp3"): + return None + for wheel in wheels: + _, _, _, tags = parse_wheel_filename(wheel.name) + for tag in tags: + if tag.abi != "abi3": + continue + if not tag.interpreter.startswith("cp3"): + continue + if int(tag.interpreter[3:]) > int(interpreter[3:]): + continue + if platform.startswith(("manylinux", "musllinux", "macosx")): + # Linux, macOS + os_, arch = platform.split("_", 1) + if not tag.platform.startswith(os_): + continue + if not tag.platform.endswith("_" + arch): + continue + else: + # Windows + if not tag.platform == platform: + continue + return wheel + return None + + if sys.version_info >= (3, 8): from functools import cached_property else: diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 13b5c0c3d..85f76a04b 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -22,6 +22,7 @@ NonPlatformWheelError, call, download, + find_compatible_abi3_wheel, get_build_verbosity_extra_flags, get_pip_version, prepare_command, @@ -249,6 +250,8 @@ def build(options: Options, tmp_path: Path) -> None: ) shell(before_all_prepared, env=env) + built_wheels: List[Path] = [] + for config in python_configurations: build_options = options.build_options(config.identifier) log.build_start(config.identifier) @@ -274,83 +277,93 @@ def build(options: Options, tmp_path: Path) -> None: build_options.build_frontend, ) - # run the before_build command - if build_options.before_build: - log.step("Running before_build...") - before_build_prepared = prepare_command( - build_options.before_build, project=".", package=options.globals.package_dir + abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier) + if abi3_wheel: + log.step_end() + print( + f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) - shell(before_build_prepared, env=env) + repaired_wheel = abi3_wheel + else: + # run the before_build command + if build_options.before_build: + log.step("Running before_build...") + before_build_prepared = prepare_command( + build_options.before_build, project=".", package=options.globals.package_dir + ) + shell(before_build_prepared, env=env) - log.step("Building wheel...") - built_wheel_dir.mkdir() + log.step("Building wheel...") + built_wheel_dir.mkdir() - verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) + verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) - if build_options.build_frontend == "pip": - # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org - # see https://github.com/pypa/cibuildwheel/pull/369 - call( - "python", - "-m", - "pip", - "wheel", - options.globals.package_dir.resolve(), - f"--wheel-dir={built_wheel_dir}", - "--no-deps", - *get_build_verbosity_extra_flags(build_options.build_verbosity), - env=env, - ) - elif build_options.build_frontend == "build": - config_setting = " ".join(verbosity_flags) - build_env = env.copy() - if build_options.dependency_constraints: - constraints_path = build_options.dependency_constraints.get_for_python_version( - config.version - ) - # Bug in pip <= 21.1.3 - we can't have a space in the - # constraints file, and pip doesn't support drive letters - # in uhi. After probably pip 21.2, we can use uri. For - # now, use a temporary file. - if " " in str(constraints_path): - assert " " not in str(identifier_tmp_dir) - tmp_file = identifier_tmp_dir / "constraints.txt" - tmp_file.write_bytes(constraints_path.read_bytes()) - constraints_path = tmp_file - - build_env["PIP_CONSTRAINT"] = str(constraints_path) - build_env["VIRTUALENV_PIP"] = get_pip_version(env) + if build_options.build_frontend == "pip": + # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org + # see https://github.com/pypa/cibuildwheel/pull/369 call( "python", "-m", - "build", - build_options.package_dir, - "--wheel", - f"--outdir={built_wheel_dir}", - f"--config-setting={config_setting}", - env=build_env, + "pip", + "wheel", + options.globals.package_dir.resolve(), + f"--wheel-dir={built_wheel_dir}", + "--no-deps", + *get_build_verbosity_extra_flags(build_options.build_verbosity), + env=env, ) - else: - assert_never(build_options.build_frontend) - - built_wheel = next(built_wheel_dir.glob("*.whl")) - - # repair the wheel - repaired_wheel_dir.mkdir() - - if built_wheel.name.endswith("none-any.whl"): - raise NonPlatformWheelError() - - if 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 - ) - shell(repair_command_prepared, env=env) - else: - shutil.move(str(built_wheel), repaired_wheel_dir) + elif build_options.build_frontend == "build": + config_setting = " ".join(verbosity_flags) + build_env = env.copy() + if build_options.dependency_constraints: + constraints_path = ( + build_options.dependency_constraints.get_for_python_version( + config.version + ) + ) + # Bug in pip <= 21.1.3 - we can't have a space in the + # constraints file, and pip doesn't support drive letters + # in uhi. After probably pip 21.2, we can use uri. For + # now, use a temporary file. + if " " in str(constraints_path): + assert " " not in str(identifier_tmp_dir) + tmp_file = identifier_tmp_dir / "constraints.txt" + tmp_file.write_bytes(constraints_path.read_bytes()) + constraints_path = tmp_file + + build_env["PIP_CONSTRAINT"] = str(constraints_path) + build_env["VIRTUALENV_PIP"] = get_pip_version(env) + call( + "python", + "-m", + "build", + build_options.package_dir, + "--wheel", + f"--outdir={built_wheel_dir}", + f"--config-setting={config_setting}", + env=build_env, + ) + else: + assert_never(build_options.build_frontend) + + built_wheel = next(built_wheel_dir.glob("*.whl")) + + # repair the wheel + repaired_wheel_dir.mkdir() + + if built_wheel.name.endswith("none-any.whl"): + raise NonPlatformWheelError() + + if 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 + ) + shell(repair_command_prepared, env=env) + else: + shutil.move(str(built_wheel), repaired_wheel_dir) - repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) if build_options.test_command and options.globals.test_selector(config.identifier): log.step("Testing wheel...") @@ -405,7 +418,9 @@ def build(options: Options, tmp_path: Path) -> None: shell(test_command_prepared, cwd="c:\\", env=virtualenv_env) # we're all done here; move it to output (remove if already exists) - shutil.move(str(repaired_wheel), build_options.output_dir) + if abi3_wheel is None: + shutil.move(str(repaired_wheel), build_options.output_dir) + built_wheels.append(build_options.output_dir / repaired_wheel.name) # clean up # (we ignore errors because occasionally Windows fails to unlink a file and we diff --git a/setup.cfg b/setup.cfg index a6773844b..1fa219fc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ install_requires = bracex certifi filelock - packaging + packaging>=20.9 platformdirs dataclasses;python_version < '3.7' tomli;python_version < '3.11' diff --git a/test/test_limited_api.py b/test/test_limited_api.py new file mode 100644 index 000000000..34978c92d --- /dev/null +++ b/test/test_limited_api.py @@ -0,0 +1,50 @@ +import textwrap + +from . import test_projects, utils + +limited_api_project = test_projects.new_c_project( + setup_py_add=textwrap.dedent( + r""" + cmdclass = {} + extension_kwargs = {} + if sys.version_info[:2] >= (3, 8): + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel + + class bdist_wheel_abi3(_bdist_wheel): + def finalize_options(self): + _bdist_wheel.finalize_options(self) + self.root_is_pure = False + + def get_tag(self): + python, abi, plat = _bdist_wheel.get_tag(self) + return python, "abi3", plat + + cmdclass["bdist_wheel"] = bdist_wheel_abi3 + extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x03080000")] + extension_kwargs["py_limited_api"] = True + """ + ), + setup_py_extension_args_add="**extension_kwargs", + setup_py_setup_args_add="cmdclass=cmdclass", +) + + +def test(tmp_path): + project_dir = tmp_path / "project" + limited_api_project.generate(project_dir) + + # build the wheels + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_SKIP": "pp* ", # PyPy does not have a Py_LIMITED_API equivalent + }, + ) + + # check that the expected wheels are produced + expected_wheels = [ + w.replace("cp38-cp38", "cp38-abi3") + for w in utils.expected_wheels("spam", "0.1.0") + if "-pp" not in w and "-cp39" not in w and "-cp310" not in w + ] + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/test_projects/c.py b/test/test_projects/c.py index 41b729586..d4c37c694 100644 --- a/test/test_projects/c.py +++ b/test/test_projects/c.py @@ -54,6 +54,7 @@ 'spam', sources=['spam.c'], libraries=libraries, + {{ setup_py_extension_args_add | indent(8) }} )], {{ setup_py_setup_args_add | indent(4) }} ) @@ -73,6 +74,7 @@ def new_c_project( spam_c_top_level_add="", spam_c_function_add="", setup_py_add="", + setup_py_extension_args_add="", setup_py_setup_args_add="", setup_cfg_add="", ): @@ -91,6 +93,7 @@ def new_c_project( "spam_c_top_level_add": spam_c_top_level_add, "spam_c_function_add": spam_c_function_add, "setup_py_add": setup_py_add, + "setup_py_extension_args_add": setup_py_extension_args_add, "setup_py_setup_args_add": setup_py_setup_args_add, "setup_cfg_add": setup_cfg_add, } From 46702f581da2dde13ea3d2a49dfa55df226db9f8 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 29 Apr 2022 13:32:03 +0100 Subject: [PATCH 2/3] Space out the log statements a little --- cibuildwheel/linux.py | 2 +- cibuildwheel/macos.py | 2 +- cibuildwheel/windows.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 7be028a1c..036eb36f0 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -181,7 +181,7 @@ def build_on_docker( if abi3_wheel: log.step_end() print( - f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." + f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) repaired_wheels = [abi3_wheel] else: diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index aed483ad4..7100cd05a 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -325,7 +325,7 @@ def build(options: Options, tmp_path: Path) -> None: if abi3_wheel: log.step_end() print( - f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." + f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) repaired_wheel = abi3_wheel else: diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 85f76a04b..1e95c225e 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -281,7 +281,7 @@ def build(options: Options, tmp_path: Path) -> None: if abi3_wheel: log.step_end() print( - f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." + f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..." ) repaired_wheel = abi3_wheel else: From 75998410e05a2e00ff2d842ed2590830fc600b99 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 29 Apr 2022 14:11:35 +0100 Subject: [PATCH 3/3] Add tips+tricks entry for ABI3 building --- docs/faq.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 97e65feb7..2364be729 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -103,6 +103,12 @@ Linux), and the other architectures are emulated automatically. {% include "../examples/github-with-qemu.yml" %} ``` +### Building CPython ABI3 wheels (Limited API) {: #abi3} + +The CPython Limited API is a subset of the Python C Extension API that's declared to be forward-compatible, meaning you can compile wheels for one version of Python, and they'll be compatible with future versions. Wheels that use the Limited API are known as ABI3 wheels. + +To create a package that builds ABI3 wheels, you'll need to configure your build backend to compile libraries correctly create wheels with the right tags. [Check this repo](https://github.com/joerick/python-abi3-package-sample) for an example of how to do this with setuptools. + ### Building packages with optional C extensions `cibuildwheel` defines the environment variable `CIBUILDWHEEL` to the value `1` allowing projects for which the C extension is optional to make it mandatory when building wheels.