diff options
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, +) |