feat: stricter selector parsing, refactor to platforms module (#2291)

* feat: stricter selector parsing, refactor to `platforms` module

- Use a different method to build nothing
- Make the check aware of enable groups

* optimise unit tests - a specific platform module API for all configs

Unit test time: 26.2s -> 13.1s

* Remove unnecessary get_platform_module function
This commit is contained in:
Joe Rickerby
2025-03-24 16:58:47 +00:00
committed by GitHub
parent 4db404ca50
commit 141e702872
17 changed files with 283 additions and 183 deletions
+75 -43
View File
@@ -9,25 +9,21 @@ import textwrap
import time import time
import traceback import traceback
import typing import typing
from collections.abc import Generator, Iterable, Sequence, Set from collections.abc import Generator, Iterable, Sequence
from pathlib import Path from pathlib import Path
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any, Protocol, TextIO, assert_never from typing import Any, Literal, TextIO
import cibuildwheel import cibuildwheel
import cibuildwheel.ios
import cibuildwheel.linux
import cibuildwheel.macos
import cibuildwheel.pyodide
import cibuildwheel.util import cibuildwheel.util
import cibuildwheel.windows
from cibuildwheel import errors from cibuildwheel import errors
from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions
from cibuildwheel.logger import log from cibuildwheel.logger import log
from cibuildwheel.options import CommandLineArguments, Options, compute_options from cibuildwheel.options import CommandLineArguments, Options, compute_options
from cibuildwheel.selector import BuildSelector, EnableGroup from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
from cibuildwheel.typing import PLATFORMS, GenericPythonConfiguration, PlatformName from cibuildwheel.selector import BuildSelector, EnableGroup, selector_matches
from cibuildwheel.typing import PLATFORMS, PlatformName
from cibuildwheel.util.file import CIBW_CACHE_PATH from cibuildwheel.util.file import CIBW_CACHE_PATH
from cibuildwheel.util.helpers import strtobool from cibuildwheel.util.helpers import strtobool
@@ -286,30 +282,6 @@ def _compute_platform(args: CommandLineArguments) -> PlatformName:
return _compute_platform_auto() return _compute_platform_auto()
class PlatformModule(Protocol):
# note that as per PEP544, the self argument is ignored when the protocol
# is applied to a module
def get_python_configurations(
self, build_selector: BuildSelector, architectures: Set[Architecture]
) -> Sequence[GenericPythonConfiguration]: ...
def build(self, options: Options, tmp_path: Path) -> None: ...
def get_platform_module(platform: PlatformName) -> PlatformModule:
if platform == "linux":
return cibuildwheel.linux
if platform == "windows":
return cibuildwheel.windows
if platform == "macos":
return cibuildwheel.macos
if platform == "pyodide":
return cibuildwheel.pyodide
if platform == "ios":
return cibuildwheel.ios
assert_never(platform)
@contextlib.contextmanager @contextlib.contextmanager
def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]: def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]:
""" """
@@ -362,7 +334,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
msg = f"Could not find any of {{{names}}} at root of package" msg = f"Could not find any of {{{names}}} at root of package"
raise errors.ConfigurationError(msg) raise errors.ConfigurationError(msg)
platform_module = get_platform_module(platform) platform_module = ALL_PLATFORM_MODULES[platform]
identifiers = get_build_identifiers( identifiers = get_build_identifiers(
platform_module=platform_module, platform_module=platform_module,
build_selector=options.globals.build_selector, build_selector=options.globals.build_selector,
@@ -448,15 +420,6 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str])
print("Here we go!\n") print("Here we go!\n")
def get_build_identifiers(
platform_module: PlatformModule,
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[str]:
python_configurations = platform_module.get_python_configurations(build_selector, architectures)
return [config.identifier for config in python_configurations]
def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]: def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]:
warnings = [] warnings = []
@@ -482,6 +445,75 @@ def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str
) )
raise errors.ConfigurationError(msg) raise errors.ConfigurationError(msg)
build_selector = options.globals.build_selector
test_selector = options.globals.test_selector
all_valid_identifiers = [
config.identifier
for module in ALL_PLATFORM_MODULES.values()
for config in module.all_python_configurations()
]
enabled_selector = BuildSelector(
build_config="*", skip_config="", enable=options.globals.build_selector.enable
)
all_enabled_identifiers = [
identifier for identifier in all_valid_identifiers if enabled_selector(identifier)
]
warnings += check_for_invalid_selectors(
selector_name="build",
selector_value=build_selector.build_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
warnings += check_for_invalid_selectors(
selector_name="skip",
selector_value=build_selector.skip_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
warnings += check_for_invalid_selectors(
selector_name="test_skip",
selector_value=test_selector.skip_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
return warnings
def check_for_invalid_selectors(
*,
selector_name: Literal["build", "skip", "test_skip"],
selector_value: str,
all_valid_identifiers: Sequence[str],
all_enabled_identifiers: Sequence[str],
) -> list[str]:
warnings = []
for selector in selector_value.split():
if not any(selector_matches(selector, i) for i in all_enabled_identifiers):
msg = f"Invalid {selector_name} selector: {selector!r}. "
error_type: type = errors.ConfigurationError
if any(selector_matches(selector, i) for i in all_valid_identifiers):
msg += "This selector matches a group that wasn't enabled. Enable it using the `enable` option or remove this selector. "
if "p2" in selector or "p35" in selector:
msg += f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 1.x series or update `{selector_name}`. "
error_type = errors.DeprecationError
if "p36" in selector or "p37" in selector:
msg += f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 2.x series or update `{selector_name}`. "
error_type = errors.DeprecationError
if selector_name == "build":
raise error_type(msg)
msg += "This selector will have no effect. "
warnings.append(msg)
return warnings return warnings
+1 -24
View File
@@ -863,14 +863,6 @@ class Options:
) )
) )
def check_for_deprecated_options(self) -> None:
build_selector = self.globals.build_selector
test_selector = self.globals.test_selector
deprecated_selectors("CIBW_BUILD", build_selector.build_config, error=True)
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
@functools.cached_property @functools.cached_property
def defaults(self) -> Self: def defaults(self) -> Self:
return self.__class__( return self.__class__(
@@ -985,9 +977,7 @@ def compute_options(
command_line_arguments: CommandLineArguments, command_line_arguments: CommandLineArguments,
env: Mapping[str, str], env: Mapping[str, str],
) -> Options: ) -> Options:
options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env) return Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
options.check_for_deprecated_options()
return options
@functools.cache @functools.cache
@@ -1002,16 +992,3 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
all_pinned_images = configparser.ConfigParser() all_pinned_images = configparser.ConfigParser()
all_pinned_images.read(resources.PINNED_DOCKER_IMAGES) all_pinned_images.read(resources.PINNED_DOCKER_IMAGES)
return all_pinned_images return all_pinned_images
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
if "p2" in selector or "p35" in selector:
msg = f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 1.x series or update {name}"
if error:
raise errors.DeprecationError(msg)
log.warning(msg)
if "p36" in selector or "p37" in selector:
msg = f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 2.x series or update {name}"
if error:
raise errors.DeprecationError(msg)
log.warning(msg)
+41
View File
@@ -0,0 +1,41 @@
from __future__ import annotations
from collections.abc import Sequence
from pathlib import Path
from typing import Final, Protocol
from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options
from cibuildwheel.platforms import ios, linux, macos, pyodide, windows
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import GenericPythonConfiguration, PlatformName
class PlatformModule(Protocol):
# note that as per PEP544, the self argument is ignored when the protocol
# is applied to a module
def all_python_configurations(self) -> Sequence[GenericPythonConfiguration]: ...
def get_python_configurations(
self, build_selector: BuildSelector, architectures: set[Architecture]
) -> Sequence[GenericPythonConfiguration]: ...
def build(self, options: Options, tmp_path: Path) -> None: ...
ALL_PLATFORM_MODULES: Final[dict[PlatformName, PlatformModule]] = {
"linux": linux,
"windows": windows,
"macos": macos,
"pyodide": pyodide,
"ios": ios,
}
def get_build_identifiers(
platform_module: PlatformModule,
build_selector: BuildSelector,
architectures: set[Architecture],
) -> list[str]:
python_configurations = platform_module.get_python_configurations(build_selector, architectures)
return [config.identifier for config in python_configurations]
@@ -12,34 +12,34 @@ from typing import assert_never
from filelock import FileLock from filelock import FileLock
from . import errors from .. import errors
from .architecture import Architecture from ..architecture import Architecture
from .environment import ParsedEnvironment from ..environment import ParsedEnvironment
from .frontend import ( from ..frontend import (
BuildFrontendConfig, BuildFrontendConfig,
BuildFrontendName, BuildFrontendName,
get_build_frontend_extra_flags, get_build_frontend_extra_flags,
) )
from .logger import log from ..logger import log
from .macos import install_cpython as install_build_cpython from ..options import Options
from .options import Options from ..selector import BuildSelector
from .selector import BuildSelector from ..typing import PathOrStr
from .typing import PathOrStr from ..util import resources
from .util import resources from ..util.cmd import call, shell
from .util.cmd import call, shell from ..util.file import (
from .util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, copy_test_sources,
download, download,
move_file, move_file,
) )
from .util.helpers import prepare_command from ..util.helpers import prepare_command
from .util.packaging import ( from ..util.packaging import (
combine_constraints, combine_constraints,
find_compatible_wheel, find_compatible_wheel,
get_pip_version, get_pip_version,
) )
from .venv import virtualenv from ..venv import virtualenv
from .macos import install_cpython as install_build_cpython
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -72,10 +72,7 @@ class PythonConfiguration:
return "ios-arm64_x86_64-simulator" if self.is_simulator else "ios-arm64" return "ios-arm64_x86_64-simulator" if self.is_simulator else "ios-arm64"
def get_python_configurations( def all_python_configurations() -> list[PythonConfiguration]:
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
# iOS builds are always cross builds; we need to install a macOS Python as # iOS builds are always cross builds; we need to install a macOS Python as
# well. Rather than duplicate the location of the URL of macOS installers, # well. Rather than duplicate the location of the URL of macOS installers,
# load the macos configurations, determine the macOS configuration that # load the macos configurations, determine the macOS configuration that
@@ -97,7 +94,7 @@ def get_python_configurations(
# Load the platform configuration # Load the platform configuration
full_python_configs = resources.read_python_configs("ios") full_python_configs = resources.read_python_configs("ios")
# Build the configurations, annotating with macOS URL details. # Build the configurations, annotating with macOS URL details.
python_configurations = [ return [
PythonConfiguration( PythonConfiguration(
**item, **item,
build_url=build_url(item), build_url=build_url(item),
@@ -105,6 +102,13 @@ def get_python_configurations(
for item in full_python_configs for item in full_python_configs
] ]
def get_python_configurations(
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
python_configurations = all_python_configurations()
# Filter out configs that don't match any of the selected architectures # Filter out configs that don't match any of the selected architectures
python_configurations = [ python_configurations = [
c c
@@ -8,18 +8,18 @@ from dataclasses import dataclass
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from typing import assert_never from typing import assert_never
from . import errors from .. import errors
from .architecture import Architecture from ..architecture import Architecture
from .frontend import BuildFrontendConfig, get_build_frontend_extra_flags from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from .logger import log from ..logger import log
from .oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform from ..oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from .options import BuildOptions, Options from ..options import BuildOptions, Options
from .selector import BuildSelector from ..selector import BuildSelector
from .typing import PathOrStr from ..typing import PathOrStr
from .util import resources from ..util import resources
from .util.file import copy_test_sources from ..util.file import copy_test_sources
from .util.helpers import prepare_command, unwrap from ..util.helpers import prepare_command, unwrap
from .util.packaging import find_compatible_wheel from ..util.packaging import find_compatible_wheel
ARCHITECTURE_OCI_PLATFORM_MAP = { ARCHITECTURE_OCI_PLATFORM_MAP = {
Architecture.x86_64: OCIPlatform.AMD64, Architecture.x86_64: OCIPlatform.AMD64,
@@ -50,13 +50,16 @@ class BuildStep:
container_image: str container_image: str
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("linux")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], architectures: Set[Architecture],
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("linux") python_configurations = all_python_configurations()
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
# return all configurations whose arch is in our `architectures` set, # return all configurations whose arch is in our `architectures` set,
# and match the build/skip rules # and match the build/skip rules
@@ -15,26 +15,26 @@ from typing import Literal, assert_never
from filelock import FileLock from filelock import FileLock
from packaging.version import Version from packaging.version import Version
from . import errors from .. import errors
from .architecture import Architecture from ..architecture import Architecture
from .ci import detect_ci_provider from ..ci import detect_ci_provider
from .environment import ParsedEnvironment from ..environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from .logger import log from ..logger import log
from .options import Options from ..options import Options
from .selector import BuildSelector from ..selector import BuildSelector
from .typing import PathOrStr from ..typing import PathOrStr
from .util import resources from ..util import resources
from .util.cmd import call, shell from ..util.cmd import call, shell
from .util.file import ( from ..util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, copy_test_sources,
download, download,
move_file, move_file,
) )
from .util.helpers import prepare_command, unwrap from ..util.helpers import prepare_command, unwrap
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import find_uv, virtualenv from ..venv import find_uv, virtualenv
@functools.cache @functools.cache
@@ -84,12 +84,15 @@ class PythonConfiguration:
url: str url: str
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("macos")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, architectures: Set[Architecture] build_selector: BuildSelector, architectures: Set[Architecture]
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("macos") python_configurations = all_python_configurations()
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
# filter out configs that don't match any of the selected architectures # filter out configs that don't match any of the selected architectures
python_configurations = [ python_configurations = [
@@ -11,17 +11,17 @@ from typing import Final
from filelock import FileLock from filelock import FileLock
from . import errors from .. import errors
from .architecture import Architecture from ..architecture import Architecture
from .environment import ParsedEnvironment from ..environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, get_build_frontend_extra_flags from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from .logger import log from ..logger import log
from .options import Options from ..options import Options
from .selector import BuildSelector from ..selector import BuildSelector
from .typing import PathOrStr from ..typing import PathOrStr
from .util import resources from ..util import resources
from .util.cmd import call, shell from ..util.cmd import call, shell
from .util.file import ( from ..util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, copy_test_sources,
download, download,
@@ -29,9 +29,9 @@ from .util.file import (
extract_zip, extract_zip,
move_file, move_file,
) )
from .util.helpers import prepare_command from ..util.helpers import prepare_command
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import virtualenv from ..venv import virtualenv
IS_WIN: Final[bool] = sys.platform.startswith("win") IS_WIN: Final[bool] = sys.platform.startswith("win")
@@ -215,15 +215,16 @@ def setup_python(
return env return env
def all_python_configurations() -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("pyodide")
return [PythonConfiguration(**item) for item in full_python_configs]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], # noqa: ARG001 architectures: Set[Architecture], # noqa: ARG001
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("pyodide") return [c for c in all_python_configurations() if build_selector(c.identifier)]
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
return python_configurations
def build(options: Options, tmp_path: Path) -> None: def build(options: Options, tmp_path: Path) -> None:
@@ -11,20 +11,20 @@ from typing import assert_never
from filelock import FileLock from filelock import FileLock
from . import errors from .. import errors
from .architecture import Architecture from ..architecture import Architecture
from .environment import ParsedEnvironment from ..environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from .logger import log from ..logger import log
from .options import Options from ..options import Options
from .selector import BuildSelector from ..selector import BuildSelector
from .typing import PathOrStr from ..typing import PathOrStr
from .util import resources from ..util import resources
from .util.cmd import call, shell from ..util.cmd import call, shell
from .util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file
from .util.helpers import prepare_command, unwrap from ..util.helpers import prepare_command, unwrap
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import find_uv, virtualenv from ..venv import find_uv, virtualenv
def get_nuget_args( def get_nuget_args(
@@ -59,13 +59,16 @@ class PythonConfiguration:
url: str | None = None url: str | None = None
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("windows")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], architectures: Set[Architecture],
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("windows") python_configurations = all_python_configurations()
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
map_arch = {"32": Architecture.x86, "64": Architecture.AMD64, "ARM64": Architecture.ARM64} map_arch = {"32": Architecture.x86, "64": Architecture.AMD64, "ARM64": Architecture.ARM64}
+6 -2
View File
@@ -1,6 +1,6 @@
import itertools import itertools
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import StrEnum
from fnmatch import fnmatch from fnmatch import fnmatch
from typing import Any from typing import Any
@@ -24,7 +24,7 @@ def selector_matches(patterns: str, string: str) -> bool:
return any(fnmatch(string, pat) for pat in expanded_patterns) return any(fnmatch(string, pat) for pat in expanded_patterns)
class EnableGroup(Enum): class EnableGroup(StrEnum):
""" """
Groups of build selectors that are not enabled by default. Groups of build selectors that are not enabled by default.
""" """
@@ -33,6 +33,10 @@ class EnableGroup(Enum):
CPythonPrerelease = "cpython-prerelease" CPythonPrerelease = "cpython-prerelease"
PyPy = "pypy" PyPy = "pypy"
@classmethod
def all_groups(cls) -> frozenset["EnableGroup"]:
return frozenset(cls)
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
class BuildSelector: class BuildSelector:
+1 -1
View File
@@ -83,7 +83,7 @@ def test_allow_empty(tmp_path, add_args, env_allow_empty):
# without error # without error
actual_wheels = utils.cibuildwheel_run( actual_wheels = utils.cibuildwheel_run(
project_dir, project_dir,
add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL", **env_allow_empty}, add_env={"CIBW_SKIP": "*", **env_allow_empty},
add_args=add_args, add_args=add_args,
) )
+2 -1
View File
@@ -17,6 +17,7 @@ import pytest
from cibuildwheel.architecture import Architecture from cibuildwheel.architecture import Architecture
from cibuildwheel.ci import CIProvider, detect_ci_provider from cibuildwheel.ci import CIProvider, detect_ci_provider
from cibuildwheel.selector import EnableGroup
from cibuildwheel.util.file import CIBW_CACHE_PATH from cibuildwheel.util.file import CIBW_CACHE_PATH
EMULATED_ARCHS: Final[list[str]] = sorted( EMULATED_ARCHS: Final[list[str]] = sorted(
@@ -120,7 +121,7 @@ def cibuildwheel_run(
_update_pip_cache_dir(env) _update_pip_cache_dir(env)
env["CIBW_ENABLE"] = "cpython-prerelease cpython-freethreading pypy" env["CIBW_ENABLE"] = " ".join(EnableGroup.all_groups())
if single_python: if single_python:
env["CIBW_BUILD"] = "cp{}{}-*".format(*SINGLE_PYTHON_VERSION) env["CIBW_BUILD"] = "cp{}{}-*".format(*SINGLE_PYTHON_VERSION)
+1 -1
View File
@@ -7,7 +7,7 @@ import setuptools._distutils.util
from cibuildwheel.ci import CIProvider, detect_ci_provider from cibuildwheel.ci import CIProvider, detect_ci_provider
from cibuildwheel.errors import FatalError from cibuildwheel.errors import FatalError
from cibuildwheel.windows import PythonConfiguration, setup_setuptools_cross_compile from cibuildwheel.platforms.windows import PythonConfiguration, setup_setuptools_cross_compile
# monkeypatching os.name is too flaky. E.g. It works on my machine, but fails in pipeline # monkeypatching os.name is too flaky. E.g. It works on my machine, but fails in pipeline
if not sys.platform.startswith("win"): if not sys.platform.startswith("win"):
+8 -6
View File
@@ -4,7 +4,7 @@ from pprint import pprint
import pytest import pytest
import cibuildwheel.linux import cibuildwheel.platforms.linux
from cibuildwheel.oci_container import OCIContainerEngineConfig from cibuildwheel.oci_container import OCIContainerEngineConfig
from cibuildwheel.options import CommandLineArguments, Options from cibuildwheel.options import CommandLineArguments, Options
@@ -46,20 +46,22 @@ def test_linux_container_split(tmp_path: Path, monkeypatch: pytest.MonkeyPatch)
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
options = Options("linux", command_line_arguments=args, env={}) options = Options("linux", command_line_arguments=args, env={})
python_configurations = cibuildwheel.linux.get_python_configurations( python_configurations = cibuildwheel.platforms.linux.get_python_configurations(
options.globals.build_selector, options.globals.architectures options.globals.build_selector, options.globals.architectures
) )
build_steps = list(cibuildwheel.linux.get_build_steps(options, python_configurations)) build_steps = list(cibuildwheel.platforms.linux.get_build_steps(options, python_configurations))
# helper functions to extract test info # helper functions to extract test info
def identifiers(step: cibuildwheel.linux.BuildStep) -> list[str]: def identifiers(step: cibuildwheel.platforms.linux.BuildStep) -> list[str]:
return [c.identifier for c in step.platform_configs] return [c.identifier for c in step.platform_configs]
def before_alls(step: cibuildwheel.linux.BuildStep) -> list[str]: def before_alls(step: cibuildwheel.platforms.linux.BuildStep) -> list[str]:
return [options.build_options(c.identifier).before_all for c in step.platform_configs] return [options.build_options(c.identifier).before_all for c in step.platform_configs]
def container_engines(step: cibuildwheel.linux.BuildStep) -> list[OCIContainerEngineConfig]: def container_engines(
step: cibuildwheel.platforms.linux.BuildStep,
) -> list[OCIContainerEngineConfig]:
return [options.build_options(c.identifier).container_engine for c in step.platform_configs] return [options.build_options(c.identifier).container_engine for c in step.platform_configs]
pprint(build_steps) pprint(build_steps)
+2 -1
View File
@@ -6,7 +6,8 @@ from pathlib import Path
import pytest import pytest
from cibuildwheel import __main__, architecture, linux, macos, pyodide, windows from cibuildwheel import __main__, architecture
from cibuildwheel.platforms import linux, macos, pyodide, windows
from cibuildwheel.util import file from cibuildwheel.util import file
+35 -8
View File
@@ -50,22 +50,49 @@ def test_output_dir_argument(also_set_environment, intercepted_build_args, monke
@pytest.mark.usefixtures("platform", "allow_empty") @pytest.mark.usefixtures("platform", "allow_empty")
def test_build_selector(intercepted_build_args, monkeypatch): def test_build_selector(intercepted_build_args, monkeypatch):
BUILD = "some build* *-selector" monkeypatch.setenv("CIBW_BUILD", "cp313-*")
SKIP = "some skip* *-selector" monkeypatch.setenv("CIBW_SKIP", "cp39-*")
monkeypatch.setenv("CIBW_BUILD", BUILD)
monkeypatch.setenv("CIBW_SKIP", SKIP)
main() main()
intercepted_build_selector = intercepted_build_args.args[0].globals.build_selector intercepted_build_selector = intercepted_build_args.args[0].globals.build_selector
assert isinstance(intercepted_build_selector, BuildSelector) assert isinstance(intercepted_build_selector, BuildSelector)
assert intercepted_build_selector("build24-this") assert intercepted_build_selector("cp313-something-to-build")
assert not intercepted_build_selector("skip65-that") assert not intercepted_build_selector("cp39-something-to-skip")
# This unit test is just testing the options of 'main' # This unit test is just testing the options of 'main'
# Unit tests for BuildSelector are in build_selector_test.py # Unit tests for BuildSelector are in build_selector_test.py
@pytest.mark.usefixtures("platform", "allow_empty")
def test_invalid_build_selector(monkeypatch, capsys):
monkeypatch.setenv("CIBW_BUILD", "invalid")
with pytest.raises(SystemExit) as e:
main()
assert e.value.code == 2
_, err = capsys.readouterr()
assert "Invalid build selector" in err
@pytest.mark.parametrize(
("option_name", "option_env_var"),
[
("skip", "CIBW_SKIP"),
("test_skip", "CIBW_TEST_SKIP"),
],
)
@pytest.mark.usefixtures("platform", "intercepted_build_args")
def test_invalid_skip_selector(monkeypatch, capsys, option_name, option_env_var):
monkeypatch.setenv(option_env_var, "invalid")
main()
_, err = capsys.readouterr()
print(err)
assert f"Invalid {option_name} selector" in err
@pytest.mark.usefixtures("platform", "intercepted_build_args") @pytest.mark.usefixtures("platform", "intercepted_build_args")
def test_empty_selector(monkeypatch): def test_empty_selector(monkeypatch):
monkeypatch.setenv("CIBW_SKIP", "*") monkeypatch.setenv("CIBW_SKIP", "*")
@@ -313,7 +340,7 @@ def test_build_selector_deprecated_error(monkeypatch, selector, pattern, capsys)
stderr = capsys.readouterr().err stderr = capsys.readouterr().err
series = "2" if "6" in pattern else "1" series = "2" if "6" in pattern else "1"
msg = f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the {series}.x series or update {selector}" msg = f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the {series}.x series or update"
assert msg in stderr assert msg in stderr
+6 -5
View File
@@ -8,7 +8,7 @@ from unittest import mock
import pytest import pytest
from cibuildwheel import linux from cibuildwheel import platforms
from cibuildwheel.__main__ import main from cibuildwheel.__main__ import main
from cibuildwheel.oci_container import OCIPlatform from cibuildwheel.oci_container import OCIPlatform
from cibuildwheel.util import file from cibuildwheel.util import file
@@ -39,10 +39,11 @@ def mock_build_container(monkeypatch):
monkeypatch.setattr(subprocess, "Popen", fail_on_call) monkeypatch.setattr(subprocess, "Popen", fail_on_call)
monkeypatch.setattr(subprocess, "run", ignore_call) monkeypatch.setattr(subprocess, "run", ignore_call)
monkeypatch.setattr(file, "download", fail_on_call) monkeypatch.setattr(file, "download", fail_on_call)
monkeypatch.setattr("cibuildwheel.linux.OCIContainer", ignore_context_call) monkeypatch.setattr("cibuildwheel.platforms.linux.OCIContainer", ignore_context_call)
monkeypatch.setattr( monkeypatch.setattr(
"cibuildwheel.linux.build_in_container", mock.Mock(spec=linux.build_in_container) "cibuildwheel.platforms.linux.build_in_container",
mock.Mock(spec=platforms.linux.build_in_container),
) )
monkeypatch.setattr("cibuildwheel.__main__.print_new_wheels", ignore_context_call) monkeypatch.setattr("cibuildwheel.__main__.print_new_wheels", ignore_context_call)
@@ -53,7 +54,7 @@ def test_build_default_launches(monkeypatch):
main() main()
build_in_container = typing.cast(mock.Mock, linux.build_in_container) build_in_container = typing.cast(mock.Mock, platforms.linux.build_in_container)
assert build_in_container.call_count == 4 assert build_in_container.call_count == 4
@@ -121,7 +122,7 @@ before-all = "true"
main() main()
build_in_container = typing.cast(mock.Mock, linux.build_in_container) build_in_container = typing.cast(mock.Mock, platforms.linux.build_in_container)
assert build_in_container.call_count == 6 assert build_in_container.call_count == 6
+2 -2
View File
@@ -6,13 +6,13 @@ from pathlib import Path
import pytest import pytest
from cibuildwheel import errors from cibuildwheel import errors
from cibuildwheel.__main__ import get_build_identifiers, get_platform_module
from cibuildwheel.bashlex_eval import local_environment_executor from cibuildwheel.bashlex_eval import local_environment_executor
from cibuildwheel.options import ( from cibuildwheel.options import (
CommandLineArguments, CommandLineArguments,
Options, Options,
_get_pinned_container_images, _get_pinned_container_images,
) )
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
from cibuildwheel.selector import EnableGroup from cibuildwheel.selector import EnableGroup
from cibuildwheel.util import resources from cibuildwheel.util import resources
from cibuildwheel.util.packaging import DependencyConstraints from cibuildwheel.util.packaging import DependencyConstraints
@@ -51,7 +51,7 @@ def test_options_1(tmp_path, monkeypatch):
options = Options(platform="linux", command_line_arguments=args, env={}) options = Options(platform="linux", command_line_arguments=args, env={})
module = get_platform_module("linux") module = ALL_PLATFORM_MODULES["linux"]
identifiers = get_build_identifiers( identifiers = get_build_identifiers(
platform_module=module, platform_module=module,
build_selector=options.globals.build_selector, build_selector=options.globals.build_selector,