feat: configuration support (#684)

* feat: configuration support

* refactor: include manylinux, minor redesign

* "project global" overrides "default platform"

This allows overriding `repair-wheel-command` in the `[tool.cibuildwheel.global]` section.

* fix: add PyPy images

* refactor: remove global

* fix: support tables and arrays

* docs: add pyproject.toml config to docs

* docs: updates based on feedback

* fix: only join if sensible

* refactor: remove manylinux dict

* docs: examples headers and sections on each

* feat: support changing the config from the command line

* fix: use {package}

* tests: add a few tests and mention config one more place

* Restyle examples tabs to remove indent

* Unrelated docs improvements

* Absorb extra content into the tab

* Make the tabs smaller and to the right

* Copy edits to options

* Fix header ids

* Use fewer [tool.cibuildwheel] headers

* docs: minor additions/fixes

* feat: disallow options on some platforms

* docs: make the examples a tiny bit more tabby

* fix: enforce tables/lists are optionally separate

* Some unrelated fixes to the other tab styling

* Minor fix to tab styling

* Refactor to make code a bit more linear and immutable

* Minor docs changes

Co-authored-by: mayeut <mayeut@users.noreply.github.com>
Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
Henry Schreiner
2021-06-21 12:26:46 -04:00
committed by GitHub
co-authored by mayeut Joe Rickerby
parent f7bdccfbd7
commit f62cbc303a
9 changed files with 1111 additions and 283 deletions
+1
View File
@@ -123,6 +123,7 @@ Options
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds |
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |
These options can be specified in a pyproject.toml file, as well; see [configuration](https://cibuildwheel.readthedocs.io/en/stable/options/#configuration).
Working examples
----------------
+62 -95
View File
@@ -5,7 +5,7 @@ import textwrap
import traceback
from configparser import ConfigParser
from pathlib import Path
from typing import Dict, List, Optional, Set, Union, overload
from typing import Dict, List, Optional, Set, Union
from packaging.specifiers import SpecifierSet
@@ -16,6 +16,7 @@ import cibuildwheel.util
import cibuildwheel.windows
from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.environment import EnvironmentParseError, parse_environment
from cibuildwheel.options import ConfigOptions
from cibuildwheel.projectfiles import get_requires_python_str
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
from cibuildwheel.util import (
@@ -28,39 +29,16 @@ from cibuildwheel.util import (
resources_dir,
)
@overload
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: str
) -> str:
... # noqa: E704
@overload
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: None = None
) -> Optional[str]:
... # noqa: E704 E302
def get_option_from_environment(
option_name: str, *, platform: Optional[str] = None, default: Optional[str] = None
) -> Optional[str]: # noqa: E302
"""
Returns an option from the environment, optionally scoped by the platform.
Example:
get_option_from_environment('CIBW_COLOR', platform='macos')
This will return the value of CIBW_COLOR_MACOS if it exists, otherwise the value of
CIBW_COLOR.
"""
if platform:
option = os.environ.get(f"{option_name}_{platform.upper()}")
if option is not None:
return option
return os.environ.get(option_name, default)
MANYLINUX_ARCHS = (
"x86_64",
"i686",
"pypy_x86_64",
"aarch64",
"ppc64le",
"s390x",
"pypy_aarch64",
"pypy_i686",
)
def main() -> None:
@@ -69,8 +47,9 @@ def main() -> None:
parser = argparse.ArgumentParser(
description="Build wheels for all the platforms.",
epilog="""
Most options are supplied via environment variables.
See https://github.com/pypa/cibuildwheel#options for info.
Most options are supplied via environment variables or in
--config-file (pyproject.toml usually). See
https://github.com/pypa/cibuildwheel#options for info.
""",
)
@@ -104,10 +83,17 @@ def main() -> None:
parser.add_argument(
"--output-dir",
default=os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse"),
help="Destination folder for the wheels.",
)
parser.add_argument(
"--config-file",
help="""
TOML config file for cibuildwheel. Defaults to pyproject.toml, but
can be overridden with this option.
""",
)
parser.add_argument(
"package_dir",
default=".",
@@ -176,40 +162,40 @@ def main() -> None:
sys.exit(2)
package_dir = Path(args.package_dir)
output_dir = Path(args.output_dir)
if platform == "linux":
repair_command_default = "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
repair_command_default = "delocate-listdeps {wheel} && delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}"
elif platform == "windows":
repair_command_default = ""
else:
assert_never(platform)
manylinux_identifiers = {
f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS
}
disallow = {
"linux": {"dependency-versions"},
"macos": manylinux_identifiers,
"windows": manylinux_identifiers,
}
options = ConfigOptions(package_dir, args.config_file, platform=platform, disallow=disallow)
output_dir = Path(
args.output_dir
if args.output_dir is not None
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
)
build_config = options("build", env_plat=False, sep=" ") or "*"
skip_config = options("skip", env_plat=False, sep=" ")
test_skip = options("test-skip", env_plat=False, sep=" ")
archs_config_str = args.archs or options("archs", sep=" ")
environment_config = options("environment", table={"item": '{k}="{v}"', "sep": " "})
before_all = options("before-all", sep=" && ")
before_build = options("before-build", sep=" && ")
repair_command = options("repair-wheel-command", sep=" && ")
dependency_versions = options("dependency-versions")
test_command = options("test-command", sep=" && ")
before_test = options("before-test", sep=" && ")
test_requires = options("test-requires", sep=" ").split()
test_extras = options("test-extras", sep=",")
build_verbosity_str = options("build-verbosity")
build_config = os.environ.get("CIBW_BUILD") or "*"
skip_config = os.environ.get("CIBW_SKIP", "")
test_skip = os.environ.get("CIBW_TEST_SKIP", "")
environment_config = get_option_from_environment(
"CIBW_ENVIRONMENT", platform=platform, default=""
)
before_all = get_option_from_environment("CIBW_BEFORE_ALL", platform=platform, default="")
before_build = get_option_from_environment("CIBW_BEFORE_BUILD", platform=platform)
repair_command = get_option_from_environment(
"CIBW_REPAIR_WHEEL_COMMAND", platform=platform, default=repair_command_default
)
dependency_versions = get_option_from_environment(
"CIBW_DEPENDENCY_VERSIONS", platform=platform, default="pinned"
)
test_command = get_option_from_environment("CIBW_TEST_COMMAND", platform=platform)
before_test = get_option_from_environment("CIBW_BEFORE_TEST", platform=platform)
test_requires = get_option_from_environment(
"CIBW_TEST_REQUIRES", platform=platform, default=""
).split()
test_extras = get_option_from_environment("CIBW_TEST_EXTRAS", platform=platform, default="")
build_verbosity_str = get_option_from_environment(
"CIBW_BUILD_VERBOSITY", platform=platform, default=""
)
prerelease_pythons = args.prerelease_pythons or cibuildwheel.util.strtobool(
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
)
@@ -218,11 +204,11 @@ def main() -> None:
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ", ".join(sorted(package_files, reverse=True))
print(
f"cibuildwheel: Could not find any of {{{names}}} at root of package", file=sys.stderr
)
msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package"
print(msg, file=sys.stderr)
sys.exit(2)
# 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: Optional[str] = os.environ.get(
"CIBW_PROJECT_REQUIRES_PYTHON"
@@ -270,13 +256,6 @@ def main() -> None:
# This needs to be passed on to the docker container in linux.py
os.environ["CIBUILDWHEEL"] = "1"
if args.archs is not None:
archs_config_str = args.archs
else:
archs_config_str = get_option_from_environment(
"CIBW_ARCHS", platform=platform, default="auto"
)
archs = Architecture.parse_config(archs_config_str, platform=platform)
identifiers = get_build_identifiers(platform, build_selector, archs)
@@ -286,7 +265,7 @@ def main() -> None:
print(identifier)
sys.exit(0)
manylinux_images: Optional[Dict[str, str]] = None
manylinux_images: Dict[str, str] = {}
if platform == "linux":
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
all_pinned_docker_images = ConfigParser()
@@ -297,22 +276,10 @@ def main() -> None:
# 'pypy_x86_64': {'manylinux2010': '...' }
# ... }
manylinux_images = {}
for build_platform in [
"x86_64",
"i686",
"pypy_x86_64",
"aarch64",
"ppc64le",
"s390x",
"pypy_aarch64",
"pypy_i686",
]:
for build_platform in MANYLINUX_ARCHS:
pinned_images = all_pinned_docker_images[build_platform]
config_name = f"CIBW_MANYLINUX_{build_platform.upper()}_IMAGE"
config_value = os.environ.get(config_name)
config_value = options(f"manylinux-{build_platform}-image")
if config_value is None:
# default to manylinux2010 if it's available, otherwise manylinux2014
@@ -340,7 +307,7 @@ def main() -> None:
repair_command=repair_command,
environment=environment,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images,
manylinux_images=manylinux_images or None,
)
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
+171
View File
@@ -0,0 +1,171 @@
import os
from pathlib import Path
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
import toml
from .typing import PLATFORMS, TypedDict
from .util import resources_dir
Setting = Union[Dict[str, str], List[str], str]
class TableFmt(TypedDict):
item: str
sep: str
class ConfigOptionError(KeyError):
pass
def _dig_first(*pairs: Tuple[Mapping[str, Any], str]) -> Setting:
"""
Return the first dict item that matches from pairs of dicts and keys.
Final result is will throw a KeyError if missing.
_dig_first((dict1, "key1"), (dict2, "key2"), ...)
"""
(dict_like, key), *others = pairs
return dict_like.get(key, _dig_first(*others)) if others else dict_like[key]
class ConfigOptions:
"""
Gets options from the environment, config or defaults, optionally scoped
by the platform.
Example:
>>> options = ConfigOptions(package_dir, platform='macos')
>>> options('cool-color')
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'
from pyproject.toml, or from cibuildwheel/resources/defaults.toml. An
error is thrown if there are any unexpected keys or sections in
tool.cibuildwheel.
"""
def __init__(
self,
package_path: Path,
config_file: Optional[str] = None,
*,
platform: str,
disallow: Optional[Dict[str, Set[str]]] = None,
) -> None:
self.platform = platform
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] = {}
if config_file is not None:
config_path = Path(config_file.format(package=package_path))
config_options, config_platform_options = self._load_file(config_path)
else:
# load pyproject.toml, if it's available
pyproject_toml_path = package_path / "pyproject.toml"
if pyproject_toml_path.exists():
config_options, config_platform_options = self._load_file(pyproject_toml_path)
# validate project config
for option_name in config_options:
if not self._is_valid_global_option(option_name):
raise ConfigOptionError(f'Option "{option_name}" not supported in a config file')
for option_name in config_platform_options:
if not self._is_valid_platform_option(option_name):
raise ConfigOptionError(
f'Option "{option_name}" not supported in the "{self.platform}" section'
)
self.config_options = config_options
self.config_platform_options = config_platform_options
def _is_valid_global_option(self, name: str) -> bool:
"""
Returns True if an option with this name is allowed in the
[tool.cibuildwheel] section of a config file.
"""
allowed_option_names = self.default_options.keys() | PLATFORMS
return name in allowed_option_names
def _is_valid_platform_option(self, name: str) -> bool:
"""
Returns True if an option with this name is allowed in the
[tool.cibuildwheel.<current-platform>] section of a config file.
"""
disallowed_platform_options = self.disallow.get(self.platform, set())
if name in disallowed_platform_options:
return False
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
return name in allowed_option_names
def _load_file(self, filename: Path) -> Tuple[Dict[str, Any], Dict[str, Any]]:
"""
Load a toml file, returns global and platform as separate dicts.
"""
config = toml.load(filename)
global_options = config.get("tool", {}).get("cibuildwheel", {})
platform_options = global_options.get(self.platform, {})
return global_options, platform_options
def __call__(
self,
name: str,
*,
env_plat: bool = True,
sep: Optional[str] = None,
table: Optional[TableFmt] = None,
) -> 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
with "table['sep']".
"""
if name not in self.default_options and name not in self.default_platform_options:
raise ConfigOptionError(f"{name} must be in cibuildwheel/resources/defaults.toml file")
# Environment variable form
envvar = f"CIBW_{name.upper().replace('-', '_')}"
plat_envvar = f"{envvar}_{self.platform.upper()}"
# 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(
(os.environ if env_plat else {}, plat_envvar), # type: ignore
(os.environ, envvar),
(self.config_platform_options, name),
(self.config_options, name),
(self.default_platform_options, name),
(self.default_options, name),
)
if isinstance(result, dict):
if table is None:
raise ConfigOptionError(f"{name} does not accept a table")
return table["sep"].join(table["item"].format(k=k, v=v) for k, v in result.items())
elif isinstance(result, list):
if sep is None:
raise ConfigOptionError(f"{name} does not accept a list")
return sep.join(result)
elif isinstance(result, int):
return str(result)
else:
return result
+39
View File
@@ -0,0 +1,39 @@
[tool.cibuildwheel]
build = "*"
skip = ""
test-skip = ""
archs = ["auto"]
dependency-versions = "pinned"
environment = {}
build-verbosity = ""
before-all = ""
before-build = ""
repair-wheel-command = ""
test-command = ""
before-test = ""
test-requires = []
test-extras = []
manylinux-x86_64-image = "manylinux2010"
manylinux-i686-image = "manylinux2010"
manylinux-aarch64-image = "manylinux2014"
manylinux-ppc64le-image = "manylinux2014"
manylinux-s390x-image = "manylinux2014"
manylinux-pypy_x86_64-image = "manylinux2010"
manylinux-pypy_i686-image = "manylinux2010"
manylinux-pypy_aarch64-image = "manylinux2014"
[tool.cibuildwheel.linux]
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
[tool.cibuildwheel.macos]
repair-wheel-command = [
"delocate-listdeps {wheel}",
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}",
]
[tool.cibuildwheel.windows]
+73 -8
View File
@@ -1,6 +1,10 @@
/* Global styles */
body {
overflow-wrap: break-word;
}
p {
margin-bottom: 12px;
line-height: 26px;
@@ -20,9 +24,9 @@ code, pre, tt {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
}
.hljs {
padding: 1em;
background-color: #f6f8fa;
pre code, pre code.hljs {
padding: 1em;
background-color: #f6f8fa;
}
h1, h2, h3, h4, h5, h6 {
@@ -112,7 +116,21 @@ h1, h2, h3, h4, h5, h6 {
background-color: white;
display: flex;
justify-content: flex-start;
border-bottom: 2px solid #ccc;
overflow-x: auto;
overflow-y: hidden;
position: relative;
}
.tabs-header::before {
position: absolute;
content: "";
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #ccc;
}
.tabs-header > * {
position: relative;
}
/* Style the buttons that are used to open the tab content */
@@ -126,15 +144,13 @@ h1, h2, h3, h4, h5, h6 {
font-size: 0.9em;
font-weight: 600;
padding: 8px 16px;
border-bottom: 2px solid transparent;
/* put the border on top of the parent border */
margin-bottom: -2px;
border-bottom: 2px solid #ccc;
}
.tabs-header button:focus-visible {
/* preserve an outline for accesibility purposes */
outline: 1px solid currentColor;
outline-offset: -1px;
}
/* Change background color of buttons on hover */
@@ -153,4 +169,53 @@ h1, h2, h3, h4, h5, h6 {
background-color: #f1f6fa;
padding: 1px 1em;
padding-top: 0.8em;
/* don't collapse inner margins */
overflow-y: hidden;
}
/* Examples tabs styling */
.tabs.examples .tabs-header {
justify-content: flex-end;
background-color: transparent;
border-bottom-style: none;
padding: 0;
}
.tabs.examples .tabs-header::before {
height: 0;
}
.tabs.examples .tabs-header button {
padding: 6px 12px;
border-bottom: none;
color: rgba(0, 0, 0, 0.3);
font-size: 0.8em;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.tabs-header button:hover {
background-color: unset;
}
.tabs.examples .tabs-header button.active {
border-bottom-style: none;
background: #f6f8fa;
color: inherit;
}
.tabs.examples .tabs-content {
padding: 0;
background-color: transparent;
background-color: #f6f8fa;
}
.tabs.examples .tabs-content .tab > * {
padding-left: 0.8em;
padding-right: 0.8em;
}
.tabs.examples .tabs-content .tab > pre {
padding-left: 0;
padding-right: 0;
margin-bottom: 0.5em;
}
.tabs.examples pre:first-child {
margin-top: 0;
}
+11
View File
@@ -20,6 +20,17 @@ while (true) {
tabContainer.insertBefore(firstTab);
tabContainer.append(headerContainer, contentContainer)
// add extra classes from the first tab to the container
const classes = Array.from(firstTab[0].classList)
for (let i = 0; i < classes.length; i++) {
const element = classes[i];
if (element == 'tab' || element == 'admonition') {
continue
}
tabContainer.addClass(element)
}
const selectTab = function (index) {
headerContainer.children().removeClass('active')
headerContainer.children().eq(index).addClass('active')
+561 -174
View File
File diff suppressed because it is too large Load Diff
+3 -6
View File
@@ -200,7 +200,7 @@ def test_test_command(
main()
assert intercepted_build_args.args[0].test_command == test_command
assert intercepted_build_args.args[0].test_command == (test_command or "")
@pytest.mark.parametrize("before_build", [None, "before --build"])
@@ -217,7 +217,7 @@ def test_before_build(
main()
assert intercepted_build_args.args[0].before_build == before_build
assert intercepted_build_args.args[0].before_build == (before_build or "")
@pytest.mark.parametrize("build_verbosity", [None, 0, 2, -2, 4, -4])
@@ -286,7 +286,4 @@ def test_before_all(before_all, platform_specific, platform, intercepted_build_a
main()
if before_all is None:
before_all = ""
assert intercepted_build_args.args[0].before_all == before_all
assert intercepted_build_args.args[0].before_all == (before_all or "")
+190
View File
@@ -0,0 +1,190 @@
import pytest
from cibuildwheel.options import ConfigOptionError, ConfigOptions
PYPROJECT_1 = """
[tool.cibuildwheel]
build = "cp39*"
environment = {THING = "OTHER", FOO="BAR"}
test-command = "pyproject"
test-requires = "something"
test-extras = ["one", "two"]
manylinux-x86_64-image = "manylinux1"
[tool.cibuildwheel.macos]
test-requires = "else"
[tool.cibuildwheel.linux]
test-requires = ["other", "many"]
"""
@pytest.fixture(params=["linux", "macos", "windows"])
def platform(request):
return request.param
@pytest.mark.parametrize("fname", ["pyproject.toml", "cibuildwheel.toml"])
def test_simple_settings(tmp_path, platform, fname):
with tmp_path.joinpath(fname).open("w") as f:
f.write(PYPROJECT_1)
options = ConfigOptions(tmp_path, f"{{package}}/{fname}", platform=platform)
assert options("build", env_plat=False, sep=" ") == "cp39*"
assert options("test-command") == "pyproject"
assert options("archs", sep=" ") == "auto"
assert (
options("test-requires", sep=" ")
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
)
# Also testing options for support for both lists and tables
assert (
options("environment", table={"item": '{k}="{v}"', "sep": " "}) == 'THING="OTHER" FOO="BAR"'
)
assert (
options("environment", sep="x", table={"item": '{k}="{v}"', "sep": " "})
== 'THING="OTHER" FOO="BAR"'
)
assert options("test-extras", sep=",") == "one,two"
assert options("test-extras", sep=",", table={"item": '{k}="{v}"', "sep": " "}) == "one,two"
assert options("manylinux-x86_64-image") == "manylinux1"
assert options("manylinux-i686-image") == "manylinux2010"
with pytest.raises(ConfigOptionError):
options("environment", sep=" ")
with pytest.raises(ConfigOptionError):
options("test-extras", table={"item": '{k}="{v}"', "sep": " "})
def test_envvar_override(tmp_path, platform, monkeypatch):
monkeypatch.setenv("CIBW_BUILD", "cp38*")
monkeypatch.setenv("CIBW_MANYLINUX_X86_64_IMAGE", "manylinux2014")
monkeypatch.setenv("CIBW_TEST_COMMAND", "mytest")
monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
with tmp_path.joinpath("pyproject.toml").open("w") as f:
f.write(PYPROJECT_1)
options = ConfigOptions(tmp_path, platform=platform)
assert options("archs", sep=" ") == "auto"
assert options("build", sep=" ") == "cp38*"
assert options("manylinux-x86_64-image") == "manylinux2014"
assert options("manylinux-i686-image") == "manylinux2010"
assert (
options("test-requires", sep=" ")
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
)
assert options("test-command") == "mytest"
def test_project_global_override_default_platform(tmp_path, platform):
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel]
repair-wheel-command = "repair-project-global"
"""
)
options = ConfigOptions(tmp_path, platform=platform)
assert options("repair-wheel-command") == "repair-project-global"
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
options = ConfigOptions(tmp_path, platform=platform)
assert options("repair-wheel-command") == "repair-env-global"
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel.linux]
repair-wheel-command = "repair-project-linux"
[tool.cibuildwheel.windows]
repair-wheel-command = "repair-project-windows"
[tool.cibuildwheel.macos]
repair-wheel-command = "repair-project-macos"
"""
)
options = ConfigOptions(tmp_path, platform=platform)
assert options("repair-wheel-command") == "repair-env-global"
def test_global_platform_order(tmp_path, platform):
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel.linux]
repair-wheel-command = "repair-project-linux"
[tool.cibuildwheel.windows]
repair-wheel-command = "repair-project-windows"
[tool.cibuildwheel.macos]
repair-wheel-command = "repair-project-macos"
[tool.cibuildwheel]
repair-wheel-command = "repair-project-global"
"""
)
options = ConfigOptions(tmp_path, platform=platform)
assert options("repair-wheel-command") == f"repair-project-{platform}"
def test_unexpected_key(tmp_path):
# Note that platform contents are only checked when running
# for that platform.
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel]
repairs-wheel-command = "repair-project-linux"
"""
)
with pytest.raises(ConfigOptionError):
ConfigOptions(tmp_path, platform="linux")
def test_unexpected_table(tmp_path):
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel.linus]
repair-wheel-command = "repair-project-linux"
"""
)
with pytest.raises(ConfigOptionError):
ConfigOptions(tmp_path, platform="linux")
def test_unsupported_join(tmp_path):
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel]
build = ["1", "2"]
"""
)
options = ConfigOptions(tmp_path, platform="linux")
assert "1, 2" == options("build", sep=", ")
with pytest.raises(ConfigOptionError):
options("build")
def test_disallowed_a(tmp_path):
tmp_path.joinpath("pyproject.toml").write_text(
"""
[tool.cibuildwheel.windows]
manylinux-x64_86-image = "manylinux1"
"""
)
disallow = {"windows": {"manylinux-x64_86-image"}}
ConfigOptions(tmp_path, platform="linux", disallow=disallow)
with pytest.raises(ConfigOptionError):
ConfigOptions(tmp_path, platform="windows", disallow=disallow)