Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions bin/generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@
description: cibuildwheel's settings.
type: object
properties:
inherit:
type: object
additionalProperties: false
properties:
audit-command: {"$ref": "#/$defs/inherit"}
audit-requires: {"$ref": "#/$defs/inherit"}
before-all: {"$ref": "#/$defs/inherit"}
before-build: {"$ref": "#/$defs/inherit"}
xbuild-tools: {"$ref": "#/$defs/inherit"}
xbuild-files: {"$ref": "#/$defs/inherit"}
before-test: {"$ref": "#/$defs/inherit"}
config-settings: {"$ref": "#/$defs/inherit"}
container-engine: {"$ref": "#/$defs/inherit"}
environment: {"$ref": "#/$defs/inherit"}
environment-pass: {"$ref": "#/$defs/inherit"}
repair-wheel-command: {"$ref": "#/$defs/inherit"}
test-command: {"$ref": "#/$defs/inherit"}
test-extras: {"$ref": "#/$defs/inherit"}
test-sources: {"$ref": "#/$defs/inherit"}
test-requires: {"$ref": "#/$defs/inherit"}
test-environment: {"$ref": "#/$defs/inherit"}
test-runtime: {"$ref": "#/$defs/inherit"}
audit-command:
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
type: string_array
Expand Down Expand Up @@ -315,28 +337,6 @@
additionalProperties: false
properties:
select: {}
inherit:
type: object
additionalProperties: false
properties:
audit-command: {"$ref": "#/$defs/inherit"}
audit-requires: {"$ref": "#/$defs/inherit"}
before-all: {"$ref": "#/$defs/inherit"}
before-build: {"$ref": "#/$defs/inherit"}
xbuild-tools: {"$ref": "#/$defs/inherit"}
xbuild-files: {"$ref": "#/$defs/inherit"}
before-test: {"$ref": "#/$defs/inherit"}
config-settings: {"$ref": "#/$defs/inherit"}
container-engine: {"$ref": "#/$defs/inherit"}
environment: {"$ref": "#/$defs/inherit"}
environment-pass: {"$ref": "#/$defs/inherit"}
repair-wheel-command: {"$ref": "#/$defs/inherit"}
test-command: {"$ref": "#/$defs/inherit"}
test-extras: {"$ref": "#/$defs/inherit"}
test-sources: {"$ref": "#/$defs/inherit"}
test-requires: {"$ref": "#/$defs/inherit"}
test-environment: {"$ref": "#/$defs/inherit"}
test-runtime: {"$ref": "#/$defs/inherit"}
"""
)

Expand Down
162 changes: 119 additions & 43 deletions cibuildwheel/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@
from cibuildwheel.selector import BuildSelector, EnableGroup, TestSelector, selector_matches
from cibuildwheel.typing import PLATFORMS, PlatformName
from cibuildwheel.util import resources
from cibuildwheel.util.helpers import format_safe, parse_key_value_string, strtobool, unwrap
from cibuildwheel.util.helpers import (
format_safe,
parse_arbitrary_key_value_string,
parse_key_value_string,
strtobool,
unwrap,
)
from cibuildwheel.util.packaging import DependencyConstraints

TYPE_CHECKING = False
Expand Down Expand Up @@ -436,6 +442,28 @@ def _stringify_setting(
return setting


def parse_inherit(config: str | dict[str, str] | None) -> dict[str, InheritRule]:
inherit_dict: dict[str, str]

if config is None:
return {}

if isinstance(config, str):
parsed = parse_arbitrary_key_value_string(config, default_value="append")
inherit_dict = {k: "".join(v) for k, v in parsed.items()}
elif isinstance(config, dict):
inherit_dict = config
else:
msg = "'inherit' must be a string or a table"
raise OptionsReaderError(msg)

if not all(v in {"none", "append", "prepend"} for v in inherit_dict.values()):
msg = "'inherit' must contain only {'none', 'append', 'prepend'} values"
raise OptionsReaderError(msg)

return {k: InheritRule[v.upper()] for k, v in inherit_dict.items()}


class OptionsReader:
"""
Gets options from the environment, config or defaults, optionally scoped
Expand Down Expand Up @@ -484,45 +512,18 @@ def __init__(
self._validate_platform_option(option_name)

self.config_options = config_options
self.config_options_inherit = parse_inherit(config_options.get("inherit"))
self.config_platform_options = config_platform_options
self.config_platform_options_inherit = parse_inherit(config_platform_options.get("inherit"))

self.overrides: list[Override] = []
self.current_identifier: str | None = None

config_overrides = self.config_options.get("overrides")

if config_overrides is not None:
if not isinstance(config_overrides, list):
msg = "'tool.cibuildwheel.overrides' must be a list"
raise OptionsReaderError(msg)

for config_override in config_overrides:
select = config_override.pop("select", None)

if not select:
msg = "'select' must be set in an override"
raise OptionsReaderError(msg)

if isinstance(select, list):
select = " ".join(select)

inherit = config_override.pop("inherit", {})
if not isinstance(inherit, dict) or not all(
i in {"none", "append", "prepend"} for i in inherit.values()
):
msg = "'inherit' must be a dict containing only {'none', 'append', 'prepend'} values"
raise OptionsReaderError(msg)

inherit_enum = {k: InheritRule[v.upper()] for k, v in inherit.items()}

self.overrides.append(Override(select, config_override, inherit_enum))

def _validate_global_option(self, name: str) -> None:
"""
Raises an error if an option with this name is not allowed in the
[tool.cibuildwheel] section of a config file.
"""
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
allowed_option_names = self.default_options.keys() | PLATFORMS | {"inherit", "overrides"}

if name not in allowed_option_names:
msg = f"Option {name!r} not supported in a config file."
Expand All @@ -541,7 +542,9 @@ def _validate_platform_option(self, name: str) -> None:
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
raise OptionsReaderError(msg)

allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
allowed_option_names = (
self.default_options.keys() | self.default_platform_options.keys() | {"inherit"}
)

if name not in allowed_option_names:
msg = f"Option {name!r} not supported in the {self.platform!r} section"
Expand All @@ -562,6 +565,56 @@ def _load_file(self, filename: Path) -> tuple[dict[str, Any], dict[str, Any]]:

return global_options, platform_options

@functools.cached_property
def overrides(self) -> list[Override]:
config_overrides = self.config_options.get("overrides")
overrides: list[Override] = []

if config_overrides is not None:
if not isinstance(config_overrides, list):
msg = "'tool.cibuildwheel.overrides' must be a list"
raise OptionsReaderError(msg)

for config_override in config_overrides:
select = config_override.pop("select", None)

if not select:
msg = "'select' must be set in an override"
raise OptionsReaderError(msg)

if isinstance(select, list):
select = " ".join(select)

inherit = config_override.pop("inherit", {})

overrides.append(Override(select, config_override, parse_inherit(inherit)))

return overrides

@functools.cached_property
def env_inherit(self) -> dict[str, InheritRule]:
env_inherit_str = self.env.get("CIBW_INHERIT", "")
try:
return parse_inherit(env_inherit_str)
except OptionsReaderError as e:
msg = f"Failed to parse CIBW_INHERIT environment variable. {e}"
raise errors.ConfigurationError(msg) from e

@functools.cached_property
def env_platform_inherit(self) -> dict[str, InheritRule]:
env_inherit = self.env_inherit

# find the rules which have -{platform} on the end of their key,
# remove the platform suffix from the key and return the resulting
# rule.
platform_suffix = f"-{self.platform}"

return {
key.removesuffix(platform_suffix): value
for key, value in env_inherit.items()
if key.endswith(platform_suffix)
}

@property
def active_config_overrides(self) -> list[Override]:
if self.current_identifier is None:
Expand All @@ -585,7 +638,7 @@ def get(
env_plat: bool = True,
option_format: OptionFormat | None = None,
ignore_empty: bool = False,
env_rule: InheritRule = InheritRule.NONE,
default_env_rule: InheritRule = InheritRule.NONE,
) -> str:
"""
Get and return the value for the named option from environment,
Expand All @@ -609,16 +662,37 @@ def get(
# get the option from the default, then the config file, then finally the environment.
# platform-specific options are preferred, if they're allowed.
return _resolve_cascade(
(self.default_options.get(name), InheritRule.NONE),
(self.default_platform_options.get(name), InheritRule.NONE),
(self.config_options.get(name), InheritRule.NONE),
(self.config_platform_options.get(name), InheritRule.NONE),
(
self.default_options.get(name),
InheritRule.NONE,
),
(
self.default_platform_options.get(name),
InheritRule.NONE,
),
(
self.config_options.get(name),
self.config_options_inherit.get(name, InheritRule.NONE),
),
(
self.config_platform_options.get(name),
self.config_platform_options_inherit.get(name, InheritRule.NONE),
),
*[
(o.options.get(name), o.inherit.get(name, InheritRule.NONE))
(
o.options.get(name),
o.inherit.get(name, InheritRule.NONE),
)
for o in self.active_config_overrides
],
(self.env.get(envvar), env_rule),
(self.env.get(plat_envvar) if env_plat else None, env_rule),
(
self.env.get(envvar),
self.env_inherit.get(name, default_env_rule),
),
(
self.env.get(plat_envvar) if env_plat else None,
self.env_platform_inherit.get(name, default_env_rule),
),
ignore_empty=ignore_empty,
option_format=option_format,
)
Expand Down Expand Up @@ -691,7 +765,10 @@ def globals(self) -> GlobalOptions:
allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))

enable_groups = self.reader.get(
"enable", env_plat=False, option_format=ListFormat(sep=" "), env_rule=InheritRule.APPEND
"enable",
env_plat=False,
option_format=ListFormat(sep=" "),
default_env_rule=InheritRule.APPEND,
)
try:
enable = {
Expand Down Expand Up @@ -797,12 +874,11 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions:
if xbuild_tools == ["\u0000"]:
xbuild_tools = None

xbuild_files = parse_key_value_string(
xbuild_files = parse_arbitrary_key_value_string(
self.reader.get(
"xbuild-files",
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
),
kw_arg_names=["*"],
)

test_sources = shlex.split(
Expand Down
Loading
Loading