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 traceback
import typing
from collections.abc import Generator, Iterable, Sequence, Set
from collections.abc import Generator, Iterable, Sequence
from pathlib import Path
from tempfile import mkdtemp
from typing import Any, Protocol, TextIO, assert_never
from typing import Any, Literal, TextIO
import cibuildwheel
import cibuildwheel.ios
import cibuildwheel.linux
import cibuildwheel.macos
import cibuildwheel.pyodide
import cibuildwheel.util
import cibuildwheel.windows
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions
from cibuildwheel.logger import log
from cibuildwheel.options import CommandLineArguments, Options, compute_options
from cibuildwheel.selector import BuildSelector, EnableGroup
from cibuildwheel.typing import PLATFORMS, GenericPythonConfiguration, PlatformName
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
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.helpers import strtobool
@@ -286,30 +282,6 @@ def _compute_platform(args: CommandLineArguments) -> PlatformName:
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
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"
raise errors.ConfigurationError(msg)
platform_module = get_platform_module(platform)
platform_module = ALL_PLATFORM_MODULES[platform]
identifiers = get_build_identifiers(
platform_module=platform_module,
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")
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]:
warnings = []
@@ -482,6 +445,75 @@ def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str
)
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
+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
def defaults(self) -> Self:
return self.__class__(
@@ -985,9 +977,7 @@ def compute_options(
command_line_arguments: CommandLineArguments,
env: Mapping[str, str],
) -> Options:
options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
options.check_for_deprecated_options()
return options
return Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
@functools.cache
@@ -1002,16 +992,3 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
all_pinned_images = configparser.ConfigParser()
all_pinned_images.read(resources.PINNED_DOCKER_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 . import errors
from .architecture import Architecture
from .environment import ParsedEnvironment
from .frontend import (
from .. import errors
from ..architecture import Architecture
from ..environment import ParsedEnvironment
from ..frontend import (
BuildFrontendConfig,
BuildFrontendName,
get_build_frontend_extra_flags,
)
from .logger import log
from .macos import install_cpython as install_build_cpython
from .options import Options
from .selector import BuildSelector
from .typing import PathOrStr
from .util import resources
from .util.cmd import call, shell
from .util.file import (
from ..logger import log
from ..options import Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH,
copy_test_sources,
download,
move_file,
)
from .util.helpers import prepare_command
from .util.packaging import (
from ..util.helpers import prepare_command
from ..util.packaging import (
combine_constraints,
find_compatible_wheel,
get_pip_version,
)
from .venv import virtualenv
from ..venv import virtualenv
from .macos import install_cpython as install_build_cpython
@dataclass(frozen=True)
@@ -72,10 +72,7 @@ class PythonConfiguration:
return "ios-arm64_x86_64-simulator" if self.is_simulator else "ios-arm64"
def get_python_configurations(
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
def all_python_configurations() -> list[PythonConfiguration]:
# 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,
# load the macos configurations, determine the macOS configuration that
@@ -97,7 +94,7 @@ def get_python_configurations(
# Load the platform configuration
full_python_configs = resources.read_python_configs("ios")
# Build the configurations, annotating with macOS URL details.
python_configurations = [
return [
PythonConfiguration(
**item,
build_url=build_url(item),
@@ -105,6 +102,13 @@ def get_python_configurations(
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
python_configurations = [
c
@@ -8,18 +8,18 @@ from dataclasses import dataclass
from pathlib import Path, PurePath, PurePosixPath
from typing import assert_never
from . import errors
from .architecture import Architecture
from .frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from .logger import log
from .oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from .options import BuildOptions, Options
from .selector import BuildSelector
from .typing import PathOrStr
from .util import resources
from .util.file import copy_test_sources
from .util.helpers import prepare_command, unwrap
from .util.packaging import find_compatible_wheel
from .. import errors
from ..architecture import Architecture
from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from ..logger import log
from ..oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from ..options import BuildOptions, Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.file import copy_test_sources
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import find_compatible_wheel
ARCHITECTURE_OCI_PLATFORM_MAP = {
Architecture.x86_64: OCIPlatform.AMD64,
@@ -50,13 +50,16 @@ class BuildStep:
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(
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("linux")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = all_python_configurations()
# return all configurations whose arch is in our `architectures` set,
# and match the build/skip rules
@@ -15,26 +15,26 @@ from typing import Literal, assert_never
from filelock import FileLock
from packaging.version import Version
from . import errors
from .architecture import Architecture
from .ci import detect_ci_provider
from .environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from .logger import log
from .options import Options
from .selector import BuildSelector
from .typing import PathOrStr
from .util import resources
from .util.cmd import call, shell
from .util.file import (
from .. import errors
from ..architecture import Architecture
from ..ci import detect_ci_provider
from ..environment import ParsedEnvironment
from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from ..logger import log
from ..options import Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH,
copy_test_sources,
download,
move_file,
)
from .util.helpers import prepare_command, unwrap
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import find_uv, virtualenv
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from ..venv import find_uv, virtualenv
@functools.cache
@@ -84,12 +84,15 @@ class PythonConfiguration:
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(
build_selector: BuildSelector, architectures: Set[Architecture]
) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("macos")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = all_python_configurations()
# filter out configs that don't match any of the selected architectures
python_configurations = [
@@ -11,17 +11,17 @@ from typing import Final
from filelock import FileLock
from . import errors
from .architecture import Architecture
from .environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from .logger import log
from .options import Options
from .selector import BuildSelector
from .typing import PathOrStr
from .util import resources
from .util.cmd import call, shell
from .util.file import (
from .. import errors
from ..architecture import Architecture
from ..environment import ParsedEnvironment
from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from ..logger import log
from ..options import Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH,
copy_test_sources,
download,
@@ -29,9 +29,9 @@ from .util.file import (
extract_zip,
move_file,
)
from .util.helpers import prepare_command
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import virtualenv
from ..util.helpers import prepare_command
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from ..venv import virtualenv
IS_WIN: Final[bool] = sys.platform.startswith("win")
@@ -215,15 +215,16 @@ def setup_python(
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(
build_selector: BuildSelector,
architectures: Set[Architecture], # noqa: ARG001
) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("pyodide")
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
return [c for c in all_python_configurations() if build_selector(c.identifier)]
def build(options: Options, tmp_path: Path) -> None:
@@ -11,20 +11,20 @@ from typing import assert_never
from filelock import FileLock
from . import errors
from .architecture import Architecture
from .environment import ParsedEnvironment
from .frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from .logger import log
from .options import Options
from .selector import BuildSelector
from .typing import PathOrStr
from .util import resources
from .util.cmd import call, shell
from .util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file
from .util.helpers import prepare_command, unwrap
from .util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from .venv import find_uv, virtualenv
from .. import errors
from ..architecture import Architecture
from ..environment import ParsedEnvironment
from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags
from ..logger import log
from ..options import Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from ..venv import find_uv, virtualenv
def get_nuget_args(
@@ -59,13 +59,16 @@ class PythonConfiguration:
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(
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("windows")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = all_python_configurations()
map_arch = {"32": Architecture.x86, "64": Architecture.AMD64, "ARM64": Architecture.ARM64}
+6 -2
View File
@@ -1,6 +1,6 @@
import itertools
from dataclasses import dataclass
from enum import Enum
from enum import StrEnum
from fnmatch import fnmatch
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)
class EnableGroup(Enum):
class EnableGroup(StrEnum):
"""
Groups of build selectors that are not enabled by default.
"""
@@ -33,6 +33,10 @@ class EnableGroup(Enum):
CPythonPrerelease = "cpython-prerelease"
PyPy = "pypy"
@classmethod
def all_groups(cls) -> frozenset["EnableGroup"]:
return frozenset(cls)
@dataclass(frozen=True, kw_only=True)
class BuildSelector:
+1 -1
View File
@@ -83,7 +83,7 @@ def test_allow_empty(tmp_path, add_args, env_allow_empty):
# without error
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL", **env_allow_empty},
add_env={"CIBW_SKIP": "*", **env_allow_empty},
add_args=add_args,
)
+2 -1
View File
@@ -17,6 +17,7 @@ import pytest
from cibuildwheel.architecture import Architecture
from cibuildwheel.ci import CIProvider, detect_ci_provider
from cibuildwheel.selector import EnableGroup
from cibuildwheel.util.file import CIBW_CACHE_PATH
EMULATED_ARCHS: Final[list[str]] = sorted(
@@ -120,7 +121,7 @@ def cibuildwheel_run(
_update_pip_cache_dir(env)
env["CIBW_ENABLE"] = "cpython-prerelease cpython-freethreading pypy"
env["CIBW_ENABLE"] = " ".join(EnableGroup.all_groups())
if single_python:
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.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
if not sys.platform.startswith("win"):
+8 -6
View File
@@ -4,7 +4,7 @@ from pprint import pprint
import pytest
import cibuildwheel.linux
import cibuildwheel.platforms.linux
from cibuildwheel.oci_container import OCIContainerEngineConfig
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)
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
)
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
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]
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]
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]
pprint(build_steps)
+2 -1
View File
@@ -6,7 +6,8 @@ from pathlib import Path
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
+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")
def test_build_selector(intercepted_build_args, monkeypatch):
BUILD = "some build* *-selector"
SKIP = "some skip* *-selector"
monkeypatch.setenv("CIBW_BUILD", BUILD)
monkeypatch.setenv("CIBW_SKIP", SKIP)
monkeypatch.setenv("CIBW_BUILD", "cp313-*")
monkeypatch.setenv("CIBW_SKIP", "cp39-*")
main()
intercepted_build_selector = intercepted_build_args.args[0].globals.build_selector
assert isinstance(intercepted_build_selector, BuildSelector)
assert intercepted_build_selector("build24-this")
assert not intercepted_build_selector("skip65-that")
assert intercepted_build_selector("cp313-something-to-build")
assert not intercepted_build_selector("cp39-something-to-skip")
# This unit test is just testing the options of 'main'
# 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")
def test_empty_selector(monkeypatch):
monkeypatch.setenv("CIBW_SKIP", "*")
@@ -313,7 +340,7 @@ def test_build_selector_deprecated_error(monkeypatch, selector, pattern, capsys)
stderr = capsys.readouterr().err
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
+6 -5
View File
@@ -8,7 +8,7 @@ from unittest import mock
import pytest
from cibuildwheel import linux
from cibuildwheel import platforms
from cibuildwheel.__main__ import main
from cibuildwheel.oci_container import OCIPlatform
from cibuildwheel.util import file
@@ -39,10 +39,11 @@ def mock_build_container(monkeypatch):
monkeypatch.setattr(subprocess, "Popen", fail_on_call)
monkeypatch.setattr(subprocess, "run", ignore_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(
"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)
@@ -53,7 +54,7 @@ def test_build_default_launches(monkeypatch):
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
@@ -121,7 +122,7 @@ before-all = "true"
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
+2 -2
View File
@@ -6,13 +6,13 @@ from pathlib import Path
import pytest
from cibuildwheel import errors
from cibuildwheel.__main__ import get_build_identifiers, get_platform_module
from cibuildwheel.bashlex_eval import local_environment_executor
from cibuildwheel.options import (
CommandLineArguments,
Options,
_get_pinned_container_images,
)
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
from cibuildwheel.selector import EnableGroup
from cibuildwheel.util import resources
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={})
module = get_platform_module("linux")
module = ALL_PLATFORM_MODULES["linux"]
identifiers = get_build_identifiers(
platform_module=module,
build_selector=options.globals.build_selector,