Skip to content

Commit

Permalink
feat: add option to opt-in free-threaded builds
Browse files Browse the repository at this point in the history
  • Loading branch information
mayeut authored and henryiii committed May 20, 2024
1 parent 345467c commit 3992d57
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 3 deletions.
5 changes: 5 additions & 0 deletions bin/generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@
description: Set environment variables on the host to pass-through to the container
during the build.
type: string_array
free-threaded-support:
type: boolean
default: false
description: The project supports free-threaded builds of Python (PEP703)
manylinux-aarch64-image:
type: string
description: Specify alternative manylinux / musllinux container images
Expand Down Expand Up @@ -248,6 +252,7 @@
del non_global_options["build"]
del non_global_options["skip"]
del non_global_options["test-skip"]
del non_global_options["free-threaded-support"]

overrides["items"]["properties"]["select"]["oneOf"] = string_array
overrides["items"]["properties"] |= non_global_options.copy()
Expand Down
12 changes: 9 additions & 3 deletions cibuildwheel/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def architectures(self) -> set[Architecture]:
return self.globals.architectures


Setting = Union[Mapping[str, str], Sequence[str], str, int]
Setting = Union[Mapping[str, str], Sequence[str], str, int, bool]


@dataclasses.dataclass(frozen=True)
Expand Down Expand Up @@ -196,7 +196,7 @@ def _resolve_cascade(
if value is None:
continue

if ignore_empty and not value:
if ignore_empty and not value and value is not False:
continue

value_string = _stringify_setting(value, list_sep, table_format)
Expand Down Expand Up @@ -258,7 +258,7 @@ def _stringify_setting(
raise ConfigOptionError(msg)
return list_sep.join(setting)

if isinstance(setting, int):
if isinstance(setting, (bool, int)):
return str(setting)

return setting
Expand Down Expand Up @@ -516,6 +516,10 @@ def globals(self) -> GlobalOptions:
skip_config = self.reader.get("skip", env_plat=False, list_sep=" ")
test_skip = self.reader.get("test-skip", env_plat=False, list_sep=" ")

free_threaded_support = strtobool(
self.reader.get("free-threaded-support", env_plat=False, ignore_empty=True)
)

prerelease_pythons = args.prerelease_pythons or strtobool(
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
)
Expand All @@ -536,12 +540,14 @@ def globals(self) -> GlobalOptions:
skip_config = ""
architectures = Architecture.all_archs(self.platform)
prerelease_pythons = True
free_threaded_support = True

build_selector = BuildSelector(
build_config=build_config,
skip_config=skip_config,
requires_python=requires_python,
prerelease_pythons=prerelease_pythons,
free_threaded_support=free_threaded_support,
)
test_selector = TestSelector(skip_config=test_skip)

Expand Down
6 changes: 6 additions & 0 deletions cibuildwheel/resources/cibuildwheel.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@
],
"title": "CIBW_ENVIRONMENT_PASS"
},
"free-threaded-support": {
"type": "boolean",
"default": false,
"description": "The project supports free-threaded builds of Python (PEP703)",
"title": "CIBW_FREE_THREADED_SUPPORT"
},
"manylinux-aarch64-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
Expand Down
1 change: 1 addition & 0 deletions cibuildwheel/resources/defaults.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
build = "*"
skip = ""
test-skip = ""
free-threaded-support = false

archs = ["auto"]
build-frontend = "default"
Expand Down
7 changes: 7 additions & 0 deletions cibuildwheel/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ class BuildSelector:
PRERELEASE_SKIP: ClassVar[str] = "cp313-* cp313t-*"
prerelease_pythons: bool = False

free_threaded_support: bool = False

def __call__(self, build_id: str) -> bool:
# Filter build selectors by python_requires if set
if self.requires_python is not None:
Expand All @@ -257,6 +259,10 @@ def __call__(self, build_id: str) -> bool:
if not self.prerelease_pythons and selector_matches(self.PRERELEASE_SKIP, build_id):
return False

# filter out free threaded pythons if self.free_threaded_support is False
if not self.free_threaded_support and selector_matches("*t-*", build_id):
return False

should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id)

Expand All @@ -268,6 +274,7 @@ def options_summary(self) -> Any:
"skip_config": self.skip_config,
"requires_python": str(self.requires_python),
"prerelease_pythons": self.prerelease_pythons,
"free_threaded_support": self.free_threaded_support,
}


Expand Down
5 changes: 5 additions & 0 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def cibuildwheel_get_build_identifiers(project_path, env=None, *, prerelease_pyt
cmd = [sys.executable, "-m", "cibuildwheel", "--print-build-identifiers", str(project_path)]
if prerelease_pythons:
cmd.append("--prerelease-pythons")
if env is None:
env = os.environ.copy()
env.setdefault("CIBW_FREE_THREADED_SUPPORT", "1")

cmd_output = subprocess.run(
cmd,
Expand Down Expand Up @@ -94,6 +97,8 @@ def cibuildwheel_run(

_update_pip_cache_dir(env)

env.setdefault("CIBW_FREE_THREADED_SUPPORT", "1")

with TemporaryDirectory() as tmp_output_dir:
subprocess.run(
[
Expand Down
9 changes: 9 additions & 0 deletions unit_test/build_selector_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def test_build_filter_pre():
assert build_selector("cp313-manylinux_x86_64")
assert build_selector("cp37-win_amd64")
assert build_selector("cp313-win_amd64")
assert not build_selector("cp313t-manylinux_x86_64")


def test_skip():
Expand Down Expand Up @@ -144,6 +145,14 @@ def test_build_limited_python_patch():
assert build_selector("cp37-manylinux_x86_64")


def test_build_free_threaded_python():
build_selector = BuildSelector(
build_config="*", skip_config="", prerelease_pythons=True, free_threaded_support=True
)

assert build_selector("cp313t-manylinux_x86_64")


def test_testing_selector():
# local import to avoid pytest trying to collect this as a test class!
from cibuildwheel.util import TestSelector
Expand Down
33 changes: 33 additions & 0 deletions unit_test/options_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,36 @@ def test_override_inherit_environment_with_references(tmp_path: Path):
) == {
"PATH": "/opt/local/bin:/opt/bin:/usr/bin:/bin",
}


@pytest.mark.parametrize(
("toml_assignment", "env", "expected_result"),
[
("", {}, False),
("free-threaded-support = true", {}, True),
("free-threaded-support = false", {}, False),
("", {"CIBW_FREE_THREADED_SUPPORT": "0"}, False),
("", {"CIBW_FREE_THREADED_SUPPORT": "1"}, True),
("free-threaded-support = false", {"CIBW_FREE_THREADED_SUPPORT": "1"}, True),
("free-threaded-support = true", {"CIBW_FREE_THREADED_SUPPORT": "0"}, False),
("free-threaded-support = true", {"CIBW_FREE_THREADED_SUPPORT": ""}, True),
("free-threaded-support = false", {"CIBW_FREE_THREADED_SUPPORT": ""}, False),
],
)
def test_free_threaded_support(
tmp_path: Path, toml_assignment: str, env: dict[str, str], expected_result: bool
):
args = CommandLineArguments.defaults()
args.package_dir = tmp_path

pyproject_toml: Path = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
textwrap.dedent(
f"""\
[tool.cibuildwheel]
{toml_assignment}
"""
)
)
options = Options(platform="linux", command_line_arguments=args, env=env)
assert options.globals.build_selector.free_threaded_support is expected_result

0 comments on commit 3992d57

Please sign in to comment.