Skip to content

Commit a100fca

Browse files
committed
BUG: improve validation of pyproject.toml meson-python configuration
Switch from an incomplete (an bugged) ad hoc validation to a scheme based validation strategy. The scheme is defined in the function _validate_pyproject_config() as nested dictionaries where the keys are configuration field names and valued are validation functions. Unknown fields result in an error. Fixes #293.
1 parent 9155036 commit a100fca

File tree

1 file changed

+40
-31
lines changed

1 file changed

+40
-31
lines changed

mesonpy/__init__.py

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,36 @@ def build_editable(self, directory: Path, verbose: bool = False) -> pathlib.Path
658658
return wheel_file
659659

660660

661+
def _validate_pyproject_config(pyproject: Dict[str, Any]) -> Dict[str, Any]:
662+
663+
def _table(scheme: Dict[str, Callable[[Any, str], Any]]) -> Callable[[Any, str], Dict[str, Any]]:
664+
def func(value: Any, name: str) -> Dict[str, Any]:
665+
if not isinstance(value, dict):
666+
raise ConfigError(f'configuration entry "{name}" must be a table')
667+
table = {}
668+
for key, val in value.items():
669+
check = scheme.get(key)
670+
if check is None:
671+
raise ConfigError(f'unknown configuration entry "{name}.{key}"')
672+
table[key] = check(val, f'{name}.{key}')
673+
return table
674+
return func
675+
676+
def _strings(value: Any, name: str) -> List[str]:
677+
if not isinstance(value, list) or not all(isinstance(x, str) for x in value):
678+
raise ConfigError(f'configuration entry "{name}" must be a list of strings')
679+
return value
680+
681+
scheme = _table({
682+
'args': _table({
683+
name: _strings for name in _MESON_ARGS_KEYS
684+
})
685+
})
686+
687+
table = pyproject.get('tool', {}).get('meson-python', {})
688+
return scheme(table, 'tool.meson-python')
689+
690+
661691
class Project():
662692
"""Meson project wrapper to generate Python artifacts."""
663693

@@ -715,11 +745,13 @@ def __init__( # noqa: C901
715745
self._meson_cross_file.write_text(cross_file_data)
716746
self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file)))
717747

718-
# load config -- PEP 621 support is optional
719-
self._config = tomllib.loads(self._source_dir.joinpath('pyproject.toml').read_text())
720-
self._pep621 = 'project' in self._config
748+
# load pyproject.toml
749+
pyproject = tomllib.loads(self._source_dir.joinpath('pyproject.toml').read_text())
750+
751+
# package metadata
752+
self._pep621 = 'project' in pyproject:
721753
if self.pep621:
722-
self._metadata = pyproject_metadata.StandardMetadata.from_pyproject(self._config, self._source_dir)
754+
self._metadata = pyproject_metadata.StandardMetadata.from_pyproject(pyproject, self._source_dir)
723755
else:
724756
print(
725757
'{yellow}{bold}! Using Meson to generate the project metadata '
@@ -730,14 +762,10 @@ def __init__( # noqa: C901
730762
if self._metadata:
731763
self._validate_metadata()
732764

733-
# load meson args
734-
for key in self._get_config_key('args'):
735-
self._meson_args[key].extend(self._get_config_key(f'args.{key}'))
736-
# XXX: We should validate the user args to make sure they don't conflict with ours.
737-
738-
self._check_for_unknown_config_keys({
739-
'args': _MESON_ARGS_KEYS,
740-
})
765+
# load meson args from pyproject.toml
766+
pyproject_config = _validate_pyproject_config(pyproject)
767+
for key, value in pyproject_config.get('args', {}).items():
768+
self._meson_args[key].extend(value)
741769

742770
# meson arguments from the command line take precedence over
743771
# arguments from the configuration file thus are added later
@@ -778,14 +806,6 @@ def __init__( # noqa: C901
778806
if self._metadata and 'version' in self._metadata.dynamic:
779807
self._metadata.version = self.version
780808

781-
def _get_config_key(self, key: str) -> Any:
782-
value: Any = self._config
783-
for part in f'tool.meson-python.{key}'.split('.'):
784-
if not isinstance(value, Mapping):
785-
raise ConfigError(f'Configuration entry "tool.meson-python.{key}" should be a TOML table not {type(value)}')
786-
value = value.get(part, {})
787-
return value
788-
789809
def _proc(self, *args: str) -> None:
790810
"""Invoke a subprocess."""
791811
print('{cyan}{bold}+ {}{reset}'.format(' '.join(args), **_STYLES))
@@ -858,17 +878,6 @@ def _validate_metadata(self) -> None:
858878
f'expected {self._metadata.requires_python}'
859879
)
860880

861-
def _check_for_unknown_config_keys(self, valid_args: Mapping[str, Collection[str]]) -> None:
862-
config = self._config.get('tool', {}).get('meson-python', {})
863-
864-
for key, valid_subkeys in config.items():
865-
if key not in valid_args:
866-
raise ConfigError(f'Unknown configuration key "tool.meson-python.{key}"')
867-
868-
for subkey in valid_args[key]:
869-
if subkey not in valid_subkeys:
870-
raise ConfigError(f'Unknown configuration key "tool.meson-python.{key}.{subkey}"')
871-
872881
@cached_property
873882
def _wheel_builder(self) -> _WheelBuilder:
874883
return _WheelBuilder(

0 commit comments

Comments
 (0)