diff options
author | Dylan Baker <dylan@pnwbakers.com> | 2021-08-10 20:07:39 -0700 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-09-30 21:01:38 +0200 |
commit | c3c30d4b060239654c9b848092692ab346ebed9d (patch) | |
tree | 52b2ebd5e5c788fb529dac1717051093311ca255 /mesonbuild/interpreter | |
parent | bb706231bd3bfd8983f1a5df24111efe1ad0734d (diff) | |
download | meson-c3c30d4b060239654c9b848092692ab346ebed9d.zip meson-c3c30d4b060239654c9b848092692ab346ebed9d.tar.gz meson-c3c30d4b060239654c9b848092692ab346ebed9d.tar.bz2 |
interpreter: Use typed_kwargs for func_custom_target
This does not convert the build side, or remove any of the checking it
does. We still need that for other callers of custom target. What we'll
do for those is add an internal interface that defaults things, then
we'll be able to have those callers do their own validation, and the
CustomTarget validation machinary can be removed.
Fixes #9096
Diffstat (limited to 'mesonbuild/interpreter')
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 90 | ||||
-rw-r--r-- | mesonbuild/interpreter/kwargs.py | 26 | ||||
-rw-r--r-- | mesonbuild/interpreter/type_checking.py | 46 |
3 files changed, 138 insertions, 24 deletions
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 78c8f95..c5997f2 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -52,11 +52,12 @@ from .interpreterobjects import ( from .type_checking import ( COMMAND_KW, DEPENDS_KW, + DEPEND_FILES_KW, DEPFILE_KW, ENV_KW, INSTALL_MODE_KW, LANGUAGE_KW, - NATIVE_KW, + NATIVE_KW, OVERRIDE_OPTIONS_KW, REQUIRED_KW, NoneType, in_set_validator, @@ -89,6 +90,18 @@ if T.TYPE_CHECKING: build.GeneratedList] +def _output_validator(outputs: T.List[str]) -> T.Optional[str]: + for i in outputs: + if i == '': + return 'Output must not be empty.' + elif i.strip() == '': + return 'Output must not consist only of whitespace.' + elif has_path_sep(i): + return f'Output {i!r} must not contain a path segment.' + + return None + + def stringifyUserArguments(args, quote=False): if isinstance(args, list): return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args]) @@ -1626,20 +1639,71 @@ external dependencies (including libraries) must go to "dependencies".''') def func_subdir_done(self, node, args, kwargs): raise SubdirDoneRequest() - @FeatureNewKwargs('custom_target', '0.60.0', ['install_tag']) - @FeatureNewKwargs('custom_target', '0.57.0', ['env']) - @FeatureNewKwargs('custom_target', '0.48.0', ['console']) - @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale']) - @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default']) - @FeatureNewKwargs('custom_target', '0.59.0', ['feed']) - @permittedKwargs({'input', 'output', 'command', 'install', 'install_dir', 'install_mode', - 'build_always', 'capture', 'depends', 'depend_files', 'depfile', - 'build_by_default', 'build_always_stale', 'console', 'env', - 'feed', 'install_tag'}) @typed_pos_args('custom_target', optargs=[str]) - def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[T.Optional[str]], kwargs: 'TYPE_kwargs') -> build.CustomTarget: - if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): + @typed_kwargs( + 'custom_target', + COMMAND_KW, + DEPEND_FILES_KW, + DEPENDS_KW, + DEPFILE_KW, + ENV_KW.evolve(since='0.57.0'), + INSTALL_MODE_KW.evolve(since='0.47.0'), + OVERRIDE_OPTIONS_KW, + KwargInfo('build_by_default', (bool, type(None)), since='0.40.0'), + KwargInfo('build_always', (bool, type(None)), deprecated='0.47.0'), + KwargInfo('build_always_stale', (bool, type(None)), since='0.47.0'), + KwargInfo('feed', bool, default=False, since='0.59.0'), + KwargInfo('capture', bool, default=False), + KwargInfo('console', bool, default=False, since='0.48.0'), + KwargInfo('install', bool, default=False), + KwargInfo('install_dir', ContainerTypeInfo(list, (str, bool)), listify=True, default=[]), + KwargInfo( + 'output', + ContainerTypeInfo(list, str, allow_empty=False), + listify=True, + required=True, + default=[], + validator=_output_validator, + ), + KwargInfo( + 'input', + ContainerTypeInfo(list, (str, mesonlib.File, ExternalProgram, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, build.ExtractedObjects, build.GeneratedList)), + listify=True, + default=[], + ), + KwargInfo('install_tag', ContainerTypeInfo(list, (str, bool)), listify=True, default=[], since='0.60.0'), + ) + def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], + kwargs: 'kwargs.CustomTarget') -> build.CustomTarget: + if kwargs['depfile'] and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) + + # Don't mutate the kwargs + kwargs = kwargs.copy() + + # Remap build_always to build_by_default and build_always_stale + if kwargs['build_always'] is not None and kwargs['build_always_stale'] is not None: + raise InterpreterException('CustomTarget: "build_always" and "build_always_stale" are mutually exclusive') + + if kwargs['build_by_default'] is None and kwargs['install']: + kwargs['build_by_default'] = True + + elif kwargs['build_always'] is not None: + if kwargs['build_by_default'] is None: + kwargs['build_by_default'] = kwargs['build_always'] + kwargs['build_always_stale'] = kwargs['build_by_default'] + + # Set this to None to satisfy process_kwargs + kwargs['build_always'] = None + + # These are are nullaable so that we can konw whether they're explicitly + # set or not. If they haven't been overwritten, set them to their true + # default + if kwargs['build_by_default'] is None: + kwargs['build_by_default'] = False + if kwargs['build_always_stale'] is None: + kwargs['build_always_stale'] = False + return self._func_custom_target_impl(node, args, kwargs) def _func_custom_target_impl(self, node, args, kwargs): diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 4281ee4..2229984 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -10,7 +10,7 @@ from typing_extensions import TypedDict, Literal from .. import build from .. import coredata -from ..mesonlib import MachineChoice, File, FileMode, FileOrString +from ..mesonlib import MachineChoice, File, FileMode, FileOrString, OptionKey from ..programs import ExternalProgram @@ -162,3 +162,27 @@ class RunTarget(TypedDict): command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, File]] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] env: build.EnvironmentVariables + + +class CustomTarget(TypedDict): + + build_always: bool + build_always_stale: bool + build_by_default: bool + capture: bool + command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, + build.CustomTargetIndex, ExternalProgram, File]] + consonle: bool + depend_files: T.List[FileOrString] + depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] + depfile: T.Optional[str] + env: build.EnvironmentVariables + feed: bool + input: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, + build.ExtractedObjects, build.GeneratedList, ExternalProgram, File]] + install: bool + install_dir: T.List[T.Union[str, bool]] + install_mode: FileMode + install_tag: T.List[T.Union[str, bool]] + output: T.List[str] + override_options: T.Dict[OptionKey, str] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index fb63fca..d8f1030 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -6,11 +6,11 @@ import typing as T from .. import compilers -from ..build import EnvironmentVariables, CustomTarget, BuildTarget +from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex from ..coredata import UserFeatureOption from ..interpreterbase import TYPE_var from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo -from ..mesonlib import File, FileMode, MachineChoice, listify, has_path_sep +from ..mesonlib import File, FileMode, MachineChoice, listify, has_path_sep, OptionKey from ..programs import ExternalProgram # Helper definition for type checks that are `Optional[T]` @@ -158,13 +158,21 @@ def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Di return None -def _env_convertor(value: T.Union[EnvironmentVariables, T.List[str], T.Dict[str, str], str, None]) -> EnvironmentVariables: - def splitter(input: str) -> T.Tuple[str, str]: - a, b = input.split('=', 1) - return (a.strip(), b.strip()) - if isinstance(value, (str, list)): - return EnvironmentVariables(dict(splitter(v) for v in listify(value))) +def split_equal_string(input: str) -> T.Tuple[str, str]: + """Split a string in the form `x=y` + + This assumes that the string has already been validated to split properly. + """ + a, b = input.split('=', 1) + return (a, b) + + +def _env_convertor(value: T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], T.Dict[str, str], str, None]) -> EnvironmentVariables: + if isinstance(value, str): + return EnvironmentVariables(dict([split_equal_string(value)])) + elif isinstance(value, list): + return EnvironmentVariables(dict(split_equal_string(v) for v in listify(value))) elif isinstance(value, dict): return EnvironmentVariables(value) elif value is None: @@ -199,11 +207,29 @@ DEPEND_FILES_KW: KwargInfo[T.List[T.Union[str, File]]] = KwargInfo( default=[], ) -COMMAND_KW: KwargInfo[T.List[T.Union[BuildTarget, CustomTarget, ExternalProgram, File]]] = KwargInfo( +COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File]]] = KwargInfo( 'command', # TODO: should accept CustomTargetIndex as well? - ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, ExternalProgram, File), allow_empty=False), + ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File), allow_empty=False), required=True, listify=True, default=[], ) + +def _override_options_convertor(raw: T.List[str]) -> T.Dict[OptionKey, str]: + output: T.Dict[OptionKey, str] = {} + for each in raw: + k, v = split_equal_string(each) + output[OptionKey.from_string(k)] = v + return output + + +OVERRIDE_OPTIONS_KW: KwargInfo[T.List[str]] = KwargInfo( + 'override_options', + ContainerTypeInfo(list, str), + listify=True, + default=[], + # Reusing the env validator is a littl overkill, but nicer than duplicating the code + validator=_env_validator, + convertor=_override_options_convertor, +) |