blob: 03091bb7cb3eda2df637e2efe35fbf6bced43192 [file] [log] [blame]
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2021 Intel Corporation
"""Helpers for strict type checking."""
import typing as T
from .. import compilers
from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList
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, OptionKey
from ..programs import ExternalProgram
# Helper definition for type checks that are `Optional[T]`
NoneType: T.Type[None] = type(None)
def in_set_validator(choices: T.Set[str]) -> T.Callable[[str], T.Optional[str]]:
"""Check that the choice given was one of the given set."""
def inner(check: str) -> T.Optional[str]:
if check not in choices:
return f"must be one of {', '.join(sorted(choices))}, not {check}"
return None
return inner
def _language_validator(l: T.List[str]) -> T.Optional[str]:
"""Validate language keyword argument.
Particularly for functions like `add_compiler()`, and `add_*_args()`
"""
diff = {a.lower() for a in l}.difference(compilers.all_languages)
if diff:
return f'unknown languages: {", ".join(diff)}'
return None
def _install_mode_validator(mode: T.List[T.Union[str, bool, int]]) -> T.Optional[str]:
"""Validate the `install_mode` keyword argument.
This is a rather odd thing, it's a scalar, or an array of 3 values in the form:
[(str | False), (str | int | False) = False, (str | int | False) = False]
where the second and third components are not required and default to False.
"""
if not mode:
return None
if True in mode:
return 'components can only be permission strings, numbers, or False'
if len(mode) > 3:
return 'may have at most 3 elements'
perms = mode[0]
if not isinstance(perms, (str, bool)):
return 'first component must be a permissions string or False'
if isinstance(perms, str):
if not len(perms) == 9:
return ('permissions string must be exactly 9 characters in the form rwxr-xr-x,'
f' got {len(perms)}')
for i in [0, 3, 6]:
if perms[i] not in {'-', 'r'}:
return f'permissions character {i+1} must be "-" or "r", not {perms[i]}'
for i in [1, 4, 7]:
if perms[i] not in {'-', 'w'}:
return f'permissions character {i+1} must be "-" or "w", not {perms[i]}'
for i in [2, 5]:
if perms[i] not in {'-', 'x', 's', 'S'}:
return f'permissions character {i+1} must be "-", "s", "S", or "x", not {perms[i]}'
if perms[8] not in {'-', 'x', 't', 'T'}:
return f'permission character 9 must be "-", "t", "T", or "x", not {perms[8]}'
if len(mode) >= 2 and not isinstance(mode[1], (int, str, bool)):
return 'second componenent can only be a string, number, or False'
if len(mode) >= 3 and not isinstance(mode[2], (int, str, bool)):
return 'third componenent can only be a string, number, or False'
return None
def _install_mode_convertor(mode: T.Optional[T.List[T.Union[str, bool, int]]]) -> FileMode:
"""Convert the DSL form of the `install_mode` keyword argument to `FileMode`
This is not required, and if not required returns None
TODO: It's not clear to me why this needs to be None and not just return an
empty FileMode.
"""
# this has already been validated by the validator
return FileMode(*(m if isinstance(m, str) else None for m in mode))
def _lower_strlist(input: T.List[str]) -> T.List[str]:
"""Lower a list of strings.
mypy (but not pyright) gets confused about using a lambda as the convertor function
"""
return [i.lower() for i in input]
NATIVE_KW = KwargInfo(
'native', bool,
default=False,
convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST)
LANGUAGE_KW = KwargInfo(
'language', ContainerTypeInfo(list, str, allow_empty=False),
listify=True,
required=True,
validator=_language_validator,
convertor=_lower_strlist)
INSTALL_MODE_KW: KwargInfo[T.List[T.Union[str, bool, int]]] = KwargInfo(
'install_mode',
ContainerTypeInfo(list, (str, bool, int)),
listify=True,
default=[],
validator=_install_mode_validator,
convertor=_install_mode_convertor,
)
REQUIRED_KW: KwargInfo[T.Union[bool, UserFeatureOption]] = KwargInfo(
'required',
(bool, UserFeatureOption),
default=True,
# TODO: extract_required_kwarg could be converted to a convertor
)
def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None]) -> T.Optional[str]:
def _splitter(v: str) -> T.Optional[str]:
split = v.split('=', 1)
if len(split) == 1:
return f'"{v}" is not two string values separated by an "="'
return None
if isinstance(value, str):
v = _splitter(value)
if v is not None:
return v
elif isinstance(value, list):
for i in listify(value):
if not isinstance(i, str):
return f"All array elements must be a string, not {i!r}"
v = _splitter(i)
if v is not None:
return v
elif isinstance(value, dict):
# We don't need to spilt here, just do the type checking
for k, dv in value.items():
if not isinstance(dv, str):
return f"Dictionary element {k} must be a string not {dv!r}"
# We know that otherwise we have an EnvironmentVariables object or None, and
# we're okay at this point
return None
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:
return EnvironmentVariables()
return value
ENV_KW: KwargInfo[T.Union[EnvironmentVariables, T.List, T.Dict, str, None]] = KwargInfo(
'env',
(EnvironmentVariables, list, dict, str, NoneType),
validator=_env_validator,
convertor=_env_convertor,
)
DEPFILE_KW: KwargInfo[T.Optional[str]] = KwargInfo(
'depfile',
(str, type(None)),
validator=lambda x: 'Depfile must be a plain filename with a subdirectory' if has_path_sep(x) else None
)
DEPENDS_KW: KwargInfo[T.List[T.Union[BuildTarget, CustomTarget]]] = KwargInfo(
'depends',
ContainerTypeInfo(list, (BuildTarget, CustomTarget)),
listify=True,
default=[],
)
DEPEND_FILES_KW: KwargInfo[T.List[T.Union[str, File]]] = KwargInfo(
'depend_files',
ContainerTypeInfo(list, (File, str)),
listify=True,
default=[],
)
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, 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,
)
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
CT_OUTPUT_KW: KwargInfo[T.List[str]] = KwargInfo(
'output',
ContainerTypeInfo(list, str, allow_empty=False),
listify=True,
required=True,
default=[],
validator=_output_validator,
)
CT_INPUT_KW: KwargInfo[T.List[T.Union[str, File, ExternalProgram, BuildTarget, CustomTarget, CustomTargetIndex, ExtractedObjects, GeneratedList]]] = KwargInfo(
'input',
ContainerTypeInfo(list, (str, File, ExternalProgram, BuildTarget, CustomTarget, CustomTargetIndex, ExtractedObjects, GeneratedList)),
listify=True,
default=[],
)
CT_INSTALL_TAG_KW: KwargInfo[T.List[T.Union[str, bool]]] = KwargInfo(
'install_tag',
ContainerTypeInfo(list, (str, bool)),
listify=True,
default=[],
since='0.60.0',
)
INSTALL_KW = KwargInfo('install', bool, default=False)
CT_INSTALL_DIR_KW: KwargInfo[T.List[T.Union[str, bool]]] = KwargInfo(
'install_dir',
ContainerTypeInfo(list, (str, bool)),
listify=True,
default=[],
)
CT_BUILD_BY_DEFAULT: KwargInfo[T.Optional[bool]] = KwargInfo('build_by_default', (bool, type(None)), since='0.40.0')