feat: adding overrides
This commit is contained in:
@@ -59,6 +59,7 @@ repos:
|
|||||||
- tomli
|
- tomli
|
||||||
- types-certifi
|
- types-certifi
|
||||||
- types-click
|
- types-click
|
||||||
|
- types-dataclasses
|
||||||
- types-jinja2
|
- types-jinja2
|
||||||
- types-pyyaml
|
- types-pyyaml
|
||||||
- types-requests
|
- types-requests
|
||||||
|
|||||||
+17
-14
@@ -14,7 +14,7 @@ from cibuildwheel.architecture import Architecture, allowed_architectures_check
|
|||||||
from cibuildwheel.options import compute_options
|
from cibuildwheel.options import compute_options
|
||||||
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import (
|
||||||
BuildOptions,
|
AllBuildOptions,
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
Unbuffered,
|
Unbuffered,
|
||||||
detect_ci_provider,
|
detect_ci_provider,
|
||||||
@@ -148,12 +148,12 @@ def main() -> None:
|
|||||||
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
|
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
|
||||||
)
|
)
|
||||||
|
|
||||||
build_options = compute_options(
|
all_build_options, build_options_by_selector = compute_options(
|
||||||
platform, package_dir, output_dir, args.config_file, args.archs, args.prerelease_pythons
|
platform, package_dir, output_dir, args.config_file, args.archs, args.prerelease_pythons
|
||||||
)
|
)
|
||||||
|
|
||||||
identifiers = get_build_identifiers(
|
identifiers = get_build_identifiers(
|
||||||
platform, build_options.build_selector, build_options.architectures
|
platform, all_build_options.build_selector, all_build_options.architectures
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.print_build_identifiers:
|
if args.print_build_identifiers:
|
||||||
@@ -161,6 +161,8 @@ def main() -> None:
|
|||||||
print(identifier)
|
print(identifier)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
build_options = AllBuildOptions(all_build_options, build_options_by_selector, identifiers)
|
||||||
|
|
||||||
# Add CIBUILDWHEEL environment variable
|
# Add CIBUILDWHEEL environment variable
|
||||||
# This needs to be passed on to the docker container in linux.py
|
# This needs to be passed on to the docker container in linux.py
|
||||||
os.environ["CIBUILDWHEEL"] = "1"
|
os.environ["CIBUILDWHEEL"] = "1"
|
||||||
@@ -200,7 +202,7 @@ def main() -> None:
|
|||||||
assert_never(platform)
|
assert_never(platform)
|
||||||
|
|
||||||
|
|
||||||
def print_preamble(platform: str, build_options: BuildOptions) -> None:
|
def print_preamble(platform: str, build_options: AllBuildOptions) -> None:
|
||||||
print(
|
print(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
@@ -254,20 +256,21 @@ def get_build_identifiers(
|
|||||||
return [config.identifier for config in python_configurations]
|
return [config.identifier for config in python_configurations]
|
||||||
|
|
||||||
|
|
||||||
def detect_warnings(platform: str, build_options: BuildOptions) -> List[str]:
|
def detect_warnings(platform: str, all_options: AllBuildOptions) -> List[str]:
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
# warn about deprecated {python} and {pip}
|
# warn about deprecated {python} and {pip}
|
||||||
for option_name in ["test_command", "before_build"]:
|
for build_options in all_options.values():
|
||||||
option_value = getattr(build_options, option_name)
|
for option_name in ["test_command", "before_build"]:
|
||||||
|
option_value = getattr(build_options, option_name)
|
||||||
|
|
||||||
if option_value and ("{python}" in option_value or "{pip}" in option_value):
|
if option_value and ("{python}" in option_value or "{pip}" in option_value):
|
||||||
# Reminder: in an f-string, double braces means literal single brace
|
# Reminder: in an f-string, double braces means literal single brace
|
||||||
msg = (
|
msg = (
|
||||||
f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
|
f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
|
||||||
"and will be removed in a future release. Simply use 'python' or 'pip' instead."
|
"and will be removed in a future release. Simply use 'python' or 'pip' instead."
|
||||||
)
|
)
|
||||||
warnings.append(msg)
|
warnings.append(msg)
|
||||||
|
|
||||||
return warnings
|
return warnings
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataclasses
|
||||||
from typing import Dict, List, Mapping, Optional
|
from typing import Dict, List, Mapping, Optional
|
||||||
|
|
||||||
import bashlex
|
import bashlex
|
||||||
@@ -60,10 +61,15 @@ class EnvironmentAssignment:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.name}={self.value}"
|
return f"{self.name}={self.value}"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, EnvironmentAssignment):
|
||||||
|
return self.name == other.name and self.value == other.value
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
class ParsedEnvironment:
|
class ParsedEnvironment:
|
||||||
def __init__(self, assignments: List[EnvironmentAssignment]):
|
assignments: List[EnvironmentAssignment]
|
||||||
self.assignments = assignments
|
|
||||||
|
|
||||||
def as_dictionary(
|
def as_dictionary(
|
||||||
self,
|
self,
|
||||||
@@ -82,7 +88,7 @@ class ParsedEnvironment:
|
|||||||
return [a.as_shell_assignment() for a in self.assignments]
|
return [a.as_shell_assignment() for a in self.assignments]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ParsedEnvironment({[repr(a) for a in self.assignments]!r})"
|
return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})"
|
||||||
|
|
||||||
|
|
||||||
def parse_environment(env_string: str) -> ParsedEnvironment:
|
def parse_environment(env_string: str) -> ParsedEnvironment:
|
||||||
|
|||||||
+26
-28
@@ -9,7 +9,7 @@ from .docker_container import DockerContainer
|
|||||||
from .logger import log
|
from .logger import log
|
||||||
from .typing import PathOrStr, assert_never
|
from .typing import PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
BuildOptions,
|
AllBuildOptions,
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
get_build_verbosity_extra_flags,
|
get_build_verbosity_extra_flags,
|
||||||
@@ -54,7 +54,7 @@ def get_python_configurations(
|
|||||||
|
|
||||||
|
|
||||||
def get_build_steps(
|
def get_build_steps(
|
||||||
options: BuildOptions, python_configurations: List[PythonConfiguration]
|
all_options: AllBuildOptions, python_configurations: List[PythonConfiguration]
|
||||||
) -> Iterator[BuildStep]:
|
) -> Iterator[BuildStep]:
|
||||||
platforms = [
|
platforms = [
|
||||||
("cp", "manylinux_x86_64", "x86_64"),
|
("cp", "manylinux_x86_64", "x86_64"),
|
||||||
@@ -73,15 +73,6 @@ def get_build_steps(
|
|||||||
]
|
]
|
||||||
|
|
||||||
for implementation, platform_tag, platform_arch in platforms:
|
for implementation, platform_tag, platform_arch in platforms:
|
||||||
assert options.manylinux_images is not None
|
|
||||||
assert options.musllinux_images is not None
|
|
||||||
|
|
||||||
docker_image = (
|
|
||||||
options.manylinux_images[platform_arch]
|
|
||||||
if platform_tag.startswith("manylinux")
|
|
||||||
else options.musllinux_images[platform_arch]
|
|
||||||
)
|
|
||||||
|
|
||||||
platform_configs = [
|
platform_configs = [
|
||||||
c
|
c
|
||||||
for c in python_configurations
|
for c in python_configurations
|
||||||
@@ -90,11 +81,15 @@ def get_build_steps(
|
|||||||
if not platform_configs:
|
if not platform_configs:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield BuildStep(platform_configs, platform_tag, docker_image)
|
for local_configs, docker_image in all_options.produce_image_batches(
|
||||||
|
platform_configs, platform_tag, platform_arch
|
||||||
|
):
|
||||||
|
# TODO: Validate that the options are not invalid for these selectors
|
||||||
|
yield BuildStep(local_configs, platform_tag, docker_image)
|
||||||
|
|
||||||
|
|
||||||
def build_on_docker(
|
def build_on_docker(
|
||||||
options: BuildOptions,
|
all_options: AllBuildOptions,
|
||||||
platform_configs: List[PythonConfiguration],
|
platform_configs: List[PythonConfiguration],
|
||||||
docker: DockerContainer,
|
docker: DockerContainer,
|
||||||
container_project_path: PurePath,
|
container_project_path: PurePath,
|
||||||
@@ -105,16 +100,16 @@ def build_on_docker(
|
|||||||
log.step("Copying project into Docker...")
|
log.step("Copying project into Docker...")
|
||||||
docker.copy_into(Path.cwd(), container_project_path)
|
docker.copy_into(Path.cwd(), container_project_path)
|
||||||
|
|
||||||
if options.before_all:
|
if all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
|
|
||||||
env = docker.get_environment()
|
env = docker.get_environment()
|
||||||
env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}'
|
env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}'
|
||||||
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||||
env = options.environment.as_dictionary(env, executor=docker.environment_executor)
|
env = all_options.environment.as_dictionary(env, executor=docker.environment_executor)
|
||||||
|
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all,
|
all_options.before_all,
|
||||||
project=container_project_path,
|
project=container_project_path,
|
||||||
package=container_package_dir,
|
package=container_package_dir,
|
||||||
)
|
)
|
||||||
@@ -122,6 +117,7 @@ def build_on_docker(
|
|||||||
|
|
||||||
for config in platform_configs:
|
for config in platform_configs:
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
options = all_options[config.identifier]
|
||||||
|
|
||||||
dependency_constraint_flags: List[PathOrStr] = []
|
dependency_constraint_flags: List[PathOrStr] = []
|
||||||
|
|
||||||
@@ -284,11 +280,11 @@ def build_on_docker(
|
|||||||
|
|
||||||
log.step("Copying wheels back to host...")
|
log.step("Copying wheels back to host...")
|
||||||
# copy the output back into the host
|
# copy the output back into the host
|
||||||
docker.copy_out(container_output_dir, options.output_dir)
|
docker.copy_out(container_output_dir, all_options.output_dir)
|
||||||
log.step_end()
|
log.step_end()
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(all_options: AllBuildOptions) -> None:
|
||||||
try:
|
try:
|
||||||
# check docker is installed
|
# check docker is installed
|
||||||
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
|
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
|
||||||
@@ -301,19 +297,19 @@ def build(options: BuildOptions) -> None:
|
|||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
assert options.manylinux_images is not None
|
python_configurations = get_python_configurations(
|
||||||
assert options.musllinux_images is not None
|
all_options.build_selector, all_options.architectures
|
||||||
python_configurations = get_python_configurations(options.build_selector, options.architectures)
|
)
|
||||||
|
|
||||||
cwd = Path.cwd()
|
cwd = Path.cwd()
|
||||||
abs_package_dir = options.package_dir.resolve()
|
abs_package_dir = all_options.package_dir.resolve()
|
||||||
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
|
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
|
||||||
raise Exception("package_dir must be inside the working directory")
|
raise Exception("package_dir must be inside the working directory")
|
||||||
|
|
||||||
container_project_path = PurePath("/project")
|
container_project_path = PurePath("/project")
|
||||||
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
|
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
|
||||||
|
|
||||||
for build_step in get_build_steps(options, python_configurations):
|
for build_step in get_build_steps(all_options, python_configurations):
|
||||||
try:
|
try:
|
||||||
log.step(f"Starting Docker image {build_step.docker_image}...")
|
log.step(f"Starting Docker image {build_step.docker_image}...")
|
||||||
|
|
||||||
@@ -324,7 +320,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
) as docker:
|
) as docker:
|
||||||
|
|
||||||
build_on_docker(
|
build_on_docker(
|
||||||
options,
|
all_options,
|
||||||
build_step.platform_configs,
|
build_step.platform_configs,
|
||||||
docker,
|
docker,
|
||||||
container_project_path,
|
container_project_path,
|
||||||
@@ -335,7 +331,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
log.step_end_with_error(
|
log.step_end_with_error(
|
||||||
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
|
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
|
||||||
)
|
)
|
||||||
troubleshoot(options, error)
|
troubleshoot(all_options, error)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -346,16 +342,18 @@ def _matches_prepared_command(error_cmd: List[str], command_template: str) -> bo
|
|||||||
return error_cmd[2].startswith(command_prefix)
|
return error_cmd[2].startswith(command_prefix)
|
||||||
|
|
||||||
|
|
||||||
def troubleshoot(options: BuildOptions, error: Exception) -> None:
|
def troubleshoot(all_options: AllBuildOptions, error: Exception) -> None:
|
||||||
|
|
||||||
if isinstance(error, subprocess.CalledProcessError) and (
|
if isinstance(error, subprocess.CalledProcessError) and (
|
||||||
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
||||||
or error.cmd[0:3] == ["python", "-m", "build"]
|
or error.cmd[0:3] == ["python", "-m", "build"]
|
||||||
or _matches_prepared_command(error.cmd, options.repair_command)
|
or _matches_prepared_command(
|
||||||
|
error.cmd, all_options.general_build_options.repair_command
|
||||||
|
) # TODO
|
||||||
):
|
):
|
||||||
# the wheel build step failed
|
# the wheel build step failed
|
||||||
print("Checking for common errors...")
|
print("Checking for common errors...")
|
||||||
so_files = list(options.package_dir.glob("**/*.so"))
|
so_files = list(all_options.package_dir.glob("**/*.so"))
|
||||||
|
|
||||||
if so_files:
|
if so_files:
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from .environment import ParsedEnvironment
|
|||||||
from .logger import log
|
from .logger import log
|
||||||
from .typing import Literal, PathOrStr, assert_never
|
from .typing import Literal, PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
|
AllBuildOptions,
|
||||||
BuildFrontend,
|
BuildFrontend,
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
download,
|
download,
|
||||||
@@ -343,26 +343,29 @@ def setup_python(
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(all_options: AllBuildOptions) -> None:
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
||||||
built_wheel_dir = temp_dir / "built_wheel"
|
built_wheel_dir = temp_dir / "built_wheel"
|
||||||
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
||||||
|
|
||||||
|
all_options.check_build_selectors()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if options.before_all:
|
if all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
env = options.environment.as_dictionary(prev_environment=os.environ)
|
env = all_options.environment.as_dictionary(prev_environment=os.environ)
|
||||||
env.setdefault("MACOSX_DEPLOYMENT_TARGET", "10.9")
|
env.setdefault("MACOSX_DEPLOYMENT_TARGET", "10.9")
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all, project=".", package=options.package_dir
|
all_options.before_all, project=".", package=all_options.package_dir
|
||||||
)
|
)
|
||||||
call([before_all_prepared], shell=True, env=env)
|
call([before_all_prepared], shell=True, env=env)
|
||||||
|
|
||||||
python_configurations = get_python_configurations(
|
python_configurations = get_python_configurations(
|
||||||
options.build_selector, options.architectures
|
all_options.build_selector, all_options.architectures
|
||||||
)
|
)
|
||||||
|
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
|
options = all_options[config.identifier]
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
|
||||||
config_is_arm64 = config.identifier.endswith("arm64")
|
config_is_arm64 = config.identifier.endswith("arm64")
|
||||||
|
|||||||
+70
-12
@@ -1,9 +1,10 @@
|
|||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
|
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, TypeVar, Union
|
||||||
|
|
||||||
import tomli
|
import tomli
|
||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
@@ -58,6 +59,9 @@ def _dig_first(*pairs: Tuple[Mapping[str, Setting], str], ignore_empty: bool = F
|
|||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ConfigOptions")
|
||||||
|
|
||||||
|
|
||||||
class ConfigOptions:
|
class ConfigOptions:
|
||||||
"""
|
"""
|
||||||
Gets options from the environment, config or defaults, optionally scoped
|
Gets options from the environment, config or defaults, optionally scoped
|
||||||
@@ -117,12 +121,27 @@ class ConfigOptions:
|
|||||||
self.config_options = config_options
|
self.config_options = config_options
|
||||||
self.config_platform_options = config_platform_options
|
self.config_platform_options = config_platform_options
|
||||||
|
|
||||||
|
self.overrides: Dict[str, Dict[str, Any]] = {}
|
||||||
|
self.current_override: str = "*"
|
||||||
|
|
||||||
|
overrides = self.config_options.get("overrides")
|
||||||
|
if overrides is not None:
|
||||||
|
if not isinstance(overrides, list):
|
||||||
|
raise ConfigOptionError('"tool.cibuildwheel.overrides" must be a list')
|
||||||
|
for override in overrides:
|
||||||
|
selector = override.pop("select")
|
||||||
|
if isinstance(selector, list):
|
||||||
|
selector = " ".join(selector)
|
||||||
|
if selector in {"", "*"}:
|
||||||
|
raise ConfigOptionError("select all must not be used in an override")
|
||||||
|
self.overrides[selector.strip()] = override
|
||||||
|
|
||||||
def _is_valid_global_option(self, name: str) -> bool:
|
def _is_valid_global_option(self, name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if an option with this name is allowed in the
|
Returns True if an option with this name is allowed in the
|
||||||
[tool.cibuildwheel] section of a config file.
|
[tool.cibuildwheel] section of a config file.
|
||||||
"""
|
"""
|
||||||
allowed_option_names = self.default_options.keys() | PLATFORMS
|
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
|
||||||
|
|
||||||
return name in allowed_option_names
|
return name in allowed_option_names
|
||||||
|
|
||||||
@@ -151,6 +170,14 @@ class ConfigOptions:
|
|||||||
|
|
||||||
return global_options, platform_options
|
return global_options, platform_options
|
||||||
|
|
||||||
|
def override(self: T, selector: str) -> T:
|
||||||
|
"""
|
||||||
|
Start an override scope.
|
||||||
|
"""
|
||||||
|
other = copy.copy(self)
|
||||||
|
other.current_override = selector
|
||||||
|
return other
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
@@ -179,9 +206,11 @@ class ConfigOptions:
|
|||||||
|
|
||||||
# get the option from the environment, then the config file, then finally the default.
|
# get the option from the environment, then the config file, then finally the default.
|
||||||
# platform-specific options are preferred, if they're allowed.
|
# platform-specific options are preferred, if they're allowed.
|
||||||
|
empty: Dict[str, Any] = {}
|
||||||
result = _dig_first(
|
result = _dig_first(
|
||||||
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
|
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
|
||||||
(os.environ, envvar),
|
(os.environ, envvar),
|
||||||
|
(self.overrides.get(self.current_override, empty), name),
|
||||||
(self.config_platform_options, name),
|
(self.config_platform_options, name),
|
||||||
(self.config_options, name),
|
(self.config_options, name),
|
||||||
(self.default_platform_options, name),
|
(self.default_platform_options, name),
|
||||||
@@ -210,7 +239,7 @@ def compute_options(
|
|||||||
config_file: Optional[str],
|
config_file: Optional[str],
|
||||||
args_archs: Optional[str],
|
args_archs: Optional[str],
|
||||||
prerelease_pythons: bool,
|
prerelease_pythons: bool,
|
||||||
) -> BuildOptions:
|
) -> Tuple[BuildOptions, Dict[str, BuildOptions]]:
|
||||||
"""
|
"""
|
||||||
Compute the options from the environment and configuration file.
|
Compute the options from the environment and configuration file.
|
||||||
"""
|
"""
|
||||||
@@ -263,11 +292,25 @@ def compute_options(
|
|||||||
)
|
)
|
||||||
test_selector = TestSelector(skip_config=test_skip)
|
test_selector = TestSelector(skip_config=test_skip)
|
||||||
|
|
||||||
return _compute_single_options(
|
return _compute_all_options(
|
||||||
options, args_archs, build_selector, test_selector, platform, package_dir, output_dir
|
options, args_archs, build_selector, test_selector, platform, package_dir, output_dir
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pinned_docker_images() -> Mapping[str, Mapping[str, str]]:
|
||||||
|
"""
|
||||||
|
This looks like a dict of dicts, e.g.
|
||||||
|
{ 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
||||||
|
'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
||||||
|
'pypy_x86_64': {'manylinux2010': '...' }
|
||||||
|
... }
|
||||||
|
"""
|
||||||
|
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
||||||
|
all_pinned_docker_images = ConfigParser()
|
||||||
|
all_pinned_docker_images.read(pinned_docker_images_file)
|
||||||
|
return all_pinned_docker_images
|
||||||
|
|
||||||
|
|
||||||
def _compute_single_options(
|
def _compute_single_options(
|
||||||
options: ConfigOptions,
|
options: ConfigOptions,
|
||||||
args_archs: Optional[str],
|
args_archs: Optional[str],
|
||||||
@@ -337,14 +380,7 @@ def _compute_single_options(
|
|||||||
manylinux_images: Dict[str, str] = {}
|
manylinux_images: Dict[str, str] = {}
|
||||||
musllinux_images: Dict[str, str] = {}
|
musllinux_images: Dict[str, str] = {}
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
all_pinned_docker_images = _get_pinned_docker_images()
|
||||||
all_pinned_docker_images = ConfigParser()
|
|
||||||
all_pinned_docker_images.read(pinned_docker_images_file)
|
|
||||||
# all_pinned_docker_images looks like a dict of dicts, e.g.
|
|
||||||
# { 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
|
||||||
# 'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
|
||||||
# 'pypy_x86_64': {'manylinux2010': '...' }
|
|
||||||
# ... }
|
|
||||||
|
|
||||||
for build_platform in MANYLINUX_ARCHS:
|
for build_platform in MANYLINUX_ARCHS:
|
||||||
pinned_images = all_pinned_docker_images[build_platform]
|
pinned_images = all_pinned_docker_images[build_platform]
|
||||||
@@ -359,6 +395,7 @@ def _compute_single_options(
|
|||||||
else:
|
else:
|
||||||
image = config_value
|
image = config_value
|
||||||
|
|
||||||
|
assert image is not None
|
||||||
manylinux_images[build_platform] = image
|
manylinux_images[build_platform] = image
|
||||||
|
|
||||||
for build_platform in MUSLLINUX_ARCHS:
|
for build_platform in MUSLLINUX_ARCHS:
|
||||||
@@ -397,6 +434,27 @@ def _compute_single_options(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_all_options(
|
||||||
|
options: ConfigOptions,
|
||||||
|
args_archs: Optional[str],
|
||||||
|
build_selector: BuildSelector,
|
||||||
|
test_selector: TestSelector,
|
||||||
|
platform: PlatformName,
|
||||||
|
package_dir: Path,
|
||||||
|
output_dir: Path,
|
||||||
|
) -> Tuple[BuildOptions, Dict[str, BuildOptions]]:
|
||||||
|
args = (args_archs, build_selector, test_selector, platform, package_dir, output_dir)
|
||||||
|
|
||||||
|
general_build_options = _compute_single_options(options, *args)
|
||||||
|
|
||||||
|
selectors = options.overrides.keys()
|
||||||
|
build_options_by_selector = {
|
||||||
|
s: _compute_single_options(options.override(s), *args) for s in selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
return general_build_options, build_options_by_selector
|
||||||
|
|
||||||
|
|
||||||
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
|
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
|
||||||
if "p2" in selector or "p35" in selector:
|
if "p2" in selector or "p35" in selector:
|
||||||
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
|
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ else:
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
"Final",
|
"Final",
|
||||||
"Literal",
|
"Literal",
|
||||||
"TypedDict",
|
"PLATFORMS",
|
||||||
"Set",
|
|
||||||
"Union",
|
|
||||||
"PopenBytes",
|
|
||||||
"PathOrStr",
|
"PathOrStr",
|
||||||
"PlatformName",
|
"PlatformName",
|
||||||
"Protocol",
|
"Protocol",
|
||||||
"PLATFORMS",
|
"PLATFORMS",
|
||||||
|
"PopenBytes",
|
||||||
|
"Protocol",
|
||||||
|
"Set",
|
||||||
|
"TypedDict",
|
||||||
|
"Union",
|
||||||
"assert_never",
|
"assert_never",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+129
-2
@@ -1,4 +1,5 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
|
import dataclasses
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
@@ -9,10 +10,23 @@ import sys
|
|||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from collections import defaultdict
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Dict, Iterator, List, NamedTuple, Optional, Set
|
from typing import (
|
||||||
|
Counter,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
NamedTuple,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
TypeVar,
|
||||||
|
)
|
||||||
|
|
||||||
import bracex
|
import bracex
|
||||||
import certifi
|
import certifi
|
||||||
@@ -22,7 +36,7 @@ from packaging.version import Version
|
|||||||
|
|
||||||
from .architecture import Architecture
|
from .architecture import Architecture
|
||||||
from .environment import ParsedEnvironment
|
from .environment import ParsedEnvironment
|
||||||
from .typing import Literal, PathOrStr, PlatformName
|
from .typing import Literal, PathOrStr, PlatformName, Protocol
|
||||||
|
|
||||||
resources_dir = Path(__file__).parent / "resources"
|
resources_dir = Path(__file__).parent / "resources"
|
||||||
|
|
||||||
@@ -250,6 +264,119 @@ class BuildOptions(NamedTuple):
|
|||||||
return "\n".join(res)
|
return "\n".join(res)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleConfig(Protocol):
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
SC = TypeVar("SC", bound=SimpleConfig)
|
||||||
|
T = TypeVar("T", bound="AllBuildOptions")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class AllBuildOptions:
|
||||||
|
general_build_options: BuildOptions
|
||||||
|
build_options_by_selector: Dict[str, BuildOptions]
|
||||||
|
identifiers: List[str]
|
||||||
|
|
||||||
|
def __getitem__(self, identifier: str) -> BuildOptions:
|
||||||
|
for sel in self.build_options_by_selector:
|
||||||
|
bs = BuildSelector(build_config=sel, skip_config="")
|
||||||
|
if bs(identifier):
|
||||||
|
return self.build_options_by_selector[sel]
|
||||||
|
|
||||||
|
return self.general_build_options
|
||||||
|
|
||||||
|
def values(self) -> Iterable[BuildOptions]:
|
||||||
|
return itertools.chain(
|
||||||
|
[self.general_build_options], self.build_options_by_selector.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
# These values are not overridable in some cases
|
||||||
|
@property
|
||||||
|
def package_dir(self) -> Path:
|
||||||
|
return self.general_build_options.package_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def build_selector(self) -> BuildSelector:
|
||||||
|
return self.general_build_options.build_selector
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_dir(self) -> Path:
|
||||||
|
return self.general_build_options.output_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def architectures(self) -> Set[Architecture]:
|
||||||
|
return self.general_build_options.architectures
|
||||||
|
|
||||||
|
@property
|
||||||
|
def environment(self) -> ParsedEnvironment:
|
||||||
|
return self.general_build_options.environment
|
||||||
|
|
||||||
|
@property
|
||||||
|
def before_all(self) -> str:
|
||||||
|
return self.general_build_options.before_all
|
||||||
|
|
||||||
|
def produce_image_batches(
|
||||||
|
self,
|
||||||
|
configurations: List[SC],
|
||||||
|
platform_tag: str,
|
||||||
|
platform_arch: str,
|
||||||
|
) -> Iterator[Tuple[List[SC], str]]:
|
||||||
|
|
||||||
|
docker_images: Mapping[str, List[SC]] = defaultdict(list)
|
||||||
|
|
||||||
|
for config in configurations:
|
||||||
|
build_options = self[config.identifier]
|
||||||
|
images = (
|
||||||
|
build_options.manylinux_images
|
||||||
|
if platform_tag.startswith("manylinux")
|
||||||
|
else build_options.musllinux_images
|
||||||
|
)
|
||||||
|
assert images is not None
|
||||||
|
docker_images[images[platform_arch]].append(config)
|
||||||
|
|
||||||
|
for image, configs in docker_images.items():
|
||||||
|
# TODO: check for colisions for identifiers in configs
|
||||||
|
# Some settings (before-all) are not overridable in the same image
|
||||||
|
yield configs, image
|
||||||
|
|
||||||
|
def check_build_selectors(self) -> None:
|
||||||
|
hits = Counter[str]()
|
||||||
|
for sel in self.build_options_by_selector:
|
||||||
|
bs = BuildSelector(build_config=sel, skip_config="")
|
||||||
|
hits += Counter(i for i in self.identifiers if bs(i))
|
||||||
|
|
||||||
|
non_unique_identifers = {idnt for idnt, count in hits.items() if count > 1}
|
||||||
|
if non_unique_identifers:
|
||||||
|
msg = "cibuildwheel: error, the windows/macOS selectors must match uniquely"
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
for sel in self.build_options_by_selector:
|
||||||
|
bs = BuildSelector(build_config=sel, skip_config="")
|
||||||
|
for i in non_unique_identifers:
|
||||||
|
if bs(i):
|
||||||
|
print(f" {sel}: {i} (nonunique match)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
results = []
|
||||||
|
for option in sorted(self.general_build_options._asdict().keys()):
|
||||||
|
variations = {
|
||||||
|
key: value._asdict()[option]
|
||||||
|
for key, value in self.build_options_by_selector.items()
|
||||||
|
}
|
||||||
|
variations["*"] = self.general_build_options._asdict()[option]
|
||||||
|
if len({repr(v) for v in variations.values()}) == 1:
|
||||||
|
results.append(f"{option}: {variations['*']!r}")
|
||||||
|
else:
|
||||||
|
results.append(f"{option}:")
|
||||||
|
for key, value in sorted(variations.items()):
|
||||||
|
results.append(f" {key}: {value!r}")
|
||||||
|
|
||||||
|
return "\n".join(results)
|
||||||
|
|
||||||
|
|
||||||
class NonPlatformWheelError(Exception):
|
class NonPlatformWheelError(Exception):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
message = textwrap.dedent(
|
message = textwrap.dedent(
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from .environment import ParsedEnvironment
|
|||||||
from .logger import log
|
from .logger import log
|
||||||
from .typing import PathOrStr, assert_never
|
from .typing import PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
|
AllBuildOptions,
|
||||||
BuildFrontend,
|
BuildFrontend,
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
download,
|
download,
|
||||||
@@ -246,25 +246,28 @@ def setup_python(
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(all_options: AllBuildOptions) -> None:
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
||||||
built_wheel_dir = temp_dir / "built_wheel"
|
built_wheel_dir = temp_dir / "built_wheel"
|
||||||
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
||||||
|
|
||||||
|
all_options.check_build_selectors()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if options.before_all:
|
if all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
env = options.environment.as_dictionary(prev_environment=os.environ)
|
env = all_options.environment.as_dictionary(prev_environment=os.environ)
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all, project=".", package=options.package_dir
|
all_options.before_all, project=".", package=all_options.package_dir
|
||||||
)
|
)
|
||||||
shell(before_all_prepared, env=env)
|
shell(before_all_prepared, env=env)
|
||||||
|
|
||||||
python_configurations = get_python_configurations(
|
python_configurations = get_python_configurations(
|
||||||
options.build_selector, options.architectures
|
all_options.build_selector, all_options.architectures
|
||||||
)
|
)
|
||||||
|
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
|
options = all_options[config.identifier]
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
|
||||||
dependency_constraint_flags: Sequence[PathOrStr] = []
|
dependency_constraint_flags: Sequence[PathOrStr] = []
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ install_requires =
|
|||||||
certifi
|
certifi
|
||||||
packaging
|
packaging
|
||||||
tomli
|
tomli
|
||||||
|
dataclasses;python_version < '3.7'
|
||||||
typing_extensions;python_version < '3.8'
|
typing_extensions;python_version < '3.8'
|
||||||
python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from cibuildwheel.__main__ import get_build_identifiers
|
||||||
|
from cibuildwheel.environment import parse_environment
|
||||||
|
from cibuildwheel.options import _get_pinned_docker_images, compute_options
|
||||||
|
from cibuildwheel.util import AllBuildOptions
|
||||||
|
|
||||||
|
PYPROJECT_1 = """
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
environment = {FOO="BAR"}
|
||||||
|
|
||||||
|
test-command = "pyproject"
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
test-requires = "else"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_build_options_1(tmp_path):
|
||||||
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
|
all_build_options, build_options_by_selector = compute_options(
|
||||||
|
"linux", tmp_path, Path("dist"), None, None, False
|
||||||
|
)
|
||||||
|
|
||||||
|
identifiers = get_build_identifiers(
|
||||||
|
"linux", all_build_options.build_selector, all_build_options.architectures
|
||||||
|
)
|
||||||
|
|
||||||
|
build_options = AllBuildOptions(all_build_options, build_options_by_selector, identifiers)
|
||||||
|
|
||||||
|
override_display = """\
|
||||||
|
test_command:
|
||||||
|
*: 'pyproject'
|
||||||
|
cp37*: 'pyproject-override'"""
|
||||||
|
|
||||||
|
assert override_display in str(build_options)
|
||||||
|
|
||||||
|
assert build_options.environment == parse_environment('FOO="BAR"')
|
||||||
|
|
||||||
|
all_pinned_docker_images = _get_pinned_docker_images()
|
||||||
|
pinned_x86_64_docker_image = all_pinned_docker_images["x86_64"]
|
||||||
|
|
||||||
|
local = build_options["cp38-manylinux_x86_64"]
|
||||||
|
assert local.manylinux_images is not None
|
||||||
|
assert local.test_command == "pyproject"
|
||||||
|
assert local.manylinux_images["x86_64"] == pinned_x86_64_docker_image["manylinux1"]
|
||||||
|
|
||||||
|
local = build_options["cp37-manylinux_x86_64"]
|
||||||
|
assert local.manylinux_images is not None
|
||||||
|
assert local.test_command == "pyproject-override"
|
||||||
|
assert local.manylinux_images["x86_64"] == pinned_x86_64_docker_image["manylinux2014"]
|
||||||
@@ -18,13 +18,13 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch):
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
|
assert intercepted_build_args.args[0].general_build_options.output_dir == OUTPUT_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
|
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == Path("wheelhouse")
|
assert intercepted_build_args.args[0].general_build_options.output_dir == Path("wheelhouse")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("also_set_environment", [False, True])
|
@pytest.mark.parametrize("also_set_environment", [False, True])
|
||||||
@@ -37,7 +37,7 @@ def test_output_dir_argument(also_set_environment, platform, intercepted_build_a
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
|
assert intercepted_build_args.args[0].general_build_options.output_dir == OUTPUT_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_empty):
|
def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_empty):
|
||||||
@@ -49,7 +49,7 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_emp
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
intercepted_build_selector = intercepted_build_args.args[0].general_build_options.build_selector
|
||||||
assert isinstance(intercepted_build_selector, BuildSelector)
|
assert isinstance(intercepted_build_selector, BuildSelector)
|
||||||
assert intercepted_build_selector("build24-this")
|
assert intercepted_build_selector("build24-this")
|
||||||
assert not intercepted_build_selector("skip65-that")
|
assert not intercepted_build_selector("skip65-that")
|
||||||
@@ -98,9 +98,12 @@ def test_manylinux_images(
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert fnmatch(intercepted_build_args.args[0].manylinux_images[architecture], full_image)
|
assert fnmatch(
|
||||||
|
intercepted_build_args.args[0].general_build_options.manylinux_images[architecture],
|
||||||
|
full_image,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert intercepted_build_args.args[0].manylinux_images is None
|
assert intercepted_build_args.args[0].general_build_options.manylinux_images is None
|
||||||
|
|
||||||
|
|
||||||
def get_default_repair_command(platform):
|
def get_default_repair_command(platform):
|
||||||
@@ -129,7 +132,7 @@ def test_repair_command(
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
expected_repair = repair_command or get_default_repair_command(platform)
|
expected_repair = repair_command or get_default_repair_command(platform)
|
||||||
assert intercepted_build_args.args[0].repair_command == expected_repair
|
assert intercepted_build_args.args[0].general_build_options.repair_command == expected_repair
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -147,7 +150,7 @@ def test_environment(environment, platform_specific, platform, intercepted_build
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_environment = intercepted_build_args.args[0].environment
|
intercepted_environment = intercepted_build_args.args[0].general_build_options.environment
|
||||||
assert isinstance(intercepted_environment, ParsedEnvironment)
|
assert isinstance(intercepted_environment, ParsedEnvironment)
|
||||||
assert intercepted_environment.as_dictionary(prev_environment={}) == environment
|
assert intercepted_environment.as_dictionary(prev_environment={}) == environment
|
||||||
|
|
||||||
@@ -166,7 +169,10 @@ def test_test_requires(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_requires == (test_requires or "").split()
|
assert (
|
||||||
|
intercepted_build_args.args[0].general_build_options.test_requires
|
||||||
|
== (test_requires or "").split()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_extras", [None, "extras"])
|
@pytest.mark.parametrize("test_extras", [None, "extras"])
|
||||||
@@ -181,7 +187,7 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_extras == (
|
assert intercepted_build_args.args[0].general_build_options.test_extras == (
|
||||||
"[" + test_extras + "]" if test_extras else ""
|
"[" + test_extras + "]" if test_extras else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -200,7 +206,7 @@ def test_test_command(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_command == (test_command or "")
|
assert intercepted_build_args.args[0].general_build_options.test_command == (test_command or "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
||||||
@@ -217,7 +223,7 @@ def test_before_build(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_build == (before_build or "")
|
assert intercepted_build_args.args[0].general_build_options.before_build == (before_build or "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("build_verbosity", [None, 0, 2, -2, 4, -4])
|
@pytest.mark.parametrize("build_verbosity", [None, 0, 2, -2, 4, -4])
|
||||||
@@ -235,7 +241,9 @@ def test_build_verbosity(
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
expected_verbosity = max(-3, min(3, int(build_verbosity or 0)))
|
expected_verbosity = max(-3, min(3, int(build_verbosity or 0)))
|
||||||
assert intercepted_build_args.args[0].build_verbosity == expected_verbosity
|
assert (
|
||||||
|
intercepted_build_args.args[0].general_build_options.build_verbosity == expected_verbosity
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -286,4 +294,4 @@ def test_before_all(before_all, platform_specific, platform, intercepted_build_a
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_all == (before_all or "")
|
assert intercepted_build_args.args[0].general_build_options.before_all == (before_all or "")
|
||||||
|
|||||||
@@ -246,3 +246,48 @@ def test_dig_first(ignore_empty):
|
|||||||
(d4, "other"),
|
(d4, "other"),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PYPROJECT_2 = """
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
environment = {FOO="BAR"}
|
||||||
|
|
||||||
|
test-command = "pyproject"
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
test-requires = "else"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyproject_2(tmp_path, platform):
|
||||||
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
|
f.write(PYPROJECT_2)
|
||||||
|
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
assert options("test-command") == "pyproject"
|
||||||
|
assert options.override("random")("test-command") == "pyproject"
|
||||||
|
assert options.override("cp37*")("test-command") == "pyproject-override"
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_not_a_list(tmp_path, platform):
|
||||||
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
|
f.write(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
[tool.cibuildwheel.overrides]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
ConfigOptions(tmp_path, platform=platform)
|
||||||
|
|||||||
Reference in New Issue
Block a user