Files
cibuildwheel/cibuildwheel/options.py
T

763 lines
27 KiB
Python
Raw Normal View History

from __future__ import annotations
2022-11-26 15:54:08 +00:00
import collections
2022-10-07 08:47:31 -04:00
import configparser
import contextlib
import dataclasses
import difflib
import functools
2022-09-06 00:56:20 -04:00
import shlex
2021-09-19 00:19:28 -04:00
import sys
2022-11-26 15:54:08 +00:00
import textwrap
2021-09-19 00:19:28 -04:00
import traceback
2023-04-18 12:38:21 -04:00
import typing
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Set
2021-06-21 12:26:46 -04:00
from pathlib import Path
2023-04-18 12:38:21 -04:00
from typing import Any, Dict, List, Union
2021-06-21 12:26:46 -04:00
from packaging.specifiers import SpecifierSet
2021-06-21 12:26:46 -04:00
2023-04-18 23:29:54 -04:00
from ._compat import tomllib
2023-04-18 23:05:34 -04:00
from ._compat.typing import Literal, NotRequired, TypedDict
2021-09-19 00:19:28 -04:00
from .architecture import Architecture
2021-10-12 02:05:47 +01:00
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
2022-11-26 15:54:08 +00:00
from .logger import log
from .oci_container import ContainerEngine
from .projectfiles import get_requires_python_str
2023-04-18 23:05:34 -04:00
from .typing import PLATFORMS, PlatformName
2021-09-19 00:19:28 -04:00
from .util import (
MANYLINUX_ARCHS,
MUSLLINUX_ARCHS,
BuildFrontend,
BuildSelector,
DependencyConstraints,
TestSelector,
cached_property,
2022-04-27 12:31:42 -04:00
format_safe,
2021-09-19 00:19:28 -04:00
resources_dir,
2021-10-12 02:05:47 +01:00
selector_matches,
strtobool,
2021-10-12 02:05:47 +01:00
unwrap,
2021-09-19 00:19:28 -04:00
)
2021-06-21 12:26:46 -04:00
2021-10-12 02:05:47 +01:00
2022-10-07 08:47:31 -04:00
@dataclasses.dataclass
2021-10-12 02:05:47 +01:00
class CommandLineArguments:
2023-02-07 10:25:34 -05:00
platform: Literal["auto", "linux", "macos", "windows"] | None
archs: str | None
2022-04-27 17:08:46 -04:00
output_dir: Path
2022-09-09 08:34:47 -04:00
only: str | None
config_file: str
2022-04-26 22:21:27 -04:00
package_dir: Path
2021-10-12 02:05:47 +01:00
print_build_identifiers: bool
allow_empty: bool
prerelease_pythons: bool
2022-11-26 15:54:08 +00:00
@staticmethod
def defaults() -> CommandLineArguments:
return CommandLineArguments(
platform="auto",
allow_empty=False,
archs=None,
only=None,
config_file="",
output_dir=Path("wheelhouse"),
package_dir=Path("."),
prerelease_pythons=False,
print_build_identifiers=False,
)
2021-10-12 02:05:47 +01:00
2022-10-07 08:47:31 -04:00
@dataclasses.dataclass(frozen=True)
2022-04-28 09:19:27 -04:00
class GlobalOptions:
2021-10-12 02:05:47 +01:00
package_dir: Path
output_dir: Path
build_selector: BuildSelector
test_selector: TestSelector
architectures: set[Architecture]
container_engine: ContainerEngine
2021-10-12 02:05:47 +01:00
2022-10-07 08:47:31 -04:00
@dataclasses.dataclass(frozen=True)
2022-04-28 09:19:27 -04:00
class BuildOptions:
2021-10-12 02:05:47 +01:00
globals: GlobalOptions
environment: ParsedEnvironment
before_all: str
before_build: str | None
2021-10-12 02:05:47 +01:00
repair_command: str
manylinux_images: dict[str, str] | None
musllinux_images: dict[str, str] | None
dependency_constraints: DependencyConstraints | None
test_command: str | None
before_test: str | None
test_requires: list[str]
2021-10-12 02:05:47 +01:00
test_extras: str
build_verbosity: int
build_frontend: BuildFrontend | Literal["default"]
2022-09-06 00:56:20 -04:00
config_settings: str
2021-10-12 02:05:47 +01:00
@property
def package_dir(self) -> Path:
return self.globals.package_dir
@property
def output_dir(self) -> Path:
return self.globals.output_dir
@property
def build_selector(self) -> BuildSelector:
return self.globals.build_selector
@property
def test_selector(self) -> TestSelector:
return self.globals.test_selector
@property
def architectures(self) -> set[Architecture]:
2021-10-12 02:05:47 +01:00
return self.globals.architectures
Setting = Union[Dict[str, str], List[str], str, int]
2021-06-21 12:26:46 -04:00
2022-10-07 08:47:31 -04:00
@dataclasses.dataclass(frozen=True)
2022-04-28 09:19:27 -04:00
class Override:
2021-10-12 02:05:47 +01:00
select_pattern: str
options: dict[str, Setting]
2021-10-12 02:05:47 +01:00
MANYLINUX_OPTIONS = {f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS}
MUSLLINUX_OPTIONS = {f"musllinux-{build_platform}-image" for build_platform in MUSLLINUX_ARCHS}
DISALLOWED_OPTIONS = {
"linux": {"dependency-versions"},
"macos": MANYLINUX_OPTIONS | MUSLLINUX_OPTIONS,
"windows": MANYLINUX_OPTIONS | MUSLLINUX_OPTIONS,
}
2021-06-21 12:26:46 -04:00
class TableFmt(TypedDict):
item: str
sep: str
quote: NotRequired[Callable[[str], str]]
2021-06-21 12:26:46 -04:00
class ConfigOptionError(KeyError):
pass
def _dig_first(*pairs: tuple[Mapping[str, Setting], str], ignore_empty: bool = False) -> Setting:
2021-06-21 12:26:46 -04:00
"""
Return the first dict item that matches from pairs of dicts and keys.
Will throw a KeyError if missing.
2021-06-21 12:26:46 -04:00
_dig_first((dict1, "key1"), (dict2, "key2"), ...)
"""
if not pairs:
2022-09-05 13:11:46 -04:00
msg = "pairs cannot be empty"
raise ValueError(msg)
for dict_like, key in pairs:
if key in dict_like:
value = dict_like[key]
if ignore_empty and value == "": # noqa: PLC1901
continue
return value
last_key = pairs[-1][1]
raise KeyError(last_key)
2021-06-21 12:26:46 -04:00
2021-10-12 02:05:47 +01:00
class OptionsReader:
2021-06-21 12:26:46 -04:00
"""
Gets options from the environment, config or defaults, optionally scoped
by the platform.
Example:
2021-10-12 02:05:47 +01:00
>>> options_reader = OptionsReader(config_file, platform='macos')
>>> options_reader.get('cool-color')
2021-06-21 12:26:46 -04:00
This will return the value of CIBW_COOL_COLOR_MACOS if it exists,
otherwise the value of CIBW_COOL_COLOR, otherwise
'tool.cibuildwheel.macos.cool-color' or 'tool.cibuildwheel.cool-color'
2021-10-12 02:05:47 +01:00
from `config_file`, or from cibuildwheel/resources/defaults.toml. An
2021-06-21 12:26:46 -04:00
error is thrown if there are any unexpected keys or sections in
tool.cibuildwheel.
"""
def __init__(
self,
config_file_path: Path | None = None,
2021-06-21 12:26:46 -04:00
*,
2021-10-12 02:05:47 +01:00
platform: PlatformName,
2022-12-05 19:18:54 +00:00
env: Mapping[str, str],
2023-04-18 12:38:21 -04:00
disallow: Mapping[str, Set[str]] | None = None,
2021-06-21 12:26:46 -04:00
) -> None:
self.platform = platform
2022-12-05 19:18:54 +00:00
self.env = env
2021-06-21 12:26:46 -04:00
self.disallow = disallow or {}
# Open defaults.toml, loading both global and platform sections
defaults_path = resources_dir / "defaults.toml"
self.default_options, self.default_platform_options = self._load_file(defaults_path)
# Load the project config file
config_options: dict[str, Any] = {}
config_platform_options: dict[str, Any] = {}
2021-06-21 12:26:46 -04:00
2021-10-12 02:05:47 +01:00
if config_file_path is not None:
config_options, config_platform_options = self._load_file(config_file_path)
2021-06-21 12:26:46 -04:00
# Validate project config
2021-06-21 12:26:46 -04:00
for option_name in config_options:
self._validate_global_option(option_name)
2021-06-21 12:26:46 -04:00
for option_name in config_platform_options:
self._validate_platform_option(option_name)
2021-06-21 12:26:46 -04:00
self.config_options = config_options
self.config_platform_options = config_platform_options
self.overrides: list[Override] = []
self.current_identifier: str | None = None
2021-09-19 00:19:28 -04:00
2021-10-12 02:05:47 +01:00
config_overrides = self.config_options.get("overrides")
if config_overrides is not None:
if not isinstance(config_overrides, list):
2022-09-05 13:11:46 -04:00
msg = "'tool.cibuildwheel.overrides' must be a list"
raise ConfigOptionError(msg)
2021-10-12 02:05:47 +01:00
for config_override in config_overrides:
select = config_override.pop("select", None)
if not select:
2022-09-05 13:11:46 -04:00
msg = "'select' must be set in an override"
raise ConfigOptionError(msg)
2021-10-12 02:05:47 +01:00
if isinstance(select, list):
select = " ".join(select)
self.overrides.append(Override(select, config_override))
2021-09-19 00:19:28 -04:00
def _validate_global_option(self, name: str) -> None:
2021-06-21 12:26:46 -04:00
"""
Raises an error if an option with this name is not allowed in the
2021-06-21 12:26:46 -04:00
[tool.cibuildwheel] section of a config file.
"""
2021-09-19 00:19:28 -04:00
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
2021-06-21 12:26:46 -04:00
if name not in allowed_option_names:
msg = f"Option {name!r} not supported in a config file."
matches = difflib.get_close_matches(name, allowed_option_names, 1, 0.7)
if matches:
msg += f" Perhaps you meant {matches[0]!r}?"
raise ConfigOptionError(msg)
2021-06-21 12:26:46 -04:00
def _validate_platform_option(self, name: str) -> None:
2021-06-21 12:26:46 -04:00
"""
Raises an error if an option with this name is not allowed in the
2021-06-21 12:26:46 -04:00
[tool.cibuildwheel.<current-platform>] section of a config file.
"""
disallowed_platform_options = self.disallow.get(self.platform, set())
if name in disallowed_platform_options:
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
raise ConfigOptionError(msg)
2021-06-21 12:26:46 -04:00
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
if name not in allowed_option_names:
msg = f"Option {name!r} not supported in the {self.platform!r} section"
matches = difflib.get_close_matches(name, allowed_option_names, 1, 0.7)
if matches:
msg += f" Perhaps you meant {matches[0]!r}?"
raise ConfigOptionError(msg)
2021-06-21 12:26:46 -04:00
def _load_file(self, filename: Path) -> tuple[dict[str, Any], dict[str, Any]]:
2021-06-21 12:26:46 -04:00
"""
Load a toml file, returns global and platform as separate dicts.
"""
2021-10-15 11:30:16 -04:00
with filename.open("rb") as f:
2022-04-15 21:00:57 -07:00
config = tomllib.load(f)
2021-06-21 12:26:46 -04:00
global_options = config.get("tool", {}).get("cibuildwheel", {})
platform_options = global_options.get(self.platform, {})
return global_options, platform_options
2021-10-12 02:05:47 +01:00
@property
def active_config_overrides(self) -> list[Override]:
2021-10-12 02:05:47 +01:00
if self.current_identifier is None:
return []
return [
o for o in self.overrides if selector_matches(o.select_pattern, self.current_identifier)
]
2021-09-19 00:19:28 -04:00
2022-10-07 08:47:31 -04:00
@contextlib.contextmanager
def identifier(self, identifier: str | None) -> Generator[None, None, None]:
2021-10-12 02:05:47 +01:00
self.current_identifier = identifier
try:
yield
finally:
self.current_identifier = None
def get(
2021-06-21 12:26:46 -04:00
self,
name: str,
*,
env_plat: bool = True,
sep: str | None = None,
table: TableFmt | None = None,
ignore_empty: bool = False,
2021-06-21 12:26:46 -04:00
) -> str:
"""
Get and return the value for the named option from environment,
configuration file, or the default. If env_plat is False, then don't
accept platform versions of the environment variable. If this is an
array it will be merged with "sep" before returning. If it is a table,
it will be formatted with "table['item']" using {k} and {v} and merged
2022-09-06 00:56:20 -04:00
with "table['sep']". If sep is also given, it will be used for arrays
inside the table (must match table['sep']). Empty variables will not
override if ignore_empty is True.
2021-06-21 12:26:46 -04:00
"""
if name not in self.default_options and name not in self.default_platform_options:
msg = f"{name!r} must be in cibuildwheel/resources/defaults.toml file to be accessed."
raise ConfigOptionError(msg)
2021-06-21 12:26:46 -04:00
# Environment variable form
envvar = f"CIBW_{name.upper().replace('-', '_')}"
plat_envvar = f"{envvar}_{self.platform.upper()}"
2021-10-12 02:05:47 +01:00
# later overrides take precedence over earlier ones, so reverse the list
active_config_overrides = reversed(self.active_config_overrides)
2021-06-21 12:26:46 -04:00
# get the option from the environment, then the config file, then finally the default.
# platform-specific options are preferred, if they're allowed.
result = _dig_first(
2022-12-05 19:18:54 +00:00
(self.env if env_plat else {}, plat_envvar),
(self.env, envvar),
2021-10-12 02:05:47 +01:00
*[(o.options, name) for o in active_config_overrides],
2021-06-21 12:26:46 -04:00
(self.config_platform_options, name),
(self.config_options, name),
(self.default_platform_options, name),
(self.default_options, name),
ignore_empty=ignore_empty,
2021-06-21 12:26:46 -04:00
)
if isinstance(result, dict):
if table is None:
2022-09-05 13:11:46 -04:00
msg = f"{name!r} does not accept a table"
raise ConfigOptionError(msg)
2022-09-06 00:56:20 -04:00
return table["sep"].join(
item for k, v in result.items() for item in _inner_fmt(k, v, table)
2022-09-06 00:56:20 -04:00
)
if isinstance(result, list):
2021-06-21 12:26:46 -04:00
if sep is None:
2022-09-05 13:11:46 -04:00
msg = f"{name!r} does not accept a list"
raise ConfigOptionError(msg)
2021-06-21 12:26:46 -04:00
return sep.join(result)
if isinstance(result, int):
2021-06-21 12:26:46 -04:00
return str(result)
return result
2021-09-19 00:19:28 -04:00
def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
quote_function = table.get("quote", lambda a: a)
2022-09-06 00:56:20 -04:00
if isinstance(v, list):
for inner_v in v:
qv = quote_function(inner_v)
yield table["item"].format(k=k, v=qv)
2022-09-06 00:56:20 -04:00
else:
qv = quote_function(v)
yield table["item"].format(k=k, v=qv)
2022-09-06 00:56:20 -04:00
2021-10-12 02:05:47 +01:00
class Options:
2022-11-26 15:54:08 +00:00
def __init__(
self,
platform: PlatformName,
command_line_arguments: CommandLineArguments,
2022-12-05 19:18:54 +00:00
env: Mapping[str, str],
2022-11-26 15:54:08 +00:00
read_config_file: bool = True,
):
2021-10-12 02:05:47 +01:00
self.platform = platform
self.command_line_arguments = command_line_arguments
2022-12-05 19:18:54 +00:00
self.env = env
2021-10-12 02:05:47 +01:00
self.reader = OptionsReader(
2022-11-26 15:54:08 +00:00
self.config_file_path if read_config_file else None,
2021-10-12 02:05:47 +01:00
platform=platform,
2022-12-05 19:18:54 +00:00
env=env,
2021-10-12 02:05:47 +01:00
disallow=DISALLOWED_OPTIONS,
)
@property
def config_file_path(self) -> Path | None:
2021-10-12 02:05:47 +01:00
args = self.command_line_arguments
if args.config_file:
2022-04-27 12:31:42 -04:00
return Path(format_safe(args.config_file, package=args.package_dir))
2021-10-12 02:05:47 +01:00
# return pyproject.toml, if it's available
pyproject_toml_path = Path(args.package_dir) / "pyproject.toml"
if pyproject_toml_path.exists():
return pyproject_toml_path
return None
@cached_property
def package_requires_python_str(self) -> str | None:
args = self.command_line_arguments
return get_requires_python_str(Path(args.package_dir))
2021-10-12 02:05:47 +01:00
@property
def globals(self) -> GlobalOptions:
args = self.command_line_arguments
2022-04-26 22:21:27 -04:00
package_dir = args.package_dir
output_dir = args.output_dir
2021-10-12 02:05:47 +01:00
build_config = self.reader.get("build", env_plat=False, sep=" ") or "*"
skip_config = self.reader.get("skip", env_plat=False, sep=" ")
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
prerelease_pythons = args.prerelease_pythons or strtobool(
2022-12-05 19:18:54 +00:00
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
2021-10-12 02:05:47 +01:00
)
# This is not supported in tool.cibuildwheel, as it comes from a standard location.
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
requires_python_str: str | None = (
2022-12-05 19:18:54 +00:00
self.env.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str
2021-10-12 02:05:47 +01:00
)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
2022-09-09 08:34:47 -04:00
archs_config_str = args.archs or self.reader.get("archs", sep=" ")
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
# Process `--only`
if args.only:
build_config = args.only
skip_config = ""
architectures = Architecture.all_archs(self.platform)
2021-10-12 02:05:47 +01:00
build_selector = BuildSelector(
build_config=build_config,
skip_config=skip_config,
requires_python=requires_python,
prerelease_pythons=prerelease_pythons,
)
test_selector = TestSelector(skip_config=test_skip)
container_engine_str = self.reader.get("container-engine")
if container_engine_str not in ["docker", "podman"]:
msg = f"cibuildwheel: Unrecognised container_engine {container_engine_str!r}, only 'docker' and 'podman' are supported"
print(msg, file=sys.stderr)
sys.exit(2)
2023-04-18 12:38:21 -04:00
container_engine = typing.cast(ContainerEngine, container_engine_str)
2021-10-12 02:05:47 +01:00
return GlobalOptions(
package_dir=package_dir,
output_dir=output_dir,
build_selector=build_selector,
test_selector=test_selector,
architectures=architectures,
container_engine=container_engine,
2021-10-12 02:05:47 +01:00
)
def build_options(self, identifier: str | None) -> BuildOptions:
2021-10-12 02:05:47 +01:00
"""
Compute BuildOptions for a single run configuration.
"""
with self.reader.identifier(identifier):
before_all = self.reader.get("before-all", sep=" && ")
build_frontend_str = self.reader.get("build-frontend", env_plat=False)
environment_config = self.reader.get(
"environment", table={"item": '{k}="{v}"', "sep": " "}
2021-10-12 02:05:47 +01:00
)
2021-11-21 15:19:45 -05:00
environment_pass = self.reader.get("environment-pass", sep=" ").split()
2021-10-12 02:05:47 +01:00
before_build = self.reader.get("before-build", sep=" && ")
repair_command = self.reader.get("repair-wheel-command", sep=" && ")
2022-09-06 00:56:20 -04:00
config_settings = self.reader.get(
"config-settings", table={"item": "{k}={v}", "sep": " ", "quote": shlex.quote}
2022-09-06 00:56:20 -04:00
)
2021-10-12 02:05:47 +01:00
dependency_versions = self.reader.get("dependency-versions")
test_command = self.reader.get("test-command", sep=" && ")
before_test = self.reader.get("before-test", sep=" && ")
test_requires = self.reader.get("test-requires", sep=" ").split()
test_extras = self.reader.get("test-extras", sep=",")
build_verbosity_str = self.reader.get("build-verbosity")
build_frontend: BuildFrontend | Literal["default"]
2021-10-12 02:05:47 +01:00
if build_frontend_str == "build":
build_frontend = "build"
elif build_frontend_str == "pip":
build_frontend = "pip"
elif build_frontend_str == "default":
build_frontend = "default"
2021-10-12 02:05:47 +01:00
else:
msg = f"cibuildwheel: Unrecognised build frontend {build_frontend_str!r}, only 'pip' and 'build' are supported"
2021-10-12 02:05:47 +01:00
print(msg, file=sys.stderr)
sys.exit(2)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print(
f"cibuildwheel: Malformed environment option {environment_config!r}",
2021-10-12 02:05:47 +01:00
file=sys.stderr,
)
traceback.print_exc(None, sys.stderr)
sys.exit(2)
2021-11-21 15:19:45 -05:00
# Pass through environment variables
if self.platform == "linux":
for env_var_name in environment_pass:
2022-10-07 08:47:31 -04:00
with contextlib.suppress(KeyError):
2022-12-05 19:18:54 +00:00
environment.add(env_var_name, self.env[env_var_name])
2021-11-21 15:19:45 -05:00
2021-10-12 02:05:47 +01:00
if dependency_versions == "pinned":
dependency_constraints: None | (
2021-10-12 02:05:47 +01:00
DependencyConstraints
) = DependencyConstraints.with_defaults()
2021-10-12 02:05:47 +01:00
elif dependency_versions == "latest":
dependency_constraints = None
else:
dependency_versions_path = Path(dependency_versions)
dependency_constraints = DependencyConstraints(dependency_versions_path)
if test_extras:
test_extras = f"[{test_extras}]"
try:
build_verbosity = min(3, max(-3, int(build_verbosity_str)))
except ValueError:
build_verbosity = 0
manylinux_images: dict[str, str] = {}
musllinux_images: dict[str, str] = {}
2021-10-12 02:05:47 +01:00
if self.platform == "linux":
all_pinned_container_images = _get_pinned_container_images()
2021-10-12 02:05:47 +01:00
for build_platform in MANYLINUX_ARCHS:
pinned_images = all_pinned_container_images[build_platform]
2021-10-12 02:05:47 +01:00
config_value = self.reader.get(
f"manylinux-{build_platform}-image", ignore_empty=True
)
if not config_value:
2021-11-18 17:37:08 +02:00
# default to manylinux2014
image = pinned_images["manylinux2014"]
2021-10-12 02:05:47 +01:00
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
manylinux_images[build_platform] = image
for build_platform in MUSLLINUX_ARCHS:
pinned_images = all_pinned_container_images[build_platform]
2021-10-12 02:05:47 +01:00
config_value = self.reader.get(f"musllinux-{build_platform}-image")
if not config_value:
2021-10-12 02:05:47 +01:00
image = pinned_images["musllinux_1_1"]
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
musllinux_images[build_platform] = image
return BuildOptions(
globals=self.globals,
test_command=test_command,
test_requires=test_requires,
test_extras=test_extras,
before_test=before_test,
before_build=before_build,
before_all=before_all,
build_verbosity=build_verbosity,
repair_command=repair_command,
environment=environment,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images or None,
musllinux_images=musllinux_images or None,
build_frontend=build_frontend,
2022-09-06 00:56:20 -04:00
config_settings=config_settings,
2021-10-12 02:05:47 +01:00
)
def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None:
2022-10-07 08:47:31 -04:00
if self.platform in {"macos", "windows"}:
2021-10-12 02:05:47 +01:00
before_all_values = {self.build_options(i).before_all for i in identifiers}
if len(before_all_values) > 1:
raise ValueError(
unwrap(
f"""
before_all cannot be set to multiple values. On macOS and Windows,
before_all is only run once, at the start of the build. before_all values
are: {before_all_values!r}
"""
)
)
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)
2022-11-26 15:54:08 +00:00
@cached_property
def defaults(self) -> Options:
return Options(
platform=self.platform,
command_line_arguments=CommandLineArguments.defaults(),
2022-12-05 19:18:54 +00:00
env={},
2022-11-26 15:54:08 +00:00
read_config_file=False,
)
2021-10-12 02:05:47 +01:00
def summary(self, identifiers: Iterable[str]) -> str:
2022-11-26 15:54:08 +00:00
lines = []
global_option_names = sorted(f.name for f in dataclasses.fields(self.globals))
for option_name in global_option_names:
option_value = getattr(self.globals, option_name)
default_value = getattr(self.defaults.globals, option_name)
lines.append(self.option_summary(option_name, option_value, default_value))
build_options = self.build_options(identifier=None)
build_options_defaults = self.defaults.build_options(identifier=None)
2022-09-09 08:34:47 -04:00
build_options_for_identifier = {
identifier: self.build_options(identifier) for identifier in identifiers
}
2021-10-12 02:05:47 +01:00
2022-11-26 15:54:08 +00:00
build_option_names = sorted(f.name for f in dataclasses.fields(build_options))
for option_name in build_option_names:
2021-10-12 02:05:47 +01:00
if option_name == "globals":
continue
2022-11-26 15:54:08 +00:00
option_value = getattr(build_options, option_name)
default_value = getattr(build_options_defaults, option_name)
overrides = {
i: getattr(build_options_for_identifier[i], option_name) for i in identifiers
}
2021-10-12 02:05:47 +01:00
2022-11-26 15:54:08 +00:00
lines.append(
self.option_summary(option_name, option_value, default_value, overrides=overrides)
)
2021-10-12 02:05:47 +01:00
return "\n".join(lines)
2022-11-26 15:54:08 +00:00
def option_summary(
self,
option_name: str,
option_value: Any,
default_value: Any,
overrides: Mapping[str, Any] | None = None,
2022-11-26 15:54:08 +00:00
) -> str:
2022-12-04 13:51:56 +00:00
"""
Return a summary of the option value, including any overrides, with
ANSI 'dim' color if it's the default.
"""
2022-11-26 15:54:08 +00:00
value_str = self.option_summary_value(option_value)
default_value_str = self.option_summary_value(default_value)
overrides_value_strs = {
k: self.option_summary_value(v) for k, v in (overrides or {}).items()
}
# if the override value is the same as the non-overridden value, don't print it
overrides_value_strs = {k: v for k, v in overrides_value_strs.items() if v != value_str}
has_been_set = (value_str != default_value_str) or overrides_value_strs
c = log.colors
2022-12-05 19:04:35 +00:00
result = c.gray if not has_been_set else ""
2022-11-26 15:54:08 +00:00
result += f"{option_name}: "
if overrides_value_strs:
overrides_groups = collections.defaultdict(list)
for k, v in overrides_value_strs.items():
overrides_groups[v].append(k)
result += "\n *: "
result += self.indent_if_multiline(value_str, " ")
for override_value_str, identifiers in overrides_groups.items():
result += f"\n {', '.join(identifiers)}: "
result += self.indent_if_multiline(override_value_str, " ")
else:
result += self.indent_if_multiline(value_str, " ")
result += c.end
return result
def indent_if_multiline(self, value: str, indent: str) -> str:
if "\n" in value:
return "\n" + textwrap.indent(value.strip(), indent)
else:
return value
def option_summary_value(self, option_value: Any) -> str:
if hasattr(option_value, "options_summary"):
option_value = option_value.options_summary()
if isinstance(option_value, list):
return "".join(f"{el}\n" for el in option_value)
if isinstance(option_value, set):
return ", ".join(str(el) for el in sorted(option_value))
if isinstance(option_value, dict):
return "".join(f"{k}: {v}\n" for k, v in option_value.items())
return str(option_value)
2021-10-12 02:05:47 +01:00
2021-09-19 00:19:28 -04:00
def compute_options(
platform: PlatformName,
2021-10-12 02:05:47 +01:00
command_line_arguments: CommandLineArguments,
2022-12-05 19:18:54 +00:00
env: Mapping[str, str],
2021-10-12 02:05:47 +01:00
) -> Options:
2022-12-05 19:18:54 +00:00
options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
2021-10-12 02:05:47 +01:00
options.check_for_deprecated_options()
return options
@functools.lru_cache(maxsize=None)
def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
2021-09-19 00:19:28 -04:00
"""
This looks like a dict of dicts, e.g.
{ 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
'pypy_x86_64': {'manylinux2010': '...' }
... }
"""
pinned_images_file = resources_dir / "pinned_docker_images.cfg"
2022-10-07 08:47:31 -04:00
all_pinned_images = configparser.ConfigParser()
all_pinned_images.read(pinned_images_file)
return all_pinned_images
2021-09-19 00:19:28 -04:00
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
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}"
print(msg, file=sys.stderr)
if error:
sys.exit(4)