|
14 | 14 | import argparse
|
15 | 15 | import collections
|
16 | 16 | import contextlib
|
| 17 | +import difflib |
17 | 18 | import functools
|
18 | 19 | import importlib.machinery
|
19 | 20 | import io
|
@@ -685,6 +686,44 @@ def _strings(value: Any, name: str) -> List[str]:
|
685 | 686 | return scheme(table, 'tool.meson-python')
|
686 | 687 |
|
687 | 688 |
|
| 689 | +def _validate_config_settings(config_settings: Dict[str, Any]) -> Dict[str, Any]: |
| 690 | + """Validate options received from build frontend.""" |
| 691 | + |
| 692 | + def _string(value: Any, name: str) -> str: |
| 693 | + if not isinstance(value, str): |
| 694 | + raise ConfigError(f'only one value for "{name}" can be specified') |
| 695 | + return value |
| 696 | + |
| 697 | + def _bool(value: Any, name: str) -> bool: |
| 698 | + return True |
| 699 | + |
| 700 | + def _string_or_strings(value: Any, name: str) -> List[str]: |
| 701 | + return list([value,] if isinstance(value, str) else value) |
| 702 | + |
| 703 | + options = { |
| 704 | + 'builddir': _string, |
| 705 | + 'editable-verbose': _bool, |
| 706 | + 'dist-args': _string_or_strings, |
| 707 | + 'setup-args': _string_or_strings, |
| 708 | + 'compile-args': _string_or_strings, |
| 709 | + 'install-args': _string_or_strings, |
| 710 | + } |
| 711 | + assert all(f'{name}-args' in options for name in _MESON_ARGS_KEYS) |
| 712 | + |
| 713 | + config = {} |
| 714 | + for key, value in config_settings.items(): |
| 715 | + parser = options.get(key) |
| 716 | + if parser is None: |
| 717 | + matches = difflib.get_close_matches(key, options.keys(), n=2) |
| 718 | + if matches: |
| 719 | + alternatives = ' or '.join(f'"{match}"' for match in matches) |
| 720 | + raise ConfigError(f'unknown option "{key}". did you mean {alternatives}?') |
| 721 | + else: |
| 722 | + raise ConfigError(f'unknown option "{key}"') |
| 723 | + config[key] = parser(value, key) |
| 724 | + return config |
| 725 | + |
| 726 | + |
688 | 727 | class Project():
|
689 | 728 | """Meson project wrapper to generate Python artifacts."""
|
690 | 729 |
|
@@ -1056,59 +1095,14 @@ def editable(self, directory: Path) -> pathlib.Path:
|
1056 | 1095 | @contextlib.contextmanager
|
1057 | 1096 | def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
|
1058 | 1097 | """Create the project given the given config settings."""
|
1059 |
| - if config_settings is None: |
1060 |
| - config_settings = {} |
1061 |
| - |
1062 |
| - # expand all string values to single element tuples and convert collections to tuple |
1063 |
| - config_settings = { |
1064 |
| - key: tuple(value) if isinstance(value, Collection) and not isinstance(value, str) else (value,) |
1065 |
| - for key, value in config_settings.items() |
1066 |
| - } |
1067 |
| - |
1068 |
| - builddir_value = config_settings.get('builddir', {}) |
1069 |
| - if len(builddir_value) > 0: |
1070 |
| - if len(builddir_value) != 1: |
1071 |
| - raise ConfigError('Only one value for configuration entry "builddir" can be specified') |
1072 |
| - builddir = builddir_value[0] |
1073 |
| - if not isinstance(builddir, str): |
1074 |
| - raise ConfigError(f'Configuration entry "builddir" should be a string not {type(builddir)}') |
1075 |
| - else: |
1076 |
| - builddir = None |
1077 |
| - |
1078 |
| - def _validate_string_collection(key: str) -> None: |
1079 |
| - assert isinstance(config_settings, Mapping) |
1080 |
| - problematic_items: Sequence[Any] = list(filter(None, ( |
1081 |
| - item if not isinstance(item, str) else None |
1082 |
| - for item in config_settings.get(key, ()) |
1083 |
| - ))) |
1084 |
| - if problematic_items: |
1085 |
| - s = ', '.join(f'"{item}" ({type(item)})' for item in problematic_items) |
1086 |
| - raise ConfigError(f'Configuration entries for "{key}" must be strings but contain: {s}') |
1087 |
| - |
1088 |
| - meson_args_keys = _MESON_ARGS_KEYS |
1089 |
| - meson_args_cli_keys = tuple(f'{key}-args' for key in meson_args_keys) |
1090 |
| - |
1091 |
| - for key in config_settings: |
1092 |
| - known_keys = ('builddir', 'editable-verbose', *meson_args_cli_keys) |
1093 |
| - if key not in known_keys: |
1094 |
| - import difflib |
1095 |
| - matches = difflib.get_close_matches(key, known_keys, n=3) |
1096 |
| - if len(matches): |
1097 |
| - alternatives = ' or '.join(f'"{match}"' for match in matches) |
1098 |
| - raise ConfigError(f'Unknown configuration entry "{key}". Did you mean {alternatives}?') |
1099 |
| - else: |
1100 |
| - raise ConfigError(f'Unknown configuration entry "{key}"') |
1101 | 1098 |
|
1102 |
| - for key in meson_args_cli_keys: |
1103 |
| - _validate_string_collection(key) |
| 1099 | + settings = _validate_config_settings(config_settings or {}) |
| 1100 | + meson_args = {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS} |
1104 | 1101 |
|
1105 | 1102 | with Project.with_temp_working_dir(
|
1106 |
| - build_dir=builddir, |
1107 |
| - meson_args=typing.cast(MesonArgs, { |
1108 |
| - key: config_settings.get(f'{key}-args', ()) |
1109 |
| - for key in meson_args_keys |
1110 |
| - }), |
1111 |
| - editable_verbose=bool(config_settings.get('editable-verbose')) |
| 1103 | + build_dir=settings.get('builddir'), |
| 1104 | + meson_args=typing.cast(MesonArgs, meson_args), |
| 1105 | + editable_verbose=bool(settings.get('editable-verbose')) |
1112 | 1106 | ) as project:
|
1113 | 1107 | yield project
|
1114 | 1108 |
|
|
0 commit comments