aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/interpreter/interpreterobjects.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/interpreter/interpreterobjects.py')
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py999
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)