diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2021-04-01 08:18:44 -0400 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2021-04-01 14:26:33 -0400 |
commit | 558a7bc6ff875f233b2ab7531e59e296b98032bd (patch) | |
tree | 6f84e2788819c9decaa0b1d72edd35c0eb8e77ac /mesonbuild/interpreter/interpreterobjects.py | |
parent | 2cd0723c42ae2076c7ba2b888fd7a5235d5cbf17 (diff) | |
download | meson-558a7bc6ff875f233b2ab7531e59e296b98032bd.zip meson-558a7bc6ff875f233b2ab7531e59e296b98032bd.tar.gz meson-558a7bc6ff875f233b2ab7531e59e296b98032bd.tar.bz2 |
interpreter: Move to its own folder and split it
Diffstat (limited to 'mesonbuild/interpreter/interpreterobjects.py')
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py new file mode 100644 index 0000000..7d43752 --- /dev/null +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -0,0 +1,999 @@ +import os +import shlex +import subprocess +import re + +from pathlib import Path, PurePath + +from .. import mesonlib +from .. import coredata +from .. import build +from .. import mlog + +from ..modules import ModuleReturnValue, ModuleObject, ModuleState +from ..backend.backends import TestProtocol +from ..interpreterbase import (InterpreterObject, ObjectHolder, MutableInterpreterObject, + FeatureNewKwargs, FeatureNew, FeatureDeprecated, + typed_pos_args, stringArgs, permittedKwargs, + noArgsFlattening, noPosargs, TYPE_var, TYPE_nkwargs, + flatten, InterpreterException, InvalidArguments, InvalidCode) +from ..dependencies import Dependency, ExternalLibrary, InternalDependency +from ..programs import ExternalProgram +from ..mesonlib import FileMode, OptionKey, listify, Popen_safe + +import typing as T + +def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True): + val = kwargs.get('required', default) + disabled = False + required = False + feature = None + if isinstance(val, FeatureOptionHolder): + if not feature_check: + feature_check = FeatureNew('User option "feature"', '0.47.0') + feature_check.use(subproject) + option = val.held_object + feature = val.name + if option.is_disabled(): + disabled = True + elif option.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. + kwargs['required'] = required + + return disabled, required, feature + +def extract_search_dirs(kwargs): + search_dirs = mesonlib.stringlistify(kwargs.get('dirs', [])) + search_dirs = [Path(d).expanduser() for d in search_dirs] + 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(InterpreterObject, ObjectHolder[coredata.UserFeatureOption]): + def __init__(self, env: 'Environment', name: str, option: coredata.UserFeatureOption): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, option) + if option.is_auto(): + # TODO: we need to case here because options is not a TypedDict + self.held_object = T.cast(coredata.UserFeatureOption, env.coredata.options[OptionKey('auto_features')]) + self.name = name + self.methods.update({'enabled': self.enabled_method, + 'disabled': self.disabled_method, + 'auto': self.auto_method, + }) + + @noPosargs + @permittedKwargs({}) + def enabled_method(self, args, kwargs): + return self.held_object.is_enabled() + + @noPosargs + @permittedKwargs({}) + def disabled_method(self, args, kwargs): + return self.held_object.is_disabled() + + @noPosargs + @permittedKwargs({}) + def auto_method(self, args, kwargs): + return self.held_object.is_auto() + +class RunProcess(InterpreterObject): + + def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True): + super().__init__() + if not isinstance(cmd, ExternalProgram): + raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') + self.capture = capture + pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) + self.returncode = pc.returncode + self.methods.update({'returncode': self.returncode_method, + 'stdout': self.stdout_method, + 'stderr': self.stderr_method, + }) + + def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False): + 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, o, e + except FileNotFoundError: + raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) + + @noPosargs + @permittedKwargs({}) + def returncode_method(self, args, kwargs): + return self.returncode + + @noPosargs + @permittedKwargs({}) + def stdout_method(self, args, kwargs): + return self.stdout + + @noPosargs + @permittedKwargs({}) + def stderr_method(self, args, kwargs): + return self.stderr + +class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.EnvironmentVariables]): + def __init__(self, initial_values=None, subproject: str = ''): + MutableInterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.EnvironmentVariables(), subproject) + 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.stringlistify(initial_values): + 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.held_object.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("EnvironmentVariablesHolder 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.held_object.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.held_object.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.held_object.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.held_object.prepend(name, values, separator) + + +class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder[build.ConfigurationData]): + def __init__(self, pv, initial_values=None): + MutableInterpreterObject.__init__(self) + self.used = False # These objects become immutable after use in configure_file. + ObjectHolder.__init__(self, build.ConfigurationData(), pv) + 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 ConfigurationDataHolder initial_values') + + def is_used(self): + return self.used + + def mark_used(self): + self.used = True + + def validate_args(self, args, kwargs): + 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 = 'Setting a configuration data value to {!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.format(val), 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.') + + return name, val, desc + + @noArgsFlattening + def set_method(self, args, kwargs): + (name, val, desc) = self.validate_args(args, kwargs) + self.held_object.values[name] = (val, desc) + + def set_quoted_method(self, args, kwargs): + (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.held_object.values[name] = ('"' + escaped_val + '"', desc) + + def set10_method(self, args, kwargs): + (name, val, desc) = self.validate_args(args, kwargs) + if val: + self.held_object.values[name] = (1, desc) + else: + self.held_object.values[name] = (0, desc) + + def has_method(self, args, kwargs): + return args[0] in self.held_object.values + + @FeatureNew('configuration_data.get()', '0.38.0') + @noArgsFlattening + def get_method(self, args, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get method takes one or two arguments.') + name = args[0] + if name in self.held_object: + return self.held_object.get(name)[0] + if len(args) > 1: + return 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, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get method takes one or two arguments.') + name = args[0] + if name in self.held_object: + val = self.held_object.get(name)[0] + elif len(args) > 1: + val = args[1] + else: + raise InterpreterException('Entry %s not in configuration data.' % name) + if val[0] == '"' and val[-1] == '"': + return val[1:-1] + return val + + def get(self, name): + return self.held_object.values[name] # (val, desc) + + @FeatureNew('configuration_data.keys()', '0.57.0') + @noPosargs + def keys_method(self, args, kwargs): + return sorted(self.keys()) + + def keys(self): + return self.held_object.values.keys() + + def merge_from_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Merge_from takes one positional argument.') + from_object = args[0] + if not isinstance(from_object, ConfigurationDataHolder): + raise InterpreterException('Merge_from argument must be a configuration data object.') + from_object = from_object.held_object + for k, v in from_object.values.items(): + self.held_object.values[k] = v + +permitted_partial_dependency_kwargs = { + 'compile_args', 'link_args', 'links', 'includes', 'sources' +} + +class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): + def __init__(self, dep: Dependency, pv: str): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, dep, pv) + 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): + return self.found_method([], {}) + + @noPosargs + @permittedKwargs({}) + def type_name_method(self, args, kwargs): + return self.held_object.type_name + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + if self.held_object.type_name == 'internal': + return True + return self.held_object.found() + + @noPosargs + @permittedKwargs({}) + def version_method(self, args, kwargs): + return self.held_object.get_version() + + @noPosargs + @permittedKwargs({}) + def name_method(self, args, kwargs): + 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, kwargs): + 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') + @permittedKwargs({}) + def configtool_method(self, args, kwargs): + 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 + @permittedKwargs(permitted_partial_dependency_kwargs) + def partial_dependency_method(self, args, kwargs): + pdep = self.held_object.get_partial_dependency(**kwargs) + return DependencyHolder(pdep, self.subproject) + + @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]) -> 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 + @permittedKwargs({}) + def include_type_method(self, args, kwargs): + return self.held_object.get_include_type() + + @FeatureNew('dep.as_system', '0.52.0') + @permittedKwargs({}) + def as_system_method(self, args, kwargs): + args = listify(args) + new_is_system = 'system' + if len(args) > 1: + raise InterpreterException('as_system takes only one optional value') + if len(args) == 1: + new_is_system = args[0] + new_dep = self.held_object.generate_system_dependency(new_is_system) + return DependencyHolder(new_dep, self.subproject) + + @FeatureNew('dep.as_link_whole', '0.56.0') + @permittedKwargs({}) + @noPosargs + def as_link_whole_method(self, args, kwargs): + 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 DependencyHolder(new_dep, self.subproject) + +class ExternalProgramHolder(InterpreterObject, ObjectHolder[ExternalProgram]): + def __init__(self, ep: ExternalProgram, subproject: str, backend=None): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, ep) + self.subproject = subproject + self.backend = backend + self.methods.update({'found': self.found_method, + 'path': self.path_method, + 'full_path': self.full_path_method}) + self.cached_version = None + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + @noPosargs + @permittedKwargs({}) + @FeatureDeprecated('ExternalProgram.path', '0.55.0', + 'use ExternalProgram.full_path() instead') + def path_method(self, args, kwargs): + return self._full_path() + + @noPosargs + @permittedKwargs({}) + @FeatureNew('ExternalProgram.full_path', '0.55.0') + def full_path_method(self, args, kwargs): + return self._full_path() + + def _full_path(self): + exe = self.held_object + if isinstance(exe, build.Executable): + return self.backend.get_target_filename_abs(exe) + return exe.get_path() + + def found(self): + return isinstance(self.held_object, build.Executable) or self.held_object.found() + + def get_command(self): + return self.held_object.get_command() + + def get_name(self): + exe = self.held_object + if isinstance(exe, build.Executable): + return exe.name + return exe.get_name() + + def get_version(self, interpreter): + if isinstance(self.held_object, build.Executable): + return self.held_object.project_version + if not self.cached_version: + raw_cmd = self.get_command() + ['--version'] + cmd = [self, '--version'] + res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) + if res.returncode != 0: + m = 'Running {!r} failed' + raise InterpreterException(m.format(raw_cmd)) + output = res.stdout.strip() + if not output: + output = res.stderr.strip() + match = re.search(r'([0-9][0-9\.]+)', output) + if not match: + m = 'Could not find a version number in output of {!r}' + raise InterpreterException(m.format(raw_cmd)) + self.cached_version = match.group(1) + return self.cached_version + +class ExternalLibraryHolder(InterpreterObject, ObjectHolder[ExternalLibrary]): + def __init__(self, el: ExternalLibrary, pv: str): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, el, pv) + self.methods.update({'found': self.found_method, + 'type_name': self.type_name_method, + 'partial_dependency': self.partial_dependency_method, + }) + + def found(self): + return self.held_object.found() + + @noPosargs + @permittedKwargs({}) + def type_name_method(self, args, kwargs): + return self.held_object.type_name + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + def get_name(self): + return self.held_object.name + + def get_compile_args(self): + return self.held_object.get_compile_args() + + def get_link_args(self): + return self.held_object.get_link_args() + + def get_exe_args(self): + return self.held_object.get_exe_args() + + @FeatureNew('dep.partial_dependency', '0.46.0') + @noPosargs + @permittedKwargs(permitted_partial_dependency_kwargs) + def partial_dependency_method(self, args, kwargs): + pdep = self.held_object.get_partial_dependency(**kwargs) + return DependencyHolder(pdep, self.subproject) + +class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): + @FeatureNewKwargs('generator', '0.43.0', ['capture']) + def __init__(self, interp, args, kwargs): + self.interpreter = interp + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject) + self.methods.update({'process': self.process_method}) + + @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from']) + @permittedKwargs({'extra_args', 'preserve_path_from'}) + def process_method(self, args, kwargs): + extras = mesonlib.stringlistify(kwargs.get('extra_args', [])) + if 'preserve_path_from' in kwargs: + preserve_path_from = kwargs['preserve_path_from'] + if not isinstance(preserve_path_from, str): + raise InvalidArguments('Preserve_path_from must be a string.') + 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.') + else: + preserve_path_from = None + gl = self.held_object.process_files('Generator', args, self.interpreter, + preserve_path_from, extra_args=extras) + return GeneratedListHolder(gl) + + +class GeneratedListHolder(InterpreterObject, ObjectHolder[build.GeneratedList]): + def __init__(self, arg1, extra_args=None): + InterpreterObject.__init__(self) + if isinstance(arg1, GeneratorHolder): + ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else [])) + else: + ObjectHolder.__init__(self, arg1) + + def __repr__(self): + r = '<{}: {!r}>' + return r.format(self.__class__.__name__, self.held_object.get_outputs()) + + def add_file(self, a): + self.held_object.add_file(a) + +# A machine that's statically known from the cross file +class MachineHolder(InterpreterObject, ObjectHolder['MachineInfo']): + def __init__(self, machine_info: 'MachineInfo'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, machine_info) + self.methods.update({'system': self.system_method, + 'cpu': self.cpu_method, + 'cpu_family': self.cpu_family_method, + 'endian': self.endian_method, + }) + + @noPosargs + @permittedKwargs({}) + def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.cpu_family + + @noPosargs + @permittedKwargs({}) + def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.cpu + + @noPosargs + @permittedKwargs({}) + def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.system + + @noPosargs + @permittedKwargs({}) + def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.endian + +class IncludeDirsHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): + def __init__(self, idobj: build.IncludeDirs): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, idobj) + +class HeadersHolder(InterpreterObject, ObjectHolder[build.Headers]): + + def __init__(self, obj: build.Headers): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + + def set_install_subdir(self, subdir): + self.held_object.install_subdir = subdir + + def get_install_subdir(self): + return self.held_object.install_subdir + + def get_sources(self): + return self.held_object.sources + + def get_custom_install_dir(self): + return self.held_object.custom_install_dir + + def get_custom_install_mode(self): + return self.held_object.custom_install_mode + +class DataHolder(InterpreterObject, ObjectHolder[build.Data]): + def __init__(self, data: build.Data): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, data) + + def get_source_subdir(self): + return self.held_object.source_subdir + + def get_sources(self): + return self.held_object.sources + + def get_install_dir(self): + return self.held_object.install_dir + +class InstallDirHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): + + def __init__(self, obj: build.InstallDir): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + +class ManHolder(InterpreterObject, ObjectHolder[build.Man]): + + def __init__(self, obj: build.Man): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.held_object.custom_install_dir + + def get_custom_install_mode(self) -> T.Optional[FileMode]: + return self.held_object.custom_install_mode + + def locale(self) -> T.Optional[str]: + return self.held_object.locale + + def get_sources(self) -> T.List[mesonlib.File]: + return self.held_object.sources + +class GeneratedObjectsHolder(InterpreterObject, ObjectHolder[build.ExtractedObjects]): + def __init__(self, held_object: build.ExtractedObjects): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, held_object) + +class Test(InterpreterObject): + 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): + InterpreterObject.__init__(self) + self.name = name + self.suite = 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): + return self.exe + + def get_name(self): + return self.name + +class SubprojectHolder(InterpreterObject, ObjectHolder[T.Optional['Interpreter']]): + + def __init__(self, subinterpreter: T.Optional['Interpreter'], subdir: str, warnings=0, disabled_feature=None, + exception=None): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, 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 + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + def found(self): + return self.held_object is not None + + @permittedKwargs({}) + @noArgsFlattening + def get_variable_method(self, args, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get_variable takes one or two arguments.') + if 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(InterpreterObject, ObjectHolder['ModuleObject']): + def __init__(self, modobj: 'ModuleObject', interpreter: 'Interpreter'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, modobj) + self.interpreter = interpreter + + def method_call(self, method_name, args, kwargs): + modobj = self.held_object + method = modobj.methods.get(method_name) + if not method and not modobj.methods: + # FIXME: Port all modules to use the methods dict. + method = getattr(modobj, method_name, None) + if method_name.startswith('_'): + raise InvalidArguments(f'Method {method_name!r} is private.') + if not method: + raise InvalidCode('Unknown method "%s" in object.' % method_name) + if not getattr(method, 'no-args-flattening', False): + args = flatten(args) + 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. + modobj.interpreter = self.interpreter + if method_name in modobj.snippets: + ret = method(self.interpreter, state, args, kwargs) + else: + # This is not 100% reliable but we can't use hash() + # because the Build object contains dicts and lists. + num_targets = len(self.interpreter.build.targets) + ret = method(state, args, kwargs) + if num_targets != len(self.interpreter.build.targets): + raise InterpreterException('Extension module altered internal state illegally.') + if isinstance(ret, ModuleReturnValue): + self.interpreter.process_new_values(ret.new_objects) + ret = ret.return_value + return self.interpreter.holderify(ret) + +_Target = T.TypeVar('_Target', bound=build.Target) + + +class TargetHolder(InterpreterObject, ObjectHolder[_Target]): + def __init__(self, target: _Target, interp: 'Interpreter'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, target, interp.subproject) + self.interpreter = interp + + +_BuildTarget = T.TypeVar('_BuildTarget', bound=build.BuildTarget) + +class BuildTargetHolder(TargetHolder[_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, + 'private_dir_include': self.private_dir_include_method, + }) + + def __repr__(self): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.filename) + + def is_cross(self): + return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine) + + @noPosargs + @permittedKwargs({}) + def private_dir_include_method(self, args, kwargs): + return IncludeDirsHolder(build.IncludeDirs('', [], False, + [self.interpreter.backend.get_target_private_dir(self.held_object)])) + + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + + @noPosargs + @permittedKwargs({}) + def outdir_method(self, args, kwargs): + return self.interpreter.backend.get_target_dir(self.held_object) + + @permittedKwargs({}) + def extract_objects_method(self, args, kwargs): + gobjs = self.held_object.extract_objects(args) + return GeneratedObjectsHolder(gobjs) + + @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive']) + @noPosargs + @permittedKwargs({'recursive'}) + def extract_all_objects_method(self, args, kwargs): + recursive = kwargs.get('recursive', False) + gobjs = self.held_object.extract_all_objects(recursive) + if gobjs.objlist and 'recursive' not in kwargs: + mlog.warning('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.', + location=self.current_node) + return GeneratedObjectsHolder(gobjs) + + @noPosargs + @permittedKwargs({}) + def get_id_method(self, args, kwargs): + return self.held_object.get_id() + + @FeatureNew('name', '0.54.0') + @noPosargs + @permittedKwargs({}) + def name_method(self, args, kwargs): + return self.held_object.name + +class ExecutableHolder(BuildTargetHolder[build.Executable]): + pass + +class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]): + pass + +class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): + def __init__(self, target: build.SharedLibrary, interp: 'Interpreter'): + super().__init__(target, interp) + # Set to True only when called from self.func_shared_lib(). + target.shared_library_only = False + +class BothLibrariesHolder(BuildTargetHolder): + def __init__(self, shared_holder, static_holder, interp): + # FIXME: This build target always represents the shared library, but + # that should be configurable. + super().__init__(shared_holder.held_object, interp) + self.shared_holder = shared_holder + self.static_holder = static_holder + self.methods.update({'get_shared_lib': self.get_shared_lib_method, + 'get_static_lib': self.get_static_lib_method, + }) + + def __repr__(self): + r = '<{} {}: {}, {}: {}>' + h1 = self.shared_holder.held_object + h2 = self.static_holder.held_object + return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) + + @noPosargs + @permittedKwargs({}) + def get_shared_lib_method(self, args, kwargs): + return self.shared_holder + + @noPosargs + @permittedKwargs({}) + def get_static_lib_method(self, args, kwargs): + return self.static_holder + +class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): + pass + +class JarHolder(BuildTargetHolder[build.Jar]): + pass + +class CustomTargetIndexHolder(TargetHolder[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 + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + +class CustomTargetHolder(TargetHolder): + 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): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.command) + + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + + @FeatureNew('custom_target.to_list', '0.54.0') + @noPosargs + @permittedKwargs({}) + def to_list_method(self, args, kwargs): + result = [] + for i in self.held_object: + result.append(CustomTargetIndexHolder(i, self.interpreter)) + return result + + def __getitem__(self, index): + return CustomTargetIndexHolder(self.held_object[index], self.interpreter) + + def __setitem__(self, index, value): # lgtm[py/unexpected-raise-in-special-method] + raise InterpreterException('Cannot set a member of a CustomTarget') + + def __delitem__(self, index): # lgtm[py/unexpected-raise-in-special-method] + raise InterpreterException('Cannot delete a member of a CustomTarget') + + def outdir_include(self): + return IncludeDirsHolder(build.IncludeDirs('', [], False, + [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))])) + +class RunTargetHolder(TargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + + def __repr__(self): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.command) |