diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
commit | 7b6e527f440cd7e6f8be2b07cee320ee6ca18786 (patch) | |
tree | 4a2738d69fa2814659fdadddf5826282e73d81f4 /mesonbuild/interpreter/interpreterobjects.py | |
parent | Initial commit. (diff) | |
download | meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.tar.xz meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.zip |
Adding upstream version 1.0.1.upstream/1.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mesonbuild/interpreter/interpreterobjects.py')
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py new file mode 100644 index 0000000..538d134 --- /dev/null +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -0,0 +1,987 @@ +from __future__ import annotations +import os +import shlex +import subprocess +import copy +import textwrap + +from pathlib import Path, PurePath + +from .. import mesonlib +from .. import coredata +from .. import build +from .. import mlog + +from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule +from ..backend.backends import TestProtocol +from ..interpreterbase import ( + ContainerTypeInfo, KwargInfo, MesonOperator, + MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, + FeatureNew, FeatureDeprecated, + typed_pos_args, typed_kwargs, typed_operator, + noArgsFlattening, noPosargs, noKwargs, unholder_return, + flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode) +from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW +from ..dependencies import Dependency, ExternalLibrary, InternalDependency +from ..programs import ExternalProgram +from ..mesonlib import HoldableObject, OptionKey, listify, Popen_safe + +import typing as T + +if T.TYPE_CHECKING: + from . import kwargs + from ..cmake.interpreter import CMakeInterpreter + from ..envconfig import MachineInfo + from ..interpreterbase import FeatureCheckBase, InterpreterObject, SubProject, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs + from .interpreter import Interpreter + + from typing_extensions import TypedDict + + class EnvironmentSeparatorKW(TypedDict): + + separator: str + + +def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', + subproject: 'SubProject', + feature_check: T.Optional[FeatureCheckBase] = None, + default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]: + val = kwargs.get('required', default) + disabled = False + required = False + feature: T.Optional[str] = None + if isinstance(val, coredata.UserFeatureOption): + if not feature_check: + feature_check = FeatureNew('User option "feature"', '0.47.0') + feature_check.use(subproject) + feature = val.name + if val.is_disabled(): + disabled = True + elif val.is_enabled(): + required = True + elif isinstance(val, bool): + required = val + else: + raise InterpreterException('required keyword argument must be boolean or a feature option') + + # Keep boolean value in kwargs to simplify other places where this kwarg is + # checked. + # TODO: this should be removed, and those callers should learn about FeatureOptions + kwargs['required'] = required + + return disabled, required, feature + +def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]: + search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', [])) + search_dirs = [Path(d).expanduser() for d in search_dirs_str] + for d in search_dirs: + if mesonlib.is_windows() and d.root.startswith('\\'): + # a Unix-path starting with `/` that is not absolute on Windows. + # discard without failing for end-user ease of cross-platform directory arrays + continue + if not d.is_absolute(): + raise InvalidCode(f'Search directory {d} is not an absolute path.') + return [str(s) for s in search_dirs] + +class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): + def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'): + super().__init__(option, interpreter) + if option and option.is_auto(): + # TODO: we need to cast here because options is not a TypedDict + auto = T.cast('coredata.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) + self.held_object = copy.copy(auto) + self.held_object.name = option.name + self.methods.update({'enabled': self.enabled_method, + 'disabled': self.disabled_method, + 'allowed': self.allowed_method, + 'auto': self.auto_method, + 'require': self.require_method, + 'disable_auto_if': self.disable_auto_if_method, + }) + + @property + def value(self) -> str: + return 'disabled' if not self.held_object else self.held_object.value + + def as_disabled(self) -> coredata.UserFeatureOption: + disabled = copy.deepcopy(self.held_object) + disabled.value = 'disabled' + return disabled + + @noPosargs + @noKwargs + def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.value == 'enabled' + + @noPosargs + @noKwargs + def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.value == 'disabled' + + @noPosargs + @noKwargs + @FeatureNew('feature_option.allowed()', '0.59.0') + def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.value != 'disabled' + + @noPosargs + @noKwargs + def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.value == 'auto' + + @FeatureNew('feature_option.require()', '0.59.0') + @typed_pos_args('feature_option.require', bool) + @typed_kwargs( + 'feature_option.require', + KwargInfo('error_message', (str, NoneType)) + ) + def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + if args[0]: + return copy.deepcopy(self.held_object) + + if self.value == 'enabled': + err_msg = f'Feature {self.held_object.name} cannot be enabled' + if kwargs['error_message']: + err_msg += f': {kwargs["error_message"]}' + raise InterpreterException(err_msg) + return self.as_disabled() + + @FeatureNew('feature_option.disable_auto_if()', '0.59.0') + @noKwargs + @typed_pos_args('feature_option.disable_auto_if', bool) + def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: + return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() + + +class RunProcess(MesonInterpreterObject): + + def __init__(self, + cmd: ExternalProgram, + args: T.List[str], + env: build.EnvironmentVariables, + source_dir: str, + build_dir: str, + subdir: str, + mesonintrospect: T.List[str], + in_builddir: bool = False, + check: bool = False, + capture: bool = True) -> None: + super().__init__() + if not isinstance(cmd, ExternalProgram): + raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') + self.capture = capture + self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) + self.methods.update({'returncode': self.returncode_method, + 'stdout': self.stdout_method, + 'stderr': self.stderr_method, + }) + + def run_command(self, + cmd: ExternalProgram, + args: T.List[str], + env: build.EnvironmentVariables, + source_dir: str, + build_dir: str, + subdir: str, + mesonintrospect: T.List[str], + in_builddir: bool, + check: bool = False) -> T.Tuple[int, str, str]: + command_array = cmd.get_command() + args + menv = {'MESON_SOURCE_ROOT': source_dir, + 'MESON_BUILD_ROOT': build_dir, + 'MESON_SUBDIR': subdir, + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]), + } + if in_builddir: + cwd = os.path.join(build_dir, subdir) + else: + cwd = os.path.join(source_dir, subdir) + child_env = os.environ.copy() + child_env.update(menv) + child_env = env.get_env(child_env) + stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL + mlog.debug('Running command:', mesonlib.join_args(command_array)) + try: + p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd) + if self.capture: + mlog.debug('--- stdout ---') + mlog.debug(o) + else: + o = '' + mlog.debug('--- stdout disabled ---') + mlog.debug('--- stderr ---') + mlog.debug(e) + mlog.debug('') + + if check and p.returncode != 0: + raise InterpreterException('Command `{}` failed with status {}.'.format(mesonlib.join_args(command_array), p.returncode)) + + return p.returncode, o, e + except FileNotFoundError: + raise InterpreterException('Could not execute command `%s`.' % mesonlib.join_args(command_array)) + + @noPosargs + @noKwargs + def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + return self.returncode + + @noPosargs + @noKwargs + def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.stdout + + @noPosargs + @noKwargs + def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.stderr + +class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject): + + def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'): + super().__init__(obj, interpreter) + self.methods.update({'set': self.set_method, + 'append': self.append_method, + 'prepend': self.prepend_method, + }) + + def __repr__(self) -> str: + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.held_object.envvars) + + def __deepcopy__(self, memo: T.Dict[str, object]) -> 'EnvironmentVariablesHolder': + # Avoid trying to copy the interpreter + return EnvironmentVariablesHolder(copy.deepcopy(self.held_object), self.interpreter) + + def warn_if_has_name(self, name: str) -> None: + # Multiple append/prepend operations was not supported until 0.58.0. + if self.held_object.has_name(name): + m = f'Overriding previous value of environment variable {name!r} with a new one' + FeatureNew(m, '0.58.0').use(self.subproject, self.current_node) + + @typed_pos_args('environment.set', str, varargs=str, min_varargs=1) + @typed_kwargs('environment.set', ENV_SEPARATOR_KW) + def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: + name, values = args + self.held_object.set(name, values, kwargs['separator']) + + @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) + @typed_kwargs('environment.append', ENV_SEPARATOR_KW) + def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: + name, values = args + self.warn_if_has_name(name) + self.held_object.append(name, values, kwargs['separator']) + + @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) + @typed_kwargs('environment.prepend', ENV_SEPARATOR_KW) + def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: + name, values = args + self.warn_if_has_name(name) + self.held_object.prepend(name, values, kwargs['separator']) + + +_CONF_DATA_SET_KWS: KwargInfo[T.Optional[str]] = KwargInfo('description', (str, NoneType)) + + +class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInterpreterObject): + + def __init__(self, obj: build.ConfigurationData, interpreter: 'Interpreter'): + super().__init__(obj, interpreter) + self.methods.update({'set': self.set_method, + 'set10': self.set10_method, + 'set_quoted': self.set_quoted_method, + 'has': self.has_method, + 'get': self.get_method, + 'keys': self.keys_method, + 'get_unquoted': self.get_unquoted_method, + 'merge_from': self.merge_from_method, + }) + + def __deepcopy__(self, memo: T.Dict) -> 'ConfigurationDataHolder': + return ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter) + + def is_used(self) -> bool: + return self.held_object.used + + def __check_used(self) -> None: + if self.is_used(): + raise InterpreterException("Can not set values on configuration object that has been used.") + + @typed_pos_args('configuration_data.set', str, (str, int, bool)) + @typed_kwargs('configuration_data.set', _CONF_DATA_SET_KWS) + def set_method(self, args: T.Tuple[str, T.Union[str, int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: + self.__check_used() + self.held_object.values[args[0]] = (args[1], kwargs['description']) + + @typed_pos_args('configuration_data.set_quoted', str, str) + @typed_kwargs('configuration_data.set_quoted', _CONF_DATA_SET_KWS) + def set_quoted_method(self, args: T.Tuple[str, str], kwargs: 'kwargs.ConfigurationDataSet') -> None: + self.__check_used() + escaped_val = '\\"'.join(args[1].split('"')) + self.held_object.values[args[0]] = (f'"{escaped_val}"', kwargs['description']) + + @typed_pos_args('configuration_data.set10', str, (int, bool)) + @typed_kwargs('configuration_data.set10', _CONF_DATA_SET_KWS) + def set10_method(self, args: T.Tuple[str, T.Union[int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: + self.__check_used() + # bool is a subclass of int, so we need to check for bool explicitly. + # We already have typed_pos_args checking that this is either a bool or + # an int. + if not isinstance(args[1], bool): + mlog.deprecation('configuration_data.set10 with number. the `set10` ' + 'method should only be used with booleans', + location=self.interpreter.current_node) + if args[1] < 0: + mlog.warning('Passing a number that is less than 0 may not have the intended result, ' + 'as meson will treat all non-zero values as true.', + location=self.interpreter.current_node) + self.held_object.values[args[0]] = (int(args[1]), kwargs['description']) + + @typed_pos_args('configuration_data.has', (str, int, bool)) + @noKwargs + def has_method(self, args: T.Tuple[T.Union[str, int, bool]], kwargs: TYPE_kwargs) -> bool: + return args[0] in self.held_object.values + + @FeatureNew('configuration_data.get()', '0.38.0') + @typed_pos_args('configuration_data.get', str, optargs=[(str, int, bool)]) + @noKwargs + def get_method(self, args: T.Tuple[str, T.Optional[T.Union[str, int, bool]]], + kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: + name = args[0] + if name in self.held_object: + return self.held_object.get(name)[0] + elif args[1] is not None: + return args[1] + raise InterpreterException(f'Entry {name} not in configuration data.') + + @FeatureNew('configuration_data.get_unquoted()', '0.44.0') + @typed_pos_args('configuration_data.get_unquoted', str, optargs=[(str, int, bool)]) + @noKwargs + def get_unquoted_method(self, args: T.Tuple[str, T.Optional[T.Union[str, int, bool]]], + kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: + name = args[0] + if name in self.held_object: + val = self.held_object.get(name)[0] + elif args[1] is not None: + val = args[1] + else: + raise InterpreterException(f'Entry {name} not in configuration data.') + if isinstance(val, str) and val[0] == '"' and val[-1] == '"': + return val[1:-1] + return val + + def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: + return self.held_object.values[name] + + @FeatureNew('configuration_data.keys()', '0.57.0') + @noPosargs + @noKwargs + def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: + return sorted(self.keys()) + + def keys(self) -> T.List[str]: + return list(self.held_object.values.keys()) + + @typed_pos_args('configuration_data.merge_from', build.ConfigurationData) + @noKwargs + def merge_from_method(self, args: T.Tuple[build.ConfigurationData], kwargs: TYPE_kwargs) -> None: + from_object = args[0] + self.held_object.values.update(from_object.values) + + +_PARTIAL_DEP_KWARGS = [ + KwargInfo('compile_args', bool, default=False), + KwargInfo('link_args', bool, default=False), + KwargInfo('links', bool, default=False), + KwargInfo('includes', bool, default=False), + KwargInfo('sources', bool, default=False), +] + +class DependencyHolder(ObjectHolder[Dependency]): + def __init__(self, dep: Dependency, interpreter: 'Interpreter'): + super().__init__(dep, interpreter) + self.methods.update({'found': self.found_method, + 'type_name': self.type_name_method, + 'version': self.version_method, + 'name': self.name_method, + 'get_pkgconfig_variable': self.pkgconfig_method, + 'get_configtool_variable': self.configtool_method, + 'get_variable': self.variable_method, + 'partial_dependency': self.partial_dependency_method, + 'include_type': self.include_type_method, + 'as_system': self.as_system_method, + 'as_link_whole': self.as_link_whole_method, + }) + + def found(self) -> bool: + return self.found_method([], {}) + + @noPosargs + @noKwargs + def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.type_name + + @noPosargs + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + if self.held_object.type_name == 'internal': + return True + return self.held_object.found() + + @noPosargs + @noKwargs + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_version() + + @noPosargs + @noKwargs + def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_name() + + @FeatureDeprecated('dependency.get_pkgconfig_variable', '0.56.0', + 'use dependency.get_variable(pkgconfig : ...) instead') + @typed_pos_args('dependency.get_pkgconfig_variable', str) + @typed_kwargs( + 'dependency.get_pkgconfig_variable', + KwargInfo('default', (str, NoneType)), + KwargInfo( + 'define_variable', + ContainerTypeInfo(list, str, pairs=True), + default=[], + listify=True, + validator=lambda x: 'must be of length 2 or empty' if len(x) not in {0, 2} else None, + ), + ) + def pkgconfig_method(self, args: T.Tuple[str], kwargs: 'kwargs.DependencyPkgConfigVar') -> str: + return self.held_object.get_pkgconfig_variable(args[0], **kwargs) + + @FeatureNew('dependency.get_configtool_variable', '0.44.0') + @FeatureDeprecated('dependency.get_configtool_variable', '0.56.0', + 'use dependency.get_variable(configtool : ...) instead') + @noKwargs + @typed_pos_args('dependency.get_config_tool_variable', str) + def configtool_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_configtool_variable(args[0]) + + @FeatureNew('dependency.partial_dependency', '0.46.0') + @noPosargs + @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) + def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: + pdep = self.held_object.get_partial_dependency(**kwargs) + return pdep + + @FeatureNew('dependency.get_variable', '0.51.0') + @typed_pos_args('dependency.get_variable', optargs=[str]) + @typed_kwargs( + 'dependency.get_variable', + KwargInfo('cmake', (str, NoneType)), + KwargInfo('pkgconfig', (str, NoneType)), + KwargInfo('configtool', (str, NoneType)), + KwargInfo('internal', (str, NoneType), since='0.54.0'), + KwargInfo('default_value', (str, NoneType)), + KwargInfo('pkgconfig_define', ContainerTypeInfo(list, str, pairs=True), default=[], listify=True), + ) + def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.DependencyGetVariable') -> str: + default_varname = args[0] + if default_varname is not None: + FeatureNew('Positional argument to dependency.get_variable()', '0.58.0').use(self.subproject, self.current_node) + return self.held_object.get_variable( + cmake=kwargs['cmake'] or default_varname, + pkgconfig=kwargs['pkgconfig'] or default_varname, + configtool=kwargs['configtool'] or default_varname, + internal=kwargs['internal'] or default_varname, + default_value=kwargs['default_value'], + pkgconfig_define=kwargs['pkgconfig_define'], + ) + + @FeatureNew('dependency.include_type', '0.52.0') + @noPosargs + @noKwargs + def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_include_type() + + @FeatureNew('dependency.as_system', '0.52.0') + @noKwargs + @typed_pos_args('dependency.as_system', optargs=[str]) + def as_system_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> Dependency: + return self.held_object.generate_system_dependency(args[0] or 'system') + + @FeatureNew('dependency.as_link_whole', '0.56.0') + @noKwargs + @noPosargs + def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: + if not isinstance(self.held_object, InternalDependency): + raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects') + new_dep = self.held_object.generate_link_whole_dependency() + return new_dep + +class ExternalProgramHolder(ObjectHolder[ExternalProgram]): + def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None: + super().__init__(ep, interpreter) + self.methods.update({'found': self.found_method, + 'path': self.path_method, + 'version': self.version_method, + 'full_path': self.full_path_method}) + + @noPosargs + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.found() + + @noPosargs + @noKwargs + @FeatureDeprecated('ExternalProgram.path', '0.55.0', + 'use ExternalProgram.full_path() instead') + def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._full_path() + + @noPosargs + @noKwargs + @FeatureNew('ExternalProgram.full_path', '0.55.0') + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._full_path() + + def _full_path(self) -> str: + if not self.found(): + raise InterpreterException('Unable to get the path of a not-found external program') + path = self.held_object.get_path() + assert path is not None + return path + + @noPosargs + @noKwargs + @FeatureNew('ExternalProgram.version', '0.62.0') + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + if not self.found(): + raise InterpreterException('Unable to get the version of a not-found external program') + try: + return self.held_object.get_version(self.interpreter) + except mesonlib.MesonException: + return 'unknown' + + def found(self) -> bool: + return self.held_object.found() + +class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): + def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'): + super().__init__(el, interpreter) + self.methods.update({'found': self.found_method, + 'type_name': self.type_name_method, + 'partial_dependency': self.partial_dependency_method, + }) + + @noPosargs + @noKwargs + def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.type_name + + @noPosargs + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object.found() + + @FeatureNew('dependency.partial_dependency', '0.46.0') + @noPosargs + @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) + def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: + pdep = self.held_object.get_partial_dependency(**kwargs) + return pdep + +# A machine that's statically known from the cross file +class MachineHolder(ObjectHolder['MachineInfo']): + def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'): + super().__init__(machine_info, interpreter) + self.methods.update({'system': self.system_method, + 'cpu': self.cpu_method, + 'cpu_family': self.cpu_family_method, + 'endian': self.endian_method, + }) + + @noPosargs + @noKwargs + def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.cpu_family + + @noPosargs + @noKwargs + def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.cpu + + @noPosargs + @noKwargs + def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.system + + @noPosargs + @noKwargs + def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.endian + +class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]): + pass + +class FileHolder(ObjectHolder[mesonlib.File]): + pass + +class HeadersHolder(ObjectHolder[build.Headers]): + pass + +class DataHolder(ObjectHolder[build.Data]): + pass + +class SymlinkDataHolder(ObjectHolder[build.SymlinkData]): + pass + +class InstallDirHolder(ObjectHolder[build.InstallDir]): + pass + +class ManHolder(ObjectHolder[build.Man]): + pass + +class EmptyDirHolder(ObjectHolder[build.EmptyDir]): + pass + +class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]): + pass + +class Test(MesonInterpreterObject): + def __init__(self, name: str, project: str, suite: T.List[str], + exe: T.Union[ExternalProgram, build.Executable, build.CustomTarget], + depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], + is_parallel: bool, + cmd_args: T.List[T.Union[str, mesonlib.File, build.Target]], + env: build.EnvironmentVariables, + should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, + priority: int, verbose: bool): + super().__init__() + self.name = name + self.suite = listify(suite) + self.project_name = project + self.exe = exe + self.depends = depends + self.is_parallel = is_parallel + self.cmd_args = cmd_args + self.env = env + self.should_fail = should_fail + self.timeout = timeout + self.workdir = workdir + self.protocol = TestProtocol.from_str(protocol) + self.priority = priority + self.verbose = verbose + + def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarget]: + return self.exe + + def get_name(self) -> str: + return self.name + +class NullSubprojectInterpreter(HoldableObject): + pass + +# TODO: This should really be an `ObjectHolder`, but the additional stuff in this +# class prevents this. Thus, this class should be split into a pure +# `ObjectHolder` and a class specifically for storing in `Interpreter`. +class SubprojectHolder(MesonInterpreterObject): + + def __init__(self, subinterpreter: T.Union['Interpreter', NullSubprojectInterpreter], + subdir: str, + warnings: int = 0, + disabled_feature: T.Optional[str] = None, + exception: T.Optional[Exception] = None) -> None: + super().__init__() + self.held_object = subinterpreter + self.warnings = warnings + self.disabled_feature = disabled_feature + self.exception = exception + self.subdir = PurePath(subdir).as_posix() + self.cm_interpreter: T.Optional[CMakeInterpreter] = None + self.methods.update({'get_variable': self.get_variable_method, + 'found': self.found_method, + }) + + @noPosargs + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.found() + + def found(self) -> bool: + return not isinstance(self.held_object, NullSubprojectInterpreter) + + @noKwargs + @noArgsFlattening + @unholder_return + def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get_variable takes one or two arguments.') + if isinstance(self.held_object, NullSubprojectInterpreter): # == not self.found() + raise InterpreterException(f'Subproject "{self.subdir}" disabled can\'t get_variable on it.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('Get_variable first argument must be a string.') + try: + return self.held_object.variables[varname] + except KeyError: + pass + + if len(args) == 2: + return self.held_object._holderify(args[1]) + + raise InvalidArguments(f'Requested variable "{varname}" not found.') + +class ModuleObjectHolder(ObjectHolder[ModuleObject]): + def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: + modobj = self.held_object + method = modobj.methods.get(method_name) + if not method: + raise InvalidCode(f'Unknown method {method_name!r} in object.') + if not getattr(method, 'no-args-flattening', False): + args = flatten(args) + if not getattr(method, 'no-second-level-holder-flattening', False): + args, kwargs = resolve_second_level_holders(args, kwargs) + state = ModuleState(self.interpreter) + # Many modules do for example self.interpreter.find_program_impl(), + # so we have to ensure they use the current interpreter and not the one + # that first imported that module, otherwise it will use outdated + # overrides. + if isinstance(modobj, ExtensionModule): + modobj.interpreter = self.interpreter + ret = method(state, args, kwargs) + if isinstance(ret, ModuleReturnValue): + self.interpreter.process_new_values(ret.new_objects) + ret = ret.return_value + return ret + +class MutableModuleObjectHolder(ModuleObjectHolder, MutableInterpreterObject): + def __deepcopy__(self, memo: T.Dict[int, T.Any]) -> 'MutableModuleObjectHolder': + # Deepcopy only held object, not interpreter + modobj = copy.deepcopy(self.held_object, memo) + return MutableModuleObjectHolder(modobj, self.interpreter) + + +_BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build.BothLibraries]) + +class BuildTargetHolder(ObjectHolder[_BuildTarget]): + def __init__(self, target: _BuildTarget, interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'extract_objects': self.extract_objects_method, + 'extract_all_objects': self.extract_all_objects_method, + 'name': self.name_method, + 'get_id': self.get_id_method, + 'outdir': self.outdir_method, + 'full_path': self.full_path_method, + 'path': self.path_method, + 'found': self.found_method, + 'private_dir_include': self.private_dir_include_method, + }) + + def __repr__(self) -> str: + r = '<{} {}: {}>' + h = self.held_object + assert isinstance(h, build.BuildTarget) + return r.format(self.__class__.__name__, h.get_id(), h.filename) + + @property + def _target_object(self) -> build.BuildTarget: + if isinstance(self.held_object, build.BothLibraries): + return self.held_object.get_default_object() + assert isinstance(self.held_object, build.BuildTarget) + return self.held_object + + def is_cross(self) -> bool: + return not self._target_object.environment.machines.matches_build_machine(self._target_object.for_machine) + + @noPosargs + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + if not (isinstance(self.held_object, build.Executable) and self.held_object.was_returned_by_find_program): + FeatureNew.single_use('BuildTarget.found', '0.59.0', subproject=self.held_object.subproject) + return True + + @noPosargs + @noKwargs + def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs: + return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)]) + + @noPosargs + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_filename_abs(self._target_object) + + @noPosargs + @noKwargs + @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead') + def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_filename_abs(self._target_object) + + @noPosargs + @noKwargs + def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_dir(self._target_object) + + @noKwargs + @typed_pos_args('extract_objects', varargs=(mesonlib.File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) + def extract_objects_method(self, args: T.Tuple[T.List[T.Union[mesonlib.FileOrString, 'build.GeneratedTypes']]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects: + return self._target_object.extract_objects(args[0]) + + @noPosargs + @typed_kwargs( + 'extract_all_objects', + KwargInfo( + 'recursive', bool, default=False, since='0.46.0', + not_set_warning=textwrap.dedent('''\ + extract_all_objects called without setting recursive + keyword argument. Meson currently defaults to + non-recursive to maintain backward compatibility but + the default will be changed in the future. + ''') + ) + ) + def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: + return self._target_object.extract_all_objects(kwargs['recursive']) + + @noPosargs + @noKwargs + def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._target_object.get_id() + + @FeatureNew('name', '0.54.0') + @noPosargs + @noKwargs + def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._target_object.name + +class ExecutableHolder(BuildTargetHolder[build.Executable]): + pass + +class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]): + pass + +class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): + pass + +class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): + def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'): + # FIXME: This build target always represents the shared library, but + # that should be configurable. + super().__init__(libs, interp) + self.methods.update({'get_shared_lib': self.get_shared_lib_method, + 'get_static_lib': self.get_static_lib_method, + }) + + def __repr__(self) -> str: + r = '<{} {}: {}, {}: {}>' + h1 = self.held_object.shared + h2 = self.held_object.static + return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) + + @noPosargs + @noKwargs + def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary: + return self.held_object.shared + + @noPosargs + @noKwargs + def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary: + return self.held_object.static + +class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): + pass + +class JarHolder(BuildTargetHolder[build.Jar]): + pass + +class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]): + def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'full_path': self.full_path_method, + }) + + @FeatureNew('custom_target[i].full_path', '0.54.0') + @noPosargs + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + assert self.interpreter.backend is not None + return self.interpreter.backend.get_target_filename_abs(self.held_object) + +class CustomTargetHolder(ObjectHolder[build.CustomTarget]): + def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'full_path': self.full_path_method, + 'to_list': self.to_list_method, + }) + + self.operators.update({ + MesonOperator.INDEX: self.op_index, + }) + + def __repr__(self) -> str: + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.command) + + @noPosargs + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_filename_abs(self.held_object) + + @FeatureNew('custom_target.to_list', '0.54.0') + @noPosargs + @noKwargs + def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]: + result = [] + for i in self.held_object: + result.append(i) + return result + + @noKwargs + @typed_operator(MesonOperator.INDEX, int) + def op_index(self, other: int) -> build.CustomTargetIndex: + try: + return self.held_object[other] + except IndexError: + raise InvalidArguments(f'Index {other} out of bounds of custom target {self.held_object.name} output of size {len(self.held_object)}.') + +class RunTargetHolder(ObjectHolder[build.RunTarget]): + pass + +class AliasTargetHolder(ObjectHolder[build.AliasTarget]): + pass + +class GeneratedListHolder(ObjectHolder[build.GeneratedList]): + pass + +class GeneratorHolder(ObjectHolder[build.Generator]): + def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): + super().__init__(gen, interpreter) + self.methods.update({'process': self.process_method}) + + @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) + @typed_kwargs( + 'generator.process', + KwargInfo('preserve_path_from', (str, NoneType), since='0.45.0'), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + ) + def process_method(self, + args: T.Tuple[T.List[T.Union[str, mesonlib.File, 'build.GeneratedTypes']]], + kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList: + preserve_path_from = kwargs['preserve_path_from'] + if preserve_path_from is not None: + preserve_path_from = os.path.normpath(preserve_path_from) + if not os.path.isabs(preserve_path_from): + # This is a bit of a hack. Fix properly before merging. + raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') + + if any(isinstance(a, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for a in args[0]): + FeatureNew.single_use( + 'Calling generator.process with CustomTarget or Index of CustomTarget.', + '0.57.0', self.interpreter.subproject) + + gl = self.held_object.process_files(args[0], self.interpreter, + preserve_path_from, extra_args=kwargs['extra_args']) + + return gl + + +class StructuredSourcesHolder(ObjectHolder[build.StructuredSources]): + + def __init__(self, sources: build.StructuredSources, interp: 'Interpreter'): + super().__init__(sources, interp) |