Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add suggestions to the TOML error message #1205

Merged
merged 2 commits into from Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 30 additions & 21 deletions cibuildwheel/options.py
@@ -1,5 +1,6 @@
from __future__ import annotations

import difflib
import functools
import os
import sys
Expand Down Expand Up @@ -188,14 +189,10 @@ def __init__(

# 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')
self._validate_global_option(option_name)

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._validate_platform_option(option_name)

self.config_options = config_options
self.config_platform_options = config_platform_options
Expand All @@ -207,40 +204,51 @@ def __init__(

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

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

if not select:
raise ConfigOptionError('"select" must be set in an override')
raise ConfigOptionError("'select' must be set in an override")

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

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

def _is_valid_global_option(self, name: str) -> bool:
def _validate_global_option(self, name: str) -> None:
"""
Returns True if an option with this name is allowed in the
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"}

return name in allowed_option_names
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)

def _is_valid_platform_option(self, name: str) -> bool:
def _validate_platform_option(self, name: str) -> None:
"""
Returns True if an option with this name is allowed in the
Raises an error if an option with this name is not 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
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
raise ConfigOptionError(msg)

allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()

return name in allowed_option_names
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)

def _load_file(self, filename: Path) -> tuple[dict[str, Any], dict[str, Any]]:
"""
Expand Down Expand Up @@ -290,7 +298,8 @@ def get(
"""

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")
msg = f"{name!r} must be in cibuildwheel/resources/defaults.toml file to be accessed."
raise ConfigOptionError(msg)

# Environment variable form
envvar = f"CIBW_{name.upper().replace('-', '_')}"
Expand All @@ -314,12 +323,12 @@ def get(

if isinstance(result, dict):
if table is None:
raise ConfigOptionError(f"{name} does not accept a table")
raise ConfigOptionError(f"{name!r} does not accept a table")
return table["sep"].join(table["item"].format(k=k, v=v) for k, v in result.items())

if isinstance(result, list):
if sep is None:
raise ConfigOptionError(f"{name} does not accept a list")
raise ConfigOptionError(f"{name!r} does not accept a list")
return sep.join(result)

if isinstance(result, int):
Expand Down Expand Up @@ -393,7 +402,7 @@ def globals(self) -> GlobalOptions:
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}', only 'docker' and 'podman' are supported"
msg = f"cibuildwheel: Unrecognised container_engine {container_engine_str!r}, only 'docker' and 'podman' are supported"
print(msg, file=sys.stderr)
sys.exit(2)

Expand Down Expand Up @@ -437,15 +446,15 @@ def build_options(self, identifier: str | None) -> BuildOptions:
elif build_frontend_str == "pip":
build_frontend = "pip"
else:
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend_str}', only 'pip' and 'build' are supported"
msg = f"cibuildwheel: Unrecognised build frontend {build_frontend_str!r}, only 'pip' and 'build' are supported"
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}"',
f"cibuildwheel: Malformed environment option {environment_config!r}",
file=sys.stderr,
)
traceback.print_exc(None, sys.stderr)
Expand Down
21 changes: 20 additions & 1 deletion unit_test/options_toml_test.py
Expand Up @@ -160,9 +160,28 @@ def test_unexpected_key(tmp_path):
"""
)

with pytest.raises(ConfigOptionError):
with pytest.raises(ConfigOptionError) as excinfo:
OptionsReader(pyproject_toml, platform="linux")

assert "repair-wheel-command" in str(excinfo.value)


def test_underscores_in_key(tmp_path):
# Note that platform contents are only checked when running
# for that platform.
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
"""
[tool.cibuildwheel]
repair_wheel_command = "repair-project-linux"
"""
)

with pytest.raises(ConfigOptionError) as excinfo:
OptionsReader(pyproject_toml, platform="linux")

assert "repair-wheel-command" in str(excinfo.value)


def test_unexpected_table(tmp_path):
pyproject_toml = tmp_path / "pyproject.toml"
Expand Down