|
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