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:
+75
-43
@@ -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
@@ -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)
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user