| 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, |
| InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, |
| FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated, |
| typed_pos_args, typed_kwargs, stringArgs, permittedKwargs, |
| noArgsFlattening, noPosargs, noKwargs, permissive_unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs, |
| flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode) |
| from ..dependencies import Dependency, ExternalLibrary, InternalDependency |
| from ..programs import ExternalProgram |
| from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe |
| |
| import typing as T |
| |
| if T.TYPE_CHECKING: |
| from . import kwargs |
| from .interpreter import Interpreter |
| from ..environment import Environment |
| from ..envconfig import MachineInfo |
| |
| |
| def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', |
| subproject: str, |
| 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 list(map(str, 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 case here because options is not a TypedDict |
| self.held_object = T.cast(coredata.UserFeatureOption, self.env.coredata.options[OptionKey('auto_features')]) |
| 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 |
| def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: |
| return not self.value == 'disabled' |
| |
| @noPosargs |
| @noKwargs |
| def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: |
| return self.value == 'auto' |
| |
| @permittedKwargs({'error_message'}) |
| def require_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: |
| if len(args) != 1: |
| raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) |
| if not isinstance(args[0], bool): |
| raise InvalidArguments('boolean argument expected.') |
| error_message = kwargs.pop('error_message', '') |
| if error_message and not isinstance(error_message, str): |
| raise InterpreterException("Error message must be a string.") |
| if args[0]: |
| return copy.deepcopy(self.held_object) |
| |
| assert isinstance(error_message, str) |
| if self.value == 'enabled': |
| prefix = f'Feature {self.held_object.name} cannot be enabled' |
| prefix = prefix + ': ' if error_message else '' |
| raise InterpreterException(prefix + error_message) |
| return self.as_disabled() |
| |
| @noKwargs |
| def disable_auto_if_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: |
| if len(args) != 1: |
| raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) |
| if not isinstance(args[0], bool): |
| raise InvalidArguments('boolean argument expected.') |
| 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:', ' '.join(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(' '.join(command_array), p.returncode)) |
| |
| return p.returncode, o, e |
| except FileNotFoundError: |
| raise InterpreterException('Could not execute command "%s".' % ' '.join(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 |
| |
| # TODO: Parsing the initial values should be either done directly in the |
| # `Interpreter` or in `build.EnvironmentVariables`. This way, this class |
| # can be converted into a pure object holder. |
| class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObject): |
| # TODO: Move the type cheking for initial_values out of this class and replace T.Any |
| def __init__(self, initial_values: T.Optional[T.Any] = None, subproject: str = ''): |
| super().__init__(subproject=subproject) |
| self.vars = build.EnvironmentVariables() |
| self.methods.update({'set': self.set_method, |
| 'append': self.append_method, |
| 'prepend': self.prepend_method, |
| }) |
| if isinstance(initial_values, dict): |
| for k, v in initial_values.items(): |
| self.set_method([k, v], {}) |
| elif initial_values is not None: |
| for e in mesonlib.listify(initial_values): |
| if not isinstance(e, str): |
| raise InterpreterException('Env var definition must be a list of strings.') |
| if '=' not in e: |
| raise InterpreterException('Env var definition must be of type key=val.') |
| (k, val) = e.split('=', 1) |
| k = k.strip() |
| val = val.strip() |
| if ' ' in k: |
| raise InterpreterException('Env var key must not have spaces in it.') |
| self.set_method([k, val], {}) |
| |
| def __repr__(self) -> str: |
| repr_str = "<{0}: {1}>" |
| return repr_str.format(self.__class__.__name__, self.vars.envvars) |
| |
| def unpack_separator(self, kwargs: T.Dict[str, T.Any]) -> str: |
| separator = kwargs.get('separator', os.pathsep) |
| if not isinstance(separator, str): |
| raise InterpreterException("EnvironmentVariablesObject methods 'separator'" |
| " argument needs to be a string.") |
| return separator |
| |
| def warn_if_has_name(self, name: str) -> None: |
| # Multiple append/prepend operations was not supported until 0.58.0. |
| if self.vars.has_name(name): |
| m = f'Overriding previous value of environment variable {name!r} with a new one' |
| FeatureNew('0.58.0', m).use(self.subproject) |
| |
| @stringArgs |
| @permittedKwargs({'separator'}) |
| @typed_pos_args('environment.set', str, varargs=str, min_varargs=1) |
| def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: |
| name, values = args |
| separator = self.unpack_separator(kwargs) |
| self.vars.set(name, values, separator) |
| |
| @stringArgs |
| @permittedKwargs({'separator'}) |
| @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) |
| def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: |
| name, values = args |
| separator = self.unpack_separator(kwargs) |
| self.warn_if_has_name(name) |
| self.vars.append(name, values, separator) |
| |
| @stringArgs |
| @permittedKwargs({'separator'}) |
| @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) |
| def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: |
| name, values = args |
| separator = self.unpack_separator(kwargs) |
| self.warn_if_has_name(name) |
| self.vars.prepend(name, values, separator) |
| |
| |
| class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject): |
| def __init__(self, subproject: str, initial_values: T.Optional[T.Dict[str, T.Any]] = None) -> None: |
| self.used = False # These objects become immutable after use in configure_file. |
| super().__init__(subproject=subproject) |
| self.conf_data = build.ConfigurationData() |
| 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, |
| }) |
| if isinstance(initial_values, dict): |
| for k, v in initial_values.items(): |
| self.set_method([k, v], {}) |
| elif initial_values: |
| raise AssertionError('Unsupported ConfigurationDataObject initial_values') |
| |
| def is_used(self) -> bool: |
| return self.used |
| |
| def mark_used(self) -> None: |
| self.used = True |
| |
| def validate_args(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[str, T.Union[str, int, bool], T.Optional[str]]: |
| if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: |
| mlog.deprecation('Passing a list as the single argument to ' |
| 'configuration_data.set is deprecated. This will ' |
| 'become a hard error in the future.', |
| location=self.current_node) |
| args = args[0] |
| |
| if len(args) != 2: |
| raise InterpreterException("Configuration set requires 2 arguments.") |
| if self.used: |
| raise InterpreterException("Can not set values on configuration object that has been used.") |
| name, val = args |
| if not isinstance(val, (int, str)): |
| msg = f'Setting a configuration data value to {val!r} is invalid, ' \ |
| 'and will fail at configure_file(). If you are using it ' \ |
| 'just to store some values, please use a dict instead.' |
| mlog.deprecation(msg, location=self.current_node) |
| desc = kwargs.get('description', None) |
| if not isinstance(name, str): |
| raise InterpreterException("First argument to set must be a string.") |
| if desc is not None and not isinstance(desc, str): |
| raise InterpreterException('Description must be a string.') |
| |
| # TODO: Remove the cast once we get rid of the deprecation |
| return name, T.cast(T.Union[str, bool, int], val), desc |
| |
| @noArgsFlattening |
| def set_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: |
| (name, val, desc) = self.validate_args(args, kwargs) |
| self.conf_data.values[name] = (val, desc) |
| |
| def set_quoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: |
| (name, val, desc) = self.validate_args(args, kwargs) |
| if not isinstance(val, str): |
| raise InterpreterException("Second argument to set_quoted must be a string.") |
| escaped_val = '\\"'.join(val.split('"')) |
| self.conf_data.values[name] = ('"' + escaped_val + '"', desc) |
| |
| def set10_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: |
| (name, val, desc) = self.validate_args(args, kwargs) |
| if val: |
| self.conf_data.values[name] = (1, desc) |
| else: |
| self.conf_data.values[name] = (0, desc) |
| |
| def has_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: |
| return args[0] in self.conf_data.values |
| |
| @FeatureNew('configuration_data.get()', '0.38.0') |
| @noArgsFlattening |
| def get_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: |
| if len(args) < 1 or len(args) > 2: |
| raise InterpreterException('Get method takes one or two arguments.') |
| if not isinstance(args[0], str): |
| raise InterpreterException('The variable name must be a string.') |
| name = args[0] |
| if name in self.conf_data: |
| return self.conf_data.get(name)[0] |
| if len(args) > 1: |
| # Assertion does not work because setting other values is still |
| # supported, but deprecated. Use T.cast in the meantime (even though |
| # this is a lie). |
| # TODO: Fix this once the deprecation is removed |
| # assert isinstance(args[1], (int, str, bool)) |
| return T.cast(T.Union[str, int, bool], args[1]) |
| raise InterpreterException('Entry %s not in configuration data.' % name) |
| |
| @FeatureNew('configuration_data.get_unquoted()', '0.44.0') |
| def get_unquoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: |
| if len(args) < 1 or len(args) > 2: |
| raise InterpreterException('Get method takes one or two arguments.') |
| if not isinstance(args[0], str): |
| raise InterpreterException('The variable name must be a string.') |
| name = args[0] |
| if name in self.conf_data: |
| val = self.conf_data.get(name)[0] |
| elif len(args) > 1: |
| assert isinstance(args[1], (str, int, bool)) |
| val = args[1] |
| else: |
| raise InterpreterException('Entry %s not in configuration data.' % name) |
| 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.conf_data.values[name] |
| |
| @FeatureNew('configuration_data.keys()', '0.57.0') |
| @noPosargs |
| 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.conf_data.values.keys()) |
| |
| def merge_from_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: |
| if len(args) != 1: |
| raise InterpreterException('Merge_from takes one positional argument.') |
| from_object_holder = args[0] |
| if not isinstance(from_object_holder, ConfigurationDataObject): |
| raise InterpreterException('Merge_from argument must be a configuration data object.') |
| from_object = from_object_holder.conf_data |
| for k, v in from_object.values.items(): |
| self.conf_data.values[k] = v |
| |
| |
| _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') |
| @permittedKwargs({'define_variable', 'default'}) |
| def pkgconfig_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: |
| args = listify(args) |
| if len(args) != 1: |
| raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') |
| varname = args[0] |
| if not isinstance(varname, str): |
| raise InterpreterException('Variable name must be a string.') |
| return self.held_object.get_pkgconfig_variable(varname, kwargs) |
| |
| @FeatureNew('dep.get_configtool_variable', '0.44.0') |
| @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0', |
| 'use Dependency.get_variable(configtool : ...) instead') |
| @noKwargs |
| def configtool_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: |
| args = listify(args) |
| if len(args) != 1: |
| raise InterpreterException('get_configtool_variable takes exactly one argument.') |
| varname = args[0] |
| if not isinstance(varname, str): |
| raise InterpreterException('Variable name must be a string.') |
| return self.held_object.get_configtool_variable(varname) |
| |
| @FeatureNew('dep.partial_dependency', '0.46.0') |
| @noPosargs |
| @typed_kwargs('dep.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('dep.get_variable', '0.51.0') |
| @typed_pos_args('dep.get_variable', optargs=[str]) |
| @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'}) |
| @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal']) |
| def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Union[str, T.List[str]]: |
| default_varname = args[0] |
| if default_varname is not None: |
| FeatureNew('0.58.0', 'Positional argument to dep.get_variable()').use(self.subproject) |
| for k in ['cmake', 'pkgconfig', 'configtool', 'internal']: |
| kwargs.setdefault(k, default_varname) |
| return self.held_object.get_variable(**kwargs) |
| |
| @FeatureNew('dep.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('dep.as_system', '0.52.0') |
| @noKwargs |
| def as_system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: |
| args = listify(args) |
| new_is_system = 'system' |
| if len(args) > 1: |
| raise InterpreterException('as_system takes only one optional value') |
| if len(args) == 1: |
| if not isinstance(args[0], str): |
| raise InterpreterException('as_system takes exactly one string parameter') |
| new_is_system = args[0] |
| new_dep = self.held_object.generate_system_dependency(new_is_system) |
| return new_dep |
| |
| @FeatureNew('dep.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, |
| '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 |
| |
| 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('dep.partial_dependency', '0.46.0') |
| @noPosargs |
| @typed_kwargs('dep.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 InstallDirHolder(ObjectHolder[build.InstallDir]): |
| pass |
| |
| class ManHolder(ObjectHolder[build.Man]): |
| pass |
| |
| class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]): |
| pass |
| |
| class Test(MesonInterpreterObject): |
| def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable, |
| depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], |
| is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables, |
| should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, |
| priority: int): |
| 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 |
| |
| def get_exe(self) -> build.Executable: |
| 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 stroing 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[MesonException] = 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.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 |
| @permissive_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('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir)) |
| 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 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 |
| 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)) |
| def extract_objects_method(self, args: T.Tuple[T.List[mesonlib.FileOrString]], 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, |
| }) |
| |
| 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 |
| |
| def __getitem__(self, index: int) -> build.CustomTargetIndex: |
| return self.held_object[index] |
| |
| def __setitem__(self, index: int, value: T.Any) -> None: # lgtm[py/unexpected-raise-in-special-method] |
| raise InterpreterException('Cannot set a member of a CustomTarget') |
| |
| def __delitem__(self, index: int) -> None: # lgtm[py/unexpected-raise-in-special-method] |
| raise InterpreterException('Cannot delete a member of a CustomTarget') |
| |
| 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, 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.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]], |
| 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( |
| f'Calling generator.process with CustomTaget 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 |