|
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 |
@@ -689,6 +690,44 @@ def _strings(value: Any, name: str) -> List[str]: |
689 | 690 | return scheme(table, 'tool.meson-python') |
690 | 691 |
|
691 | 692 |
|
| 693 | +def _validate_config_settings(config_settings: Dict[str, Any]) -> Dict[str, Any]: |
| 694 | + """Validate options received from build frontend.""" |
| 695 | + |
| 696 | + def _string(value: Any, name: str) -> str: |
| 697 | + if not isinstance(value, str): |
| 698 | + raise ConfigError(f'only one value for "{name}" can be specified') |
| 699 | + return value |
| 700 | + |
| 701 | + def _bool(value: Any, name: str) -> bool: |
| 702 | + return True |
| 703 | + |
| 704 | + def _string_or_strings(value: Any, name: str) -> List[str]: |
| 705 | + return list([value,] if isinstance(value, str) else value) |
| 706 | + |
| 707 | + options = { |
| 708 | + 'builddir': _string, |
| 709 | + 'editable-verbose': _bool, |
| 710 | + 'dist-args': _string_or_strings, |
| 711 | + 'setup-args': _string_or_strings, |
| 712 | + 'compile-args': _string_or_strings, |
| 713 | + 'install-args': _string_or_strings, |
| 714 | + } |
| 715 | + assert all(f'{name}-args' in options for name in _MESON_ARGS_KEYS) |
| 716 | + |
| 717 | + config = {} |
| 718 | + for key, value in config_settings.items(): |
| 719 | + parser = options.get(key) |
| 720 | + if parser is None: |
| 721 | + matches = difflib.get_close_matches(key, options.keys(), n=2) |
| 722 | + if matches: |
| 723 | + alternatives = ' or '.join(f'"{match}"' for match in matches) |
| 724 | + raise ConfigError(f'unknown option "{key}". did you mean {alternatives}?') |
| 725 | + else: |
| 726 | + raise ConfigError(f'unknown option "{key}"') |
| 727 | + config[key] = parser(value, key) |
| 728 | + return config |
| 729 | + |
| 730 | + |
692 | 731 | class Project(): |
693 | 732 | """Meson project wrapper to generate Python artifacts.""" |
694 | 733 |
|
@@ -1093,59 +1132,14 @@ def editable(self, directory: Path) -> pathlib.Path: |
1093 | 1132 | @contextlib.contextmanager |
1094 | 1133 | def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]: |
1095 | 1134 | """Create the project given the given config settings.""" |
1096 | | - if config_settings is None: |
1097 | | - config_settings = {} |
1098 | | - |
1099 | | - # expand all string values to single element tuples and convert collections to tuple |
1100 | | - config_settings = { |
1101 | | - key: tuple(value) if isinstance(value, Collection) and not isinstance(value, str) else (value,) |
1102 | | - for key, value in config_settings.items() |
1103 | | - } |
1104 | | - |
1105 | | - builddir_value = config_settings.get('builddir', {}) |
1106 | | - if len(builddir_value) > 0: |
1107 | | - if len(builddir_value) != 1: |
1108 | | - raise ConfigError('Only one value for configuration entry "builddir" can be specified') |
1109 | | - builddir = builddir_value[0] |
1110 | | - if not isinstance(builddir, str): |
1111 | | - raise ConfigError(f'Configuration entry "builddir" should be a string not {type(builddir)}') |
1112 | | - else: |
1113 | | - builddir = None |
1114 | | - |
1115 | | - def _validate_string_collection(key: str) -> None: |
1116 | | - assert isinstance(config_settings, Mapping) |
1117 | | - problematic_items: Sequence[Any] = list(filter(None, ( |
1118 | | - item if not isinstance(item, str) else None |
1119 | | - for item in config_settings.get(key, ()) |
1120 | | - ))) |
1121 | | - if problematic_items: |
1122 | | - s = ', '.join(f'"{item}" ({type(item)})' for item in problematic_items) |
1123 | | - raise ConfigError(f'Configuration entries for "{key}" must be strings but contain: {s}') |
1124 | | - |
1125 | | - meson_args_keys = _MESON_ARGS_KEYS |
1126 | | - meson_args_cli_keys = tuple(f'{key}-args' for key in meson_args_keys) |
1127 | | - |
1128 | | - for key in config_settings: |
1129 | | - known_keys = ('builddir', 'editable-verbose', *meson_args_cli_keys) |
1130 | | - if key not in known_keys: |
1131 | | - import difflib |
1132 | | - matches = difflib.get_close_matches(key, known_keys, n=3) |
1133 | | - if len(matches): |
1134 | | - alternatives = ' or '.join(f'"{match}"' for match in matches) |
1135 | | - raise ConfigError(f'Unknown configuration entry "{key}". Did you mean {alternatives}?') |
1136 | | - else: |
1137 | | - raise ConfigError(f'Unknown configuration entry "{key}"') |
1138 | 1135 |
|
1139 | | - for key in meson_args_cli_keys: |
1140 | | - _validate_string_collection(key) |
| 1136 | + settings = _validate_config_settings(config_settings or {}) |
| 1137 | + meson_args = {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS} |
1141 | 1138 |
|
1142 | 1139 | with Project.with_temp_working_dir( |
1143 | | - build_dir=builddir, |
1144 | | - meson_args=typing.cast(MesonArgs, { |
1145 | | - key: config_settings.get(f'{key}-args', ()) |
1146 | | - for key in meson_args_keys |
1147 | | - }), |
1148 | | - editable_verbose=bool(config_settings.get('editable-verbose')) |
| 1140 | + build_dir=settings.get('builddir'), |
| 1141 | + meson_args=typing.cast(MesonArgs, meson_args), |
| 1142 | + editable_verbose=bool(settings.get('editable-verbose')) |
1149 | 1143 | ) as project: |
1150 | 1144 | yield project |
1151 | 1145 |
|
|
0 commit comments