feat: add inherit to override (#1730)
* feat: add inherit to override Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * refactor: use dict and support prepend Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Refactor: simplifying by splitting the responsibilities of _dig_first * Refactor to allow merging of string settings, and preserve table cascades * docs: add some docs for inherit Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Apply suggestions from code review Co-authored-by: Joe Rickerby <joerick@mac.com> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
Joe Rickerby
parent
ae2451a199
commit
8f58f71c2e
+31
-33
@@ -17,6 +17,14 @@ $schema: http://json-schema.org/draft-07/schema
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: cibuildwheel's settings.
|
description: cibuildwheel's settings.
|
||||||
type: object
|
type: object
|
||||||
|
defines:
|
||||||
|
inherit:
|
||||||
|
enum:
|
||||||
|
- none
|
||||||
|
- prepend
|
||||||
|
- append
|
||||||
|
default: none
|
||||||
|
description: How to inherit the parent's value.
|
||||||
properties:
|
properties:
|
||||||
archs:
|
archs:
|
||||||
description: Change the architectures built on your machine by default.
|
description: Change the architectures built on your machine by default.
|
||||||
@@ -153,9 +161,6 @@ properties:
|
|||||||
|
|
||||||
schema = yaml.safe_load(starter)
|
schema = yaml.safe_load(starter)
|
||||||
|
|
||||||
if args.schemastore:
|
|
||||||
schema["$id"] += "#"
|
|
||||||
|
|
||||||
string_array = yaml.safe_load(
|
string_array = yaml.safe_load(
|
||||||
"""
|
"""
|
||||||
- type: string
|
- type: string
|
||||||
@@ -214,19 +219,28 @@ items:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
properties:
|
properties:
|
||||||
select: {}
|
select: {}
|
||||||
|
inherit:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
before-all: {"$ref": "#/defines/inherit"}
|
||||||
|
before-build: {"$ref": "#/defines/inherit"}
|
||||||
|
before-test: {"$ref": "#/defines/inherit"}
|
||||||
|
config-settings: {"$ref": "#/defines/inherit"}
|
||||||
|
container-engine: {"$ref": "#/defines/inherit"}
|
||||||
|
environment: {"$ref": "#/defines/inherit"}
|
||||||
|
environment-pass: {"$ref": "#/defines/inherit"}
|
||||||
|
repair-wheel-command: {"$ref": "#/defines/inherit"}
|
||||||
|
test-command: {"$ref": "#/defines/inherit"}
|
||||||
|
test-extras: {"$ref": "#/defines/inherit"}
|
||||||
|
test-requires: {"$ref": "#/defines/inherit"}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for key, value in schema["properties"].items():
|
for key, value in schema["properties"].items():
|
||||||
value["title"] = f'CIBW_{key.replace("-", "_").upper()}'
|
value["title"] = f'CIBW_{key.replace("-", "_").upper()}'
|
||||||
|
|
||||||
if args.schemastore:
|
non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]}
|
||||||
non_global_options = {
|
|
||||||
k: {"$ref": f"#/properties/tool/properties/cibuildwheel/properties/{k}"}
|
|
||||||
for k in schema["properties"]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]}
|
|
||||||
del non_global_options["build"]
|
del non_global_options["build"]
|
||||||
del non_global_options["skip"]
|
del non_global_options["skip"]
|
||||||
del non_global_options["container-engine"]
|
del non_global_options["container-engine"]
|
||||||
@@ -273,27 +287,11 @@ del oses["linux"]["properties"]["dependency-versions"]
|
|||||||
schema["properties"]["overrides"] = overrides
|
schema["properties"]["overrides"] = overrides
|
||||||
schema["properties"] |= oses
|
schema["properties"] |= oses
|
||||||
|
|
||||||
if not args.schemastore:
|
if args.schemastore:
|
||||||
print(json.dumps(schema, indent=2))
|
schema["$id"] = "https://json.schemastore.org/partial-cibuildwheel.json"
|
||||||
raise SystemExit(0)
|
schema["$schema"] = "http://json-schema.org/draft-07/schema#"
|
||||||
|
schema[
|
||||||
|
"description"
|
||||||
|
] = "cibuildwheel's toml file, generated with ./bin/generate_schema.py --schemastore from cibuildwheel."
|
||||||
|
|
||||||
schema_store_txt = """
|
print(json.dumps(schema, indent=2))
|
||||||
$id: https://json.schemastore.org/cibuildwheel.json
|
|
||||||
$schema: http://json-schema.org/draft-07/schema#
|
|
||||||
additionalProperties: false
|
|
||||||
description: cibuildwheel's toml file, generated with ./bin/generate_schema.py --schemastore from cibuildwheel.
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
tool:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
cibuildwheel:
|
|
||||||
type: object
|
|
||||||
"""
|
|
||||||
schema_store = yaml.safe_load(schema_store_txt)
|
|
||||||
|
|
||||||
schema_store["properties"]["tool"]["properties"]["cibuildwheel"]["properties"] = schema[
|
|
||||||
"properties"
|
|
||||||
]
|
|
||||||
|
|
||||||
print(json.dumps(schema_store, indent=2))
|
|
||||||
|
|||||||
+150
-67
@@ -5,19 +5,20 @@ import configparser
|
|||||||
import contextlib
|
import contextlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import difflib
|
import difflib
|
||||||
|
import enum
|
||||||
import functools
|
import functools
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Set
|
from collections.abc import Callable, Generator, Iterable, Iterator, Set
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Literal, TypedDict, Union
|
from typing import Any, Literal, Mapping, Sequence, TypedDict, Union # noqa: TID251
|
||||||
|
|
||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
|
|
||||||
from ._compat import tomllib
|
from ._compat import tomllib
|
||||||
from ._compat.typing import NotRequired
|
from ._compat.typing import NotRequired, assert_never
|
||||||
from .architecture import Architecture
|
from .architecture import Architecture
|
||||||
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
||||||
from .logger import log
|
from .logger import log
|
||||||
@@ -116,13 +117,14 @@ class BuildOptions:
|
|||||||
return self.globals.architectures
|
return self.globals.architectures
|
||||||
|
|
||||||
|
|
||||||
Setting = Union[Dict[str, str], List[str], str, int]
|
Setting = Union[Mapping[str, str], Sequence[str], str, int]
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class Override:
|
class Override:
|
||||||
select_pattern: str
|
select_pattern: str
|
||||||
options: dict[str, Setting]
|
options: dict[str, Setting]
|
||||||
|
inherit: dict[str, InheritRule]
|
||||||
|
|
||||||
|
|
||||||
MANYLINUX_OPTIONS = {f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS}
|
MANYLINUX_OPTIONS = {f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS}
|
||||||
@@ -150,28 +152,115 @@ class ConfigOptionError(KeyError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _dig_first(*pairs: tuple[Mapping[str, Setting], str], ignore_empty: bool = False) -> Setting:
|
class InheritRule(enum.Enum):
|
||||||
"""
|
NONE = enum.auto()
|
||||||
Return the first dict item that matches from pairs of dicts and keys.
|
APPEND = enum.auto()
|
||||||
Will throw a KeyError if missing.
|
PREPEND = enum.auto()
|
||||||
|
|
||||||
_dig_first((dict1, "key1"), (dict2, "key2"), ...)
|
|
||||||
|
def _resolve_cascade(
|
||||||
|
*pairs: tuple[Setting | None, InheritRule],
|
||||||
|
ignore_empty: bool = False,
|
||||||
|
list_sep: str | None = None,
|
||||||
|
table_format: TableFmt | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Given a cascade of values with inherit rules, resolve them into a single
|
||||||
|
value.
|
||||||
|
|
||||||
|
'None' values mean that the option was not set at that level, and are
|
||||||
|
ignored. If `ignore_empty` is True, empty values are ignored too.
|
||||||
|
|
||||||
|
Values start with defaults, followed by more specific rules. If rules are
|
||||||
|
NONE, the last non-null value is returned. If a rule is APPEND or PREPEND,
|
||||||
|
the value is concatenated with the previous value.
|
||||||
|
|
||||||
|
The following idiom can be used to get the first matching value:
|
||||||
|
|
||||||
|
_resolve_cascade(("value1", Inherit.NONE), ("value2", Inherit.NONE), ...)))
|
||||||
"""
|
"""
|
||||||
if not pairs:
|
if not pairs:
|
||||||
msg = "pairs cannot be empty"
|
msg = "pairs cannot be empty"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
for dict_like, key in pairs:
|
result: str | None = None
|
||||||
if key in dict_like:
|
|
||||||
value = dict_like[key]
|
|
||||||
|
|
||||||
if ignore_empty and value == "":
|
if table_format is not None:
|
||||||
continue
|
merge_sep = table_format["sep"]
|
||||||
|
elif list_sep is not None:
|
||||||
|
merge_sep = list_sep
|
||||||
|
else:
|
||||||
|
merge_sep = None
|
||||||
|
|
||||||
return value
|
for value, rule in pairs:
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
last_key = pairs[-1][1]
|
if ignore_empty and not value:
|
||||||
raise KeyError(last_key)
|
continue
|
||||||
|
|
||||||
|
value_string = _stringify_setting(value, list_sep, table_format)
|
||||||
|
|
||||||
|
result = _merge_values(
|
||||||
|
result,
|
||||||
|
value_string,
|
||||||
|
rule=rule,
|
||||||
|
merge_sep=merge_sep,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
msg = "a setting should at least have a default value"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_values(before: str | None, after: str, rule: InheritRule, merge_sep: str | None) -> str:
|
||||||
|
if rule == InheritRule.NONE:
|
||||||
|
return after
|
||||||
|
|
||||||
|
if not before:
|
||||||
|
# if before is None, we can just return after
|
||||||
|
# if before is an empty string, we shouldn't add any separator
|
||||||
|
return after
|
||||||
|
|
||||||
|
if not after:
|
||||||
|
# if after is an empty string, we shouldn't add any separator
|
||||||
|
return before
|
||||||
|
|
||||||
|
if not merge_sep:
|
||||||
|
msg = f"Don't know how to merge {before!r} and {after!r} with {rule}"
|
||||||
|
raise ConfigOptionError(msg)
|
||||||
|
|
||||||
|
if rule == InheritRule.APPEND:
|
||||||
|
return f"{before}{merge_sep}{after}"
|
||||||
|
elif rule == InheritRule.PREPEND:
|
||||||
|
return f"{after}{merge_sep}{before}"
|
||||||
|
else:
|
||||||
|
assert_never(rule)
|
||||||
|
|
||||||
|
|
||||||
|
def _stringify_setting(
|
||||||
|
setting: Setting, list_sep: str | None, table_format: TableFmt | None
|
||||||
|
) -> str:
|
||||||
|
if isinstance(setting, Mapping):
|
||||||
|
if table_format is None:
|
||||||
|
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a table"
|
||||||
|
raise ConfigOptionError(msg)
|
||||||
|
return table_format["sep"].join(
|
||||||
|
item for k, v in setting.items() for item in _inner_fmt(k, v, table_format)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(setting, str) and isinstance(setting, Sequence):
|
||||||
|
if list_sep is None:
|
||||||
|
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list"
|
||||||
|
raise ConfigOptionError(msg)
|
||||||
|
return list_sep.join(setting)
|
||||||
|
|
||||||
|
if isinstance(setting, int):
|
||||||
|
return str(setting)
|
||||||
|
|
||||||
|
return setting
|
||||||
|
|
||||||
|
|
||||||
class OptionsReader:
|
class OptionsReader:
|
||||||
@@ -244,7 +333,16 @@ class OptionsReader:
|
|||||||
if isinstance(select, list):
|
if isinstance(select, list):
|
||||||
select = " ".join(select)
|
select = " ".join(select)
|
||||||
|
|
||||||
self.overrides.append(Override(select, config_override))
|
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 ConfigOptionError(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:
|
def _validate_global_option(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -312,8 +410,8 @@ class OptionsReader:
|
|||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
env_plat: bool = True,
|
env_plat: bool = True,
|
||||||
sep: str | None = None,
|
list_sep: str | None = None,
|
||||||
table: TableFmt | None = None,
|
table_format: TableFmt | None = None,
|
||||||
ignore_empty: bool = False,
|
ignore_empty: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -335,41 +433,24 @@ class OptionsReader:
|
|||||||
envvar = f"CIBW_{name.upper().replace('-', '_')}"
|
envvar = f"CIBW_{name.upper().replace('-', '_')}"
|
||||||
plat_envvar = f"{envvar}_{self.platform.upper()}"
|
plat_envvar = f"{envvar}_{self.platform.upper()}"
|
||||||
|
|
||||||
# later overrides take precedence over earlier ones, so reverse the list
|
# get the option from the default, then the config file, then finally the environment.
|
||||||
active_config_overrides = reversed(self.active_config_overrides)
|
|
||||||
|
|
||||||
# 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.
|
||||||
result = _dig_first(
|
return _resolve_cascade(
|
||||||
(self.env if env_plat else {}, plat_envvar),
|
(self.default_options.get(name), InheritRule.NONE),
|
||||||
(self.env, envvar),
|
(self.default_platform_options.get(name), InheritRule.NONE),
|
||||||
*[(o.options, name) for o in active_config_overrides],
|
(self.config_options.get(name), InheritRule.NONE),
|
||||||
(self.config_platform_options, name),
|
(self.config_platform_options.get(name), InheritRule.NONE),
|
||||||
(self.config_options, name),
|
*[
|
||||||
(self.default_platform_options, name),
|
(o.options.get(name), o.inherit.get(name, InheritRule.NONE))
|
||||||
(self.default_options, name),
|
for o in self.active_config_overrides
|
||||||
|
],
|
||||||
|
(self.env.get(envvar), InheritRule.NONE),
|
||||||
|
(self.env.get(plat_envvar) if env_plat else None, InheritRule.NONE),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
|
list_sep=list_sep,
|
||||||
|
table_format=table_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(result, dict):
|
|
||||||
if table is None:
|
|
||||||
msg = f"{name!r} does not accept a table"
|
|
||||||
raise ConfigOptionError(msg)
|
|
||||||
return table["sep"].join(
|
|
||||||
item for k, v in result.items() for item in _inner_fmt(k, v, table)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(result, list):
|
|
||||||
if sep is None:
|
|
||||||
msg = f"{name!r} does not accept a list"
|
|
||||||
raise ConfigOptionError(msg)
|
|
||||||
return sep.join(result)
|
|
||||||
|
|
||||||
if isinstance(result, int):
|
|
||||||
return str(result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
|
def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
|
||||||
quote_function = table.get("quote", lambda a: a)
|
quote_function = table.get("quote", lambda a: a)
|
||||||
@@ -430,9 +511,9 @@ class Options:
|
|||||||
package_dir = args.package_dir
|
package_dir = args.package_dir
|
||||||
output_dir = args.output_dir
|
output_dir = args.output_dir
|
||||||
|
|
||||||
build_config = self.reader.get("build", env_plat=False, sep=" ") or "*"
|
build_config = self.reader.get("build", env_plat=False, list_sep=" ") or "*"
|
||||||
skip_config = self.reader.get("skip", env_plat=False, sep=" ")
|
skip_config = self.reader.get("skip", env_plat=False, list_sep=" ")
|
||||||
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
|
test_skip = self.reader.get("test-skip", env_plat=False, list_sep=" ")
|
||||||
|
|
||||||
prerelease_pythons = args.prerelease_pythons or strtobool(
|
prerelease_pythons = args.prerelease_pythons or strtobool(
|
||||||
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
|
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
|
||||||
@@ -445,7 +526,7 @@ class Options:
|
|||||||
)
|
)
|
||||||
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
||||||
|
|
||||||
archs_config_str = args.archs or self.reader.get("archs", sep=" ")
|
archs_config_str = args.archs or self.reader.get("archs", list_sep=" ")
|
||||||
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
|
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
|
||||||
|
|
||||||
# Process `--only`
|
# Process `--only`
|
||||||
@@ -464,7 +545,8 @@ class Options:
|
|||||||
test_selector = TestSelector(skip_config=test_skip)
|
test_selector = TestSelector(skip_config=test_skip)
|
||||||
|
|
||||||
container_engine_str = self.reader.get(
|
container_engine_str = self.reader.get(
|
||||||
"container-engine", table={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote}
|
"container-engine",
|
||||||
|
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -489,29 +571,30 @@ class Options:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with self.reader.identifier(identifier):
|
with self.reader.identifier(identifier):
|
||||||
before_all = self.reader.get("before-all", sep=" && ")
|
before_all = self.reader.get("before-all", list_sep=" && ")
|
||||||
|
|
||||||
environment_config = self.reader.get(
|
environment_config = self.reader.get(
|
||||||
"environment", table={"item": '{k}="{v}"', "sep": " "}
|
"environment", table_format={"item": '{k}="{v}"', "sep": " "}
|
||||||
)
|
)
|
||||||
environment_pass = self.reader.get("environment-pass", sep=" ").split()
|
environment_pass = self.reader.get("environment-pass", list_sep=" ").split()
|
||||||
before_build = self.reader.get("before-build", sep=" && ")
|
before_build = self.reader.get("before-build", list_sep=" && ")
|
||||||
repair_command = self.reader.get("repair-wheel-command", sep=" && ")
|
repair_command = self.reader.get("repair-wheel-command", list_sep=" && ")
|
||||||
config_settings = self.reader.get(
|
config_settings = self.reader.get(
|
||||||
"config-settings", table={"item": "{k}={v}", "sep": " ", "quote": shlex.quote}
|
"config-settings",
|
||||||
|
table_format={"item": "{k}={v}", "sep": " ", "quote": shlex.quote},
|
||||||
)
|
)
|
||||||
|
|
||||||
dependency_versions = self.reader.get("dependency-versions")
|
dependency_versions = self.reader.get("dependency-versions")
|
||||||
test_command = self.reader.get("test-command", sep=" && ")
|
test_command = self.reader.get("test-command", list_sep=" && ")
|
||||||
before_test = self.reader.get("before-test", sep=" && ")
|
before_test = self.reader.get("before-test", list_sep=" && ")
|
||||||
test_requires = self.reader.get("test-requires", sep=" ").split()
|
test_requires = self.reader.get("test-requires", list_sep=" ").split()
|
||||||
test_extras = self.reader.get("test-extras", sep=",")
|
test_extras = self.reader.get("test-extras", list_sep=",")
|
||||||
build_verbosity_str = self.reader.get("build-verbosity")
|
build_verbosity_str = self.reader.get("build-verbosity")
|
||||||
|
|
||||||
build_frontend_str = self.reader.get(
|
build_frontend_str = self.reader.get(
|
||||||
"build-frontend",
|
"build-frontend",
|
||||||
env_plat=False,
|
env_plat=False,
|
||||||
table={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
|
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
|
||||||
)
|
)
|
||||||
build_frontend: BuildFrontendConfig | None
|
build_frontend: BuildFrontendConfig | None
|
||||||
if not build_frontend_str or build_frontend_str == "default":
|
if not build_frontend_str or build_frontend_str == "default":
|
||||||
|
|||||||
@@ -4,6 +4,17 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"description": "cibuildwheel's settings.",
|
"description": "cibuildwheel's settings.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"defines": {
|
||||||
|
"inherit": {
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"prepend",
|
||||||
|
"append"
|
||||||
|
],
|
||||||
|
"default": "none",
|
||||||
|
"description": "How to inherit the parent's value."
|
||||||
|
}
|
||||||
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"archs": {
|
"archs": {
|
||||||
"description": "Change the architectures built on your machine by default.",
|
"description": "Change the architectures built on your machine by default.",
|
||||||
@@ -423,6 +434,45 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"inherit": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"before-all": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"before-build": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"before-test": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"config-settings": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"container-engine": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"environment-pass": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"repair-wheel-command": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"test-command": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"test-extras": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
},
|
||||||
|
"test-requires": {
|
||||||
|
"$ref": "#/defines/inherit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"before-all": {
|
"before-all": {
|
||||||
"$ref": "#/properties/before-all"
|
"$ref": "#/properties/before-all"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ trigger new containers, one per image. Some commands are not supported;
|
|||||||
`output-dir`, build/skip/test_skip selectors, and architectures cannot be
|
`output-dir`, build/skip/test_skip selectors, and architectures cannot be
|
||||||
overridden.
|
overridden.
|
||||||
|
|
||||||
|
You can specify a table of overrides in `inherit={}`, any list or table in this
|
||||||
|
list will inherit from previous overrides or the main configuration. The valid
|
||||||
|
options are `"none"` (the default), `"append"`, and `"prepend"`.
|
||||||
|
|
||||||
##### Examples:
|
##### Examples:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -169,6 +173,69 @@ This example will build CPython 3.6 wheels on manylinux1, CPython 3.7-3.9
|
|||||||
wheels on manylinux2010, and manylinux2014 wheels for any newer Python
|
wheels on manylinux2010, and manylinux2014 wheels for any newer Python
|
||||||
(like 3.10).
|
(like 3.10).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
environment = {FOO="BAR", "HAM"="EGGS"}
|
||||||
|
test-command = ["pyproject"]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp311*"
|
||||||
|
|
||||||
|
inherit.test-command = "prepend"
|
||||||
|
test-command = ["pyproject-before"]
|
||||||
|
|
||||||
|
inherit.environment="append"
|
||||||
|
environment = {FOO="BAZ", "PYTHON"="MONTY"}
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp311*"
|
||||||
|
inherit.test-command = "append"
|
||||||
|
test-command = ["pyproject-after"]
|
||||||
|
```
|
||||||
|
|
||||||
|
This example will provide the command `"pyproject-before && pyproject && pyproject-after"`
|
||||||
|
on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"="EGGS"}`.
|
||||||
|
|
||||||
|
|
||||||
|
### Extending existing options {: #inherit }
|
||||||
|
|
||||||
|
In the TOML configuration, you can choose how tables and lists are inherited.
|
||||||
|
By default, all values are overridden completely (`"none"`) but sometimes you'd
|
||||||
|
rather `"append"` or `"prepend"` to an existing list or table. You can do this
|
||||||
|
with the `inherit` table in overrides. For example, if you want to add an environment
|
||||||
|
variable for CPython 3.11, without `inherit` you'd have to repeat all the
|
||||||
|
original environment variables in the override. With `inherit`, it's just:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp311*"
|
||||||
|
inherit.environment = "append"
|
||||||
|
environment.NEWVAR = "Added!"
|
||||||
|
```
|
||||||
|
|
||||||
|
For a table, `"append"` will replace a key if it exists, while `"prepend"` will
|
||||||
|
only add a new key, older keys take precedence.
|
||||||
|
|
||||||
|
Lists are also supported (and keep in mind that commands are lists). For
|
||||||
|
example, you can print a message before and after a wheel is repaired:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*"
|
||||||
|
inherit.repair-wheel-command = "prepend"
|
||||||
|
repair-wheel-command = "echo 'Before repair'"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*"
|
||||||
|
inherit.repair-wheel-command = "append"
|
||||||
|
repair-wheel-command = "echo 'After repair'"
|
||||||
|
```
|
||||||
|
|
||||||
|
As seen in this example, you can have multiple overrides match - they match top
|
||||||
|
to bottom, with the config being accumulated. If you need platform-specific
|
||||||
|
inheritance, you can use `select = "*-????linux_*"` for Linux, `select =
|
||||||
|
"*-win_*"` for Windows, and `select = "*-macosx_*"` for macOS. As always,
|
||||||
|
environment variables will completely override any TOML configuration.
|
||||||
|
|
||||||
## Options summary
|
## Options summary
|
||||||
|
|
||||||
|
|||||||
@@ -347,3 +347,71 @@ def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, res
|
|||||||
assert parsed_build_frontend.args == result_args
|
assert parsed_build_frontend.args == result_args
|
||||||
else:
|
else:
|
||||||
assert parsed_build_frontend is None
|
assert parsed_build_frontend is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_inherit_environment(tmp_path: Path):
|
||||||
|
args = CommandLineArguments.defaults()
|
||||||
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
environment = {FOO="BAR", "HAM"="EGGS"}
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
inherit.environment = "append"
|
||||||
|
environment = {FOO="BAZ", "PYTHON"="MONTY"}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||||
|
parsed_environment = options.build_options(identifier=None).environment
|
||||||
|
assert parsed_environment.as_dictionary(prev_environment={}) == {
|
||||||
|
"FOO": "BAR",
|
||||||
|
"HAM": "EGGS",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert options.build_options("cp37-manylinux_x86_64").environment.as_dictionary(
|
||||||
|
prev_environment={}
|
||||||
|
) == {
|
||||||
|
"FOO": "BAZ",
|
||||||
|
"HAM": "EGGS",
|
||||||
|
"PYTHON": "MONTY",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_inherit_environment_with_references(tmp_path: Path):
|
||||||
|
args = CommandLineArguments.defaults()
|
||||||
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
environment = {PATH="/opt/bin:$PATH"}
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
inherit.environment = "append"
|
||||||
|
environment = {PATH="/opt/local/bin:$PATH"}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
options = Options(platform="linux", command_line_arguments=args, env={"MONTY": "PYTHON"})
|
||||||
|
parsed_environment = options.build_options(identifier=None).environment
|
||||||
|
prev_environment = {"PATH": "/usr/bin:/bin"}
|
||||||
|
assert parsed_environment.as_dictionary(prev_environment=prev_environment) == {
|
||||||
|
"PATH": "/opt/bin:/usr/bin:/bin",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert options.build_options("cp37-manylinux_x86_64").environment.as_dictionary(
|
||||||
|
prev_environment=prev_environment
|
||||||
|
) == {
|
||||||
|
"PATH": "/opt/local/bin:/opt/bin:/usr/bin:/bin",
|
||||||
|
}
|
||||||
|
|||||||
+157
-45
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cibuildwheel.options import ConfigOptionError, OptionsReader, _dig_first
|
from cibuildwheel.options import ConfigOptionError, InheritRule, OptionsReader, _resolve_cascade
|
||||||
|
|
||||||
PYPROJECT_1 = """
|
PYPROJECT_1 = """
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
@@ -37,27 +37,31 @@ def test_simple_settings(tmp_path, platform, fname):
|
|||||||
|
|
||||||
options_reader = OptionsReader(config_file_path, platform=platform, env={})
|
options_reader = OptionsReader(config_file_path, platform=platform, env={})
|
||||||
|
|
||||||
assert options_reader.get("build", env_plat=False, sep=" ") == "cp39*"
|
assert options_reader.get("build", env_plat=False, list_sep=" ") == "cp39*"
|
||||||
|
|
||||||
assert options_reader.get("test-command") == "pyproject"
|
assert options_reader.get("test-command") == "pyproject"
|
||||||
assert options_reader.get("archs", sep=" ") == "auto"
|
assert options_reader.get("archs", list_sep=" ") == "auto"
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("test-requires", sep=" ")
|
options_reader.get("test-requires", list_sep=" ")
|
||||||
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Also testing options for support for both lists and tables
|
# Also testing options for support for both lists and tables
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("environment", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get("environment", table_format={"item": '{k}="{v}"', "sep": " "})
|
||||||
== 'THING="OTHER" FOO="BAR"'
|
== 'THING="OTHER" FOO="BAR"'
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("environment", sep="x", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get(
|
||||||
|
"environment", list_sep="x", table_format={"item": '{k}="{v}"', "sep": " "}
|
||||||
|
)
|
||||||
== 'THING="OTHER" FOO="BAR"'
|
== 'THING="OTHER" FOO="BAR"'
|
||||||
)
|
)
|
||||||
assert options_reader.get("test-extras", sep=",") == "one,two"
|
assert options_reader.get("test-extras", list_sep=",") == "one,two"
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("test-extras", sep=",", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get(
|
||||||
|
"test-extras", list_sep=",", table_format={"item": '{k}="{v}"', "sep": " "}
|
||||||
|
)
|
||||||
== "one,two"
|
== "one,two"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,10 +69,10 @@ def test_simple_settings(tmp_path, platform, fname):
|
|||||||
assert options_reader.get("manylinux-i686-image") == "manylinux2014"
|
assert options_reader.get("manylinux-i686-image") == "manylinux2014"
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options_reader.get("environment", sep=" ")
|
options_reader.get("environment", list_sep=" ")
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options_reader.get("test-extras", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get("test-extras", table_format={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
|
||||||
|
|
||||||
def test_envvar_override(tmp_path, platform):
|
def test_envvar_override(tmp_path, platform):
|
||||||
@@ -87,14 +91,14 @@ def test_envvar_override(tmp_path, platform):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert options_reader.get("archs", sep=" ") == "auto"
|
assert options_reader.get("archs", list_sep=" ") == "auto"
|
||||||
|
|
||||||
assert options_reader.get("build", sep=" ") == "cp38*"
|
assert options_reader.get("build", list_sep=" ") == "cp38*"
|
||||||
assert options_reader.get("manylinux-x86_64-image") == "manylinux_2_24"
|
assert options_reader.get("manylinux-x86_64-image") == "manylinux_2_24"
|
||||||
assert options_reader.get("manylinux-i686-image") == "manylinux2014"
|
assert options_reader.get("manylinux-i686-image") == "manylinux2014"
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("test-requires", sep=" ")
|
options_reader.get("test-requires", list_sep=" ")
|
||||||
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
||||||
)
|
)
|
||||||
assert options_reader.get("test-command") == "mytest"
|
assert options_reader.get("test-command") == "mytest"
|
||||||
@@ -215,7 +219,7 @@ build = ["1", "2"]
|
|||||||
)
|
)
|
||||||
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
assert options_reader.get("build", sep=", ") == "1, 2"
|
assert options_reader.get("build", list_sep=", ") == "1, 2"
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options_reader.get("build")
|
options_reader.get("build")
|
||||||
|
|
||||||
@@ -262,47 +266,92 @@ manylinux-x86_64-image = ""
|
|||||||
assert options_reader.get("manylinux-aarch64-image", ignore_empty=True) == "manylinux1"
|
assert options_reader.get("manylinux-aarch64-image", ignore_empty=True) == "manylinux1"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ignore_empty", [True, False])
|
@pytest.mark.parametrize("ignore_empty", [True, False], ids=["ignore_empty", "no_ignore_empty"])
|
||||||
def test_dig_first(ignore_empty):
|
def test_resolve_cascade(ignore_empty):
|
||||||
d1 = {"random": "thing"}
|
answer = _resolve_cascade(
|
||||||
d2 = {"this": "that", "empty": ""}
|
("not", InheritRule.NONE),
|
||||||
d3 = {"other": "hi"}
|
(None, InheritRule.NONE),
|
||||||
d4 = {"this": "d4", "empty": "not"}
|
("", InheritRule.NONE),
|
||||||
|
(None, InheritRule.NONE),
|
||||||
answer = _dig_first(
|
|
||||||
(d1, "empty"),
|
|
||||||
(d2, "empty"),
|
|
||||||
(d3, "empty"),
|
|
||||||
(d4, "empty"),
|
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
)
|
)
|
||||||
assert answer == ("not" if ignore_empty else "")
|
assert answer == ("not" if ignore_empty else "")
|
||||||
|
|
||||||
answer = _dig_first(
|
answer = _resolve_cascade(
|
||||||
(d1, "this"),
|
("d4", InheritRule.NONE),
|
||||||
(d2, "this"),
|
(None, InheritRule.NONE),
|
||||||
(d3, "this"),
|
("that", InheritRule.NONE),
|
||||||
(d4, "this"),
|
(None, InheritRule.NONE),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
)
|
)
|
||||||
assert answer == "that"
|
assert answer == "that"
|
||||||
|
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(ValueError, match="a setting should at least have a default value"):
|
||||||
_dig_first(
|
_resolve_cascade(
|
||||||
(d1, "this"),
|
(None, InheritRule.NONE),
|
||||||
(d2, "other"),
|
(None, InheritRule.NONE),
|
||||||
(d3, "this"),
|
(None, InheritRule.NONE),
|
||||||
(d4, "other"),
|
(None, InheritRule.NONE),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ignore_empty", [True, False], ids=["ignore_empty", "no_ignore_empty"])
|
||||||
|
@pytest.mark.parametrize("rule", [InheritRule.PREPEND, InheritRule.NONE, InheritRule.APPEND])
|
||||||
|
def test_resolve_cascade_merge_list(ignore_empty, rule):
|
||||||
|
answer = _resolve_cascade(
|
||||||
|
(["a1", "a2"], InheritRule.NONE),
|
||||||
|
([], InheritRule.NONE),
|
||||||
|
(["b1", "b2"], rule),
|
||||||
|
(None, InheritRule.NONE),
|
||||||
|
ignore_empty=ignore_empty,
|
||||||
|
list_sep=" ",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ignore_empty:
|
||||||
|
assert answer == "b1 b2"
|
||||||
|
else:
|
||||||
|
if rule == InheritRule.PREPEND:
|
||||||
|
assert answer == "b1 b2 a1 a2"
|
||||||
|
elif rule == InheritRule.NONE:
|
||||||
|
assert answer == "b1 b2"
|
||||||
|
elif rule == InheritRule.APPEND:
|
||||||
|
assert answer == "a1 a2 b1 b2"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("rule", [InheritRule.PREPEND, InheritRule.NONE, InheritRule.APPEND])
|
||||||
|
def test_resolve_cascade_merge_dict(rule):
|
||||||
|
answer = _resolve_cascade(
|
||||||
|
({"value": "a1", "base": "b1"}, InheritRule.NONE),
|
||||||
|
(None, InheritRule.NONE),
|
||||||
|
({"value": "override"}, rule),
|
||||||
|
(None, InheritRule.NONE),
|
||||||
|
table_format={"item": "{k}={v}", "sep": " "},
|
||||||
|
)
|
||||||
|
|
||||||
|
if rule == InheritRule.PREPEND:
|
||||||
|
assert answer == "value=override value=a1 base=b1"
|
||||||
|
elif rule == InheritRule.NONE:
|
||||||
|
assert answer == "value=override"
|
||||||
|
elif rule == InheritRule.APPEND:
|
||||||
|
assert answer == "value=a1 base=b1 value=override"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_cascade_merge_different_types():
|
||||||
|
answer = _resolve_cascade(
|
||||||
|
({"value": "a1", "base": "b1"}, InheritRule.NONE),
|
||||||
|
({"value": "override"}, InheritRule.APPEND),
|
||||||
|
table_format={"item": "{k}={v}", "sep": " "},
|
||||||
|
)
|
||||||
|
assert answer == "value=a1 base=b1 value=override"
|
||||||
|
|
||||||
|
|
||||||
PYPROJECT_2 = """
|
PYPROJECT_2 = """
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
build = ["cp38*", "cp37*"]
|
build = ["cp38*", "cp37*"]
|
||||||
environment = {FOO="BAR"}
|
environment = {FOO="BAR", "HAM"="EGGS"}
|
||||||
|
|
||||||
test-command = "pyproject"
|
test-command = ["pyproject"]
|
||||||
|
|
||||||
manylinux-x86_64-image = "manylinux1"
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
@@ -311,8 +360,25 @@ test-requires = "else"
|
|||||||
|
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
select = "cp37*"
|
select = "cp37*"
|
||||||
test-command = "pyproject-override"
|
inherit = {test-command="prepend", environment="append"}
|
||||||
|
test-command = ["pyproject-override", "override2"]
|
||||||
manylinux-x86_64-image = "manylinux2014"
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
environment = {FOO="BAZ", "PYTHON"="MONTY"}
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*-final"
|
||||||
|
inherit = {test-command="append"}
|
||||||
|
test-command = ["pyproject-finalize", "finalize2"]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*-final"
|
||||||
|
inherit = {test-command="append"}
|
||||||
|
test-command = ["extra-finalize"]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*-final"
|
||||||
|
inherit = {test-command="prepend"}
|
||||||
|
test-command = ["extra-prepend"]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -321,13 +387,30 @@ def test_pyproject_2(tmp_path, platform):
|
|||||||
pyproject_toml.write_text(PYPROJECT_2)
|
pyproject_toml.write_text(PYPROJECT_2)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform=platform, env={})
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform=platform, env={})
|
||||||
assert options_reader.get("test-command") == "pyproject"
|
assert options_reader.get("test-command", list_sep=" && ") == "pyproject"
|
||||||
|
|
||||||
with options_reader.identifier("random"):
|
with options_reader.identifier("random"):
|
||||||
assert options_reader.get("test-command") == "pyproject"
|
assert options_reader.get("test-command", list_sep=" && ") == "pyproject"
|
||||||
|
|
||||||
with options_reader.identifier("cp37-something"):
|
with options_reader.identifier("cp37-something"):
|
||||||
assert options_reader.get("test-command") == "pyproject-override"
|
assert (
|
||||||
|
options_reader.get("test-command", list_sep=" && ")
|
||||||
|
== "pyproject-override && override2 && pyproject"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
options_reader.get("environment", table_format={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
== 'FOO="BAR" HAM="EGGS" FOO="BAZ" PYTHON="MONTY"'
|
||||||
|
)
|
||||||
|
|
||||||
|
with options_reader.identifier("cp37-final"):
|
||||||
|
assert (
|
||||||
|
options_reader.get("test-command", list_sep=" && ")
|
||||||
|
== "extra-prepend && pyproject-override && override2 && pyproject && pyproject-finalize && finalize2 && extra-finalize"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
options_reader.get("environment", table_format={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
== 'FOO="BAR" HAM="EGGS" FOO="BAZ" PYTHON="MONTY"'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_overrides_not_a_list(tmp_path, platform):
|
def test_overrides_not_a_list(tmp_path, platform):
|
||||||
@@ -359,7 +442,7 @@ other = ["two", "three"]
|
|||||||
|
|
||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("config-settings", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get("config-settings", table_format={"item": '{k}="{v}"', "sep": " "})
|
||||||
== 'example="one" other="two" other="three"'
|
== 'example="one" other="two" other="three"'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -376,7 +459,36 @@ def test_pip_config_settings(tmp_path):
|
|||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
||||||
assert (
|
assert (
|
||||||
options_reader.get(
|
options_reader.get(
|
||||||
"config-settings", table={"item": "--config-settings='{k}=\"{v}\"'", "sep": " "}
|
"config-settings", table_format={"item": "--config-settings='{k}=\"{v}\"'", "sep": " "}
|
||||||
)
|
)
|
||||||
== "--config-settings='--build-option=\"--use-mypyc\"'"
|
== "--config-settings='--build-option=\"--use-mypyc\"'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_inherit(tmp_path):
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
before-all = ["before-all"]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
inherit.before-all = "append"
|
||||||
|
before-all = ["override1"]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
inherit.before-all = "prepend"
|
||||||
|
before-all = ["override2"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
||||||
|
with options_reader.identifier("cp38-something"):
|
||||||
|
assert options_reader.get("before-all", list_sep=" && ") == "before-all"
|
||||||
|
with options_reader.identifier("cp37-something"):
|
||||||
|
assert (
|
||||||
|
options_reader.get("before-all", list_sep=" && ")
|
||||||
|
== "override2 && before-all && override1"
|
||||||
|
)
|
||||||
|
|||||||
@@ -72,6 +72,50 @@ def test_overrides_only_select():
|
|||||||
validator(example)
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_valid_inherit():
|
||||||
|
example = tomllib.loads(
|
||||||
|
"""
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
inherit.repair-wheel-command = "append"
|
||||||
|
select = "somestring"
|
||||||
|
repair-wheel-command = ["something"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
validator = validate_pyproject.api.Validator()
|
||||||
|
assert validator(example) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_invalid_inherit():
|
||||||
|
example = tomllib.loads(
|
||||||
|
"""
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
inherit.something = "append"
|
||||||
|
select = "somestring"
|
||||||
|
repair-wheel-command = "something"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
validator = validate_pyproject.api.Validator()
|
||||||
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_invalid_inherit_value():
|
||||||
|
example = tomllib.loads(
|
||||||
|
"""
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
inherit.repair-wheel-command = "nothing"
|
||||||
|
select = "somestring"
|
||||||
|
repair-wheel-command = "something"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
validator = validate_pyproject.api.Validator()
|
||||||
|
with pytest.raises(validate_pyproject.error_reporting.ValidationError):
|
||||||
|
validator(example)
|
||||||
|
|
||||||
|
|
||||||
def test_docs_examples():
|
def test_docs_examples():
|
||||||
"""
|
"""
|
||||||
Parse out all the configuration examples, build valid TOML out of them, and
|
Parse out all the configuration examples, build valid TOML out of them, and
|
||||||
|
|||||||
Reference in New Issue
Block a user