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.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.py')
-rw-r--r-- | mesonbuild/interpreter.py | 5061 |
1 files changed, 0 insertions, 5061 deletions
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py deleted file mode 100644 index 2b7a36a..0000000 --- a/mesonbuild/interpreter.py +++ /dev/null @@ -1,5061 +0,0 @@ -# Copyright 2012-2021 The Meson development team -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from . import mparser -from . import environment -from . import coredata -from . import dependencies -from . import mlog -from . import build -from . import optinterpreter -from . import compilers -from .wrap import wrap, WrapMode -from . import mesonlib -from .mesonlib import FileMode, MachineChoice, OptionKey, Popen_safe, listify, extract_as_list, has_path_sep, unholder -from .programs import ExternalProgram, NonExistingExternalProgram, OverrideProgram -from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException -from .depfile import DepFile -from .interpreterbase import InterpreterBase, typed_pos_args -from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening -from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest -from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound -from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs -from .interpreterbase import ObjectHolder, MesonVersionString, RangeHolder -from .interpreterbase import TYPE_var, TYPE_nkwargs -from .modules import ModuleReturnValue, ModuleObject, ModuleState -from .cmake import CMakeInterpreter -from .backend.backends import TestProtocol, Backend, ExecutableSerialisation - -from pathlib import Path, PurePath -import os -import shutil -import uuid -import re -import shlex -import stat -import subprocess -import collections -import functools -import typing as T - -import importlib - -if T.TYPE_CHECKING: - from .compilers import Compiler - from .envconfig import MachineInfo - from .environment import Environment - -permitted_method_kwargs = { - 'partial_dependency': {'compile_args', 'link_args', 'links', 'includes', - 'sources'}, -} - -def stringifyUserArguments(args, quote=False): - if isinstance(args, list): - return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args]) - elif isinstance(args, dict): - return '{%s}' % ', '.join(['{} : {}'.format(stringifyUserArguments(k, True), stringifyUserArguments(v, True)) for k, v in args.items()]) - elif isinstance(args, int): - return str(args) - elif isinstance(args, str): - return f"'{args}'" if quote else args - raise InvalidArguments('Function accepts only strings, integers, lists, dictionaries and lists thereof.') - - -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() - -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 TryRunResultHolder(InterpreterObject): - def __init__(self, res): - super().__init__() - self.res = res - self.methods.update({'returncode': self.returncode_method, - 'compiled': self.compiled_method, - 'stdout': self.stdout_method, - 'stderr': self.stderr_method, - }) - - @noPosargs - @permittedKwargs({}) - def returncode_method(self, args, kwargs): - return self.res.returncode - - @noPosargs - @permittedKwargs({}) - def compiled_method(self, args, kwargs): - return self.res.compiled - - @noPosargs - @permittedKwargs({}) - def stdout_method(self, args, kwargs): - return self.res.stdout - - @noPosargs - @permittedKwargs({}) - def stderr_method(self, args, kwargs): - return self.res.stderr - -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 - -# Interpreter objects can not be pickled so we must have -# these wrappers. - -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_method_kwargs['partial_dependency']) - 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[dependencies.ExternalLibrary]): - def __init__(self, el: dependencies.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_method_kwargs['partial_dependency']) - 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) - - -_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) - -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.') - -header_permitted_kwargs = { - 'required', - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', -} - -find_library_permitted_kwargs = { - 'has_headers', - 'required', - 'dirs', - 'static', -} - -find_library_permitted_kwargs |= {'header_' + k for k in header_permitted_kwargs} - -class CompilerHolder(InterpreterObject): - def __init__(self, compiler: 'Compiler', env: 'Environment', subproject: str): - InterpreterObject.__init__(self) - self.compiler = compiler - self.environment = env - self.subproject = subproject - self.methods.update({'compiles': self.compiles_method, - 'links': self.links_method, - 'get_id': self.get_id_method, - 'get_linker_id': self.get_linker_id_method, - 'compute_int': self.compute_int_method, - 'sizeof': self.sizeof_method, - 'get_define': self.get_define_method, - 'check_header': self.check_header_method, - 'has_header': self.has_header_method, - 'has_header_symbol': self.has_header_symbol_method, - 'run': self.run_method, - 'has_function': self.has_function_method, - 'has_member': self.has_member_method, - 'has_members': self.has_members_method, - 'has_type': self.has_type_method, - 'alignment': self.alignment_method, - 'version': self.version_method, - 'cmd_array': self.cmd_array_method, - 'find_library': self.find_library_method, - 'has_argument': self.has_argument_method, - 'has_function_attribute': self.has_func_attribute_method, - 'get_supported_function_attributes': self.get_supported_function_attributes_method, - 'has_multi_arguments': self.has_multi_arguments_method, - 'get_supported_arguments': self.get_supported_arguments_method, - 'first_supported_argument': self.first_supported_argument_method, - 'has_link_argument': self.has_link_argument_method, - 'has_multi_link_arguments': self.has_multi_link_arguments_method, - 'get_supported_link_arguments': self.get_supported_link_arguments_method, - 'first_supported_link_argument': self.first_supported_link_argument_method, - 'unittest_args': self.unittest_args_method, - 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, - 'get_argument_syntax': self.get_argument_syntax_method, - }) - - def _dep_msg(self, deps, endl): - msg_single = 'with dependency {}' - msg_many = 'with dependencies {}' - if not deps: - return endl - if endl is None: - endl = '' - names = [] - for d in deps: - if isinstance(d, dependencies.InternalDependency): - continue - if isinstance(d, dependencies.ExternalLibrary): - name = '-l' + d.name - else: - name = d.name - names.append(name) - if not names: - return None - tpl = msg_many if len(names) > 1 else msg_single - return tpl.format(', '.join(names)) + endl - - @noPosargs - @permittedKwargs({}) - def version_method(self, args, kwargs): - return self.compiler.version - - @noPosargs - @permittedKwargs({}) - def cmd_array_method(self, args, kwargs): - return self.compiler.exelist - - def determine_args(self, kwargs, mode='link'): - nobuiltins = kwargs.get('no_builtin_args', False) - if not isinstance(nobuiltins, bool): - raise InterpreterException('Type of no_builtin_args not a boolean.') - args = [] - incdirs = extract_as_list(kwargs, 'include_directories') - for i in incdirs: - if not isinstance(i, IncludeDirsHolder): - raise InterpreterException('Include directories argument must be an include_directories object.') - for idir in i.held_object.to_string_list(self.environment.get_source_dir()): - args += self.compiler.get_include_args(idir, False) - if not nobuiltins: - opts = self.environment.coredata.options - args += self.compiler.get_option_compile_args(opts) - if mode == 'link': - args += self.compiler.get_option_link_args(opts) - args += mesonlib.stringlistify(kwargs.get('args', [])) - return args - - def determine_dependencies(self, kwargs, endl=':'): - deps = kwargs.get('dependencies', None) - if deps is not None: - final_deps = [] - while deps: - next_deps = [] - for d in unholder(listify(deps)): - if not isinstance(d, Dependency) or d.is_built(): - raise InterpreterException('Dependencies must be external dependencies') - final_deps.append(d) - next_deps.extend(d.ext_deps) - deps = next_deps - deps = final_deps - return deps, self._dep_msg(deps, endl) - - @permittedKwargs({ - 'prefix', - 'args', - 'dependencies', - }) - def alignment_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Alignment method takes exactly one positional argument.') - check_stringlist(args) - typename = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of alignment must be a string.') - extra_args = mesonlib.stringlistify(kwargs.get('args', [])) - deps, msg = self.determine_dependencies(kwargs) - result = self.compiler.alignment(typename, prefix, self.environment, - extra_args=extra_args, - dependencies=deps) - mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result) - return result - - @permittedKwargs({ - 'name', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def run_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Run method takes exactly one positional argument.') - code = args[0] - if isinstance(code, mesonlib.File): - code = mesonlib.File.from_absolute_file( - code.rel_to_builddir(self.environment.source_dir)) - elif not isinstance(code, str): - raise InvalidArguments('Argument must be string or file.') - testname = kwargs.get('name', '') - if not isinstance(testname, str): - raise InterpreterException('Testname argument must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs, endl=None) - result = self.compiler.run(code, self.environment, extra_args=extra_args, - dependencies=deps) - if len(testname) > 0: - if not result.compiled: - h = mlog.red('DID NOT COMPILE') - elif result.returncode == 0: - h = mlog.green('YES') - else: - h = mlog.red('NO (%d)' % result.returncode) - mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h) - return TryRunResultHolder(result) - - @noPosargs - @permittedKwargs({}) - def get_id_method(self, args, kwargs): - return self.compiler.get_id() - - @noPosargs - @permittedKwargs({}) - @FeatureNew('compiler.get_linker_id', '0.53.0') - def get_linker_id_method(self, args, kwargs): - return self.compiler.get_linker_id() - - @noPosargs - @permittedKwargs({}) - def symbols_have_underscore_prefix_method(self, args, kwargs): - ''' - Check if the compiler prefixes _ (underscore) to global C symbols - See: https://en.wikipedia.org/wiki/Name_mangling#C - ''' - return self.compiler.symbols_have_underscore_prefix(self.environment) - - @noPosargs - @permittedKwargs({}) - def unittest_args_method(self, args, kwargs): - ''' - This function is deprecated and should not be used. - It can be removed in a future version of Meson. - ''' - if not hasattr(self.compiler, 'get_feature_args'): - raise InterpreterException(f'This {self.compiler.get_display_language()} compiler has no feature arguments.') - build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir()) - return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src) - - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def has_member_method(self, args, kwargs): - if len(args) != 2: - raise InterpreterException('Has_member takes exactly two arguments.') - check_stringlist(args) - typename, membername = args - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_member must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - had, cached = self.compiler.has_members(typename, [membername], prefix, - self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if had: - hadtxt = mlog.green('YES') - else: - hadtxt = mlog.red('NO') - mlog.log('Checking whether type', mlog.bold(typename, True), - 'has member', mlog.bold(membername, True), msg, hadtxt, cached) - return had - - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def has_members_method(self, args, kwargs): - if len(args) < 2: - raise InterpreterException('Has_members needs at least two arguments.') - check_stringlist(args) - typename, *membernames = args - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_members must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - had, cached = self.compiler.has_members(typename, membernames, prefix, - self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if had: - hadtxt = mlog.green('YES') - else: - hadtxt = mlog.red('NO') - members = mlog.bold(', '.join([f'"{m}"' for m in membernames])) - mlog.log('Checking whether type', mlog.bold(typename, True), - 'has members', members, msg, hadtxt, cached) - return had - - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def has_function_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Has_function takes exactly one argument.') - check_stringlist(args) - funcname = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_function must be a string.') - extra_args = self.determine_args(kwargs) - deps, msg = self.determine_dependencies(kwargs) - had, cached = self.compiler.has_function(funcname, prefix, self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if had: - hadtxt = mlog.green('YES') - else: - hadtxt = mlog.red('NO') - mlog.log('Checking for function', mlog.bold(funcname, True), msg, hadtxt, cached) - return had - - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def has_type_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Has_type takes exactly one argument.') - check_stringlist(args) - typename = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_type must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - had, cached = self.compiler.has_type(typename, prefix, self.environment, - extra_args=extra_args, dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if had: - hadtxt = mlog.green('YES') - else: - hadtxt = mlog.red('NO') - mlog.log('Checking for type', mlog.bold(typename, True), msg, hadtxt, cached) - return had - - @FeatureNew('compiler.compute_int', '0.40.0') - @permittedKwargs({ - 'prefix', - 'low', - 'high', - 'guess', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def compute_int_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Compute_int takes exactly one argument.') - check_stringlist(args) - expression = args[0] - prefix = kwargs.get('prefix', '') - low = kwargs.get('low', None) - high = kwargs.get('high', None) - guess = kwargs.get('guess', None) - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of compute_int must be a string.') - if low is not None and not isinstance(low, int): - raise InterpreterException('Low argument of compute_int must be an int.') - if high is not None and not isinstance(high, int): - raise InterpreterException('High argument of compute_int must be an int.') - if guess is not None and not isinstance(guess, int): - raise InterpreterException('Guess argument of compute_int must be an int.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - res = self.compiler.compute_int(expression, low, high, guess, prefix, - self.environment, extra_args=extra_args, - dependencies=deps) - mlog.log('Computing int of', mlog.bold(expression, True), msg, res) - return res - - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def sizeof_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Sizeof takes exactly one argument.') - check_stringlist(args) - element = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of sizeof must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - esize = self.compiler.sizeof(element, prefix, self.environment, - extra_args=extra_args, dependencies=deps) - mlog.log('Checking for size of', mlog.bold(element, True), msg, esize) - return esize - - @FeatureNew('compiler.get_define', '0.40.0') - @permittedKwargs({ - 'prefix', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def get_define_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('get_define() takes exactly one argument.') - check_stringlist(args) - element = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of get_define() must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - value, cached = self.compiler.get_define(element, prefix, self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached) - return value - - @permittedKwargs({ - 'name', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def compiles_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('compiles method takes exactly one argument.') - code = args[0] - if isinstance(code, mesonlib.File): - code = mesonlib.File.from_absolute_file( - code.rel_to_builddir(self.environment.source_dir)) - elif not isinstance(code, str): - raise InvalidArguments('Argument must be string or file.') - testname = kwargs.get('name', '') - if not isinstance(testname, str): - raise InterpreterException('Testname argument must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs, endl=None) - result, cached = self.compiler.compiles(code, self.environment, - extra_args=extra_args, - dependencies=deps) - if len(testname) > 0: - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - cached = mlog.blue('(cached)') if cached else '' - mlog.log('Checking if', mlog.bold(testname, True), msg, 'compiles:', h, cached) - return result - - @permittedKwargs({ - 'name', - 'no_builtin_args', - 'include_directories', - 'args', - 'dependencies', - }) - def links_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('links method takes exactly one argument.') - code = args[0] - if isinstance(code, mesonlib.File): - code = mesonlib.File.from_absolute_file( - code.rel_to_builddir(self.environment.source_dir)) - elif not isinstance(code, str): - raise InvalidArguments('Argument must be string or file.') - testname = kwargs.get('name', '') - if not isinstance(testname, str): - raise InterpreterException('Testname argument must be a string.') - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs, endl=None) - result, cached = self.compiler.links(code, self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if len(testname) > 0: - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached) - return result - - @FeatureNew('compiler.check_header', '0.47.0') - @FeatureNewKwargs('compiler.check_header', '0.50.0', ['required']) - @permittedKwargs(header_permitted_kwargs) - def check_header_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('check_header method takes exactly one argument.') - check_stringlist(args) - hname = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_header must be a string.') - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) - if disabled: - mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled') - return False - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - haz, cached = self.compiler.check_header(hname, prefix, self.environment, - extra_args=extra_args, - dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if required and not haz: - raise InterpreterException(f'{self.compiler.get_display_language()} header {hname!r} not usable') - elif haz: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log('Check usable header', mlog.bold(hname, True), msg, h, cached) - return haz - - @FeatureNewKwargs('compiler.has_header', '0.50.0', ['required']) - @permittedKwargs(header_permitted_kwargs) - def has_header_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('has_header method takes exactly one argument.') - check_stringlist(args) - hname = args[0] - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_header must be a string.') - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) - if disabled: - mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled') - return False - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - haz, cached = self.compiler.has_header(hname, prefix, self.environment, - extra_args=extra_args, dependencies=deps) - cached = mlog.blue('(cached)') if cached else '' - if required and not haz: - raise InterpreterException(f'{self.compiler.get_display_language()} header {hname!r} not found') - elif haz: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log('Has header', mlog.bold(hname, True), msg, h, cached) - return haz - - @FeatureNewKwargs('compiler.has_header_symbol', '0.50.0', ['required']) - @permittedKwargs(header_permitted_kwargs) - def has_header_symbol_method(self, args, kwargs): - if len(args) != 2: - raise InterpreterException('has_header_symbol method takes exactly two arguments.') - check_stringlist(args) - hname, symbol = args - prefix = kwargs.get('prefix', '') - if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_header_symbol must be a string.') - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) - if disabled: - mlog.log(f'Header <{hname}> has symbol', mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled') - return False - extra_args = functools.partial(self.determine_args, kwargs) - deps, msg = self.determine_dependencies(kwargs) - haz, cached = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment, - extra_args=extra_args, - dependencies=deps) - if required and not haz: - raise InterpreterException(f'{self.compiler.get_display_language()} symbol {symbol} not found in header {hname}') - elif haz: - h = mlog.green('YES') - else: - h = mlog.red('NO') - cached = mlog.blue('(cached)') if cached else '' - mlog.log(f'Header <{hname}> has symbol', mlog.bold(symbol, True), msg, h, cached) - return haz - - def notfound_library(self, libname): - lib = dependencies.ExternalLibrary(libname, None, - self.environment, - self.compiler.language, - silent=True) - return ExternalLibraryHolder(lib, self.subproject) - - @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static']) - @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers']) - @FeatureNewKwargs('compiler.find_library', '0.49.0', ['disabler']) - @disablerIfNotFound - @permittedKwargs(find_library_permitted_kwargs) - def find_library_method(self, args, kwargs): - # TODO add dependencies support? - if len(args) != 1: - raise InterpreterException('find_library method takes one argument.') - libname = args[0] - if not isinstance(libname, str): - raise InterpreterException('Library name not a string.') - - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled') - return self.notfound_library(libname) - - has_header_kwargs = {k[7:]: v for k, v in kwargs.items() if k.startswith('header_')} - has_header_kwargs['required'] = required - headers = mesonlib.stringlistify(kwargs.get('has_headers', [])) - for h in headers: - if not self.has_header_method([h], has_header_kwargs): - return self.notfound_library(libname) - - search_dirs = extract_search_dirs(kwargs) - - libtype = mesonlib.LibType.PREFER_SHARED - if 'static' in kwargs: - if not isinstance(kwargs['static'], bool): - raise InterpreterException('static must be a boolean') - libtype = mesonlib.LibType.STATIC if kwargs['static'] else mesonlib.LibType.SHARED - linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype) - if required and not linkargs: - if libtype == mesonlib.LibType.PREFER_SHARED: - libtype = 'shared or static' - else: - libtype = libtype.name.lower() - raise InterpreterException('{} {} library {!r} not found' - .format(self.compiler.get_display_language(), - libtype, libname)) - lib = dependencies.ExternalLibrary(libname, linkargs, self.environment, - self.compiler.language) - return ExternalLibraryHolder(lib, self.subproject) - - @permittedKwargs({}) - def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool: - args = mesonlib.stringlistify(args) - if len(args) != 1: - raise InterpreterException('has_argument takes exactly one argument.') - return self.has_multi_arguments_method(args, kwargs) - - @permittedKwargs({}) - def has_multi_arguments_method(self, args: T.Sequence[str], kwargs: dict): - args = mesonlib.stringlistify(args) - result, cached = self.compiler.has_multi_arguments(args, self.environment) - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - cached = mlog.blue('(cached)') if cached else '' - mlog.log( - 'Compiler for {} supports arguments {}:'.format( - self.compiler.get_display_language(), ' '.join(args)), - h, cached) - return result - - @FeatureNew('compiler.get_supported_arguments', '0.43.0') - @permittedKwargs({}) - def get_supported_arguments_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - supported_args = [] - for arg in args: - if self.has_argument_method(arg, kwargs): - supported_args.append(arg) - return supported_args - - @permittedKwargs({}) - def first_supported_argument_method(self, args: T.Sequence[str], kwargs: dict) -> T.List[str]: - for arg in mesonlib.stringlistify(args): - if self.has_argument_method(arg, kwargs): - mlog.log('First supported argument:', mlog.bold(arg)) - return [arg] - mlog.log('First supported argument:', mlog.red('None')) - return [] - - @FeatureNew('compiler.has_link_argument', '0.46.0') - @permittedKwargs({}) - def has_link_argument_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - if len(args) != 1: - raise InterpreterException('has_link_argument takes exactly one argument.') - return self.has_multi_link_arguments_method(args, kwargs) - - @FeatureNew('compiler.has_multi_link_argument', '0.46.0') - @permittedKwargs({}) - def has_multi_link_arguments_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - result, cached = self.compiler.has_multi_link_arguments(args, self.environment) - cached = mlog.blue('(cached)') if cached else '' - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log( - 'Compiler for {} supports link arguments {}:'.format( - self.compiler.get_display_language(), ' '.join(args)), - h, cached) - return result - - @FeatureNew('compiler.get_supported_link_arguments_method', '0.46.0') - @permittedKwargs({}) - def get_supported_link_arguments_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - supported_args = [] - for arg in args: - if self.has_link_argument_method(arg, kwargs): - supported_args.append(arg) - return supported_args - - @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0') - @permittedKwargs({}) - def first_supported_link_argument_method(self, args, kwargs): - for i in mesonlib.stringlistify(args): - if self.has_link_argument_method(i, kwargs): - mlog.log('First supported link argument:', mlog.bold(i)) - return [i] - mlog.log('First supported link argument:', mlog.red('None')) - return [] - - @FeatureNew('compiler.has_function_attribute', '0.48.0') - @permittedKwargs({}) - def has_func_attribute_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - if len(args) != 1: - raise InterpreterException('has_func_attribute takes exactly one argument.') - result, cached = self.compiler.has_func_attribute(args[0], self.environment) - cached = mlog.blue('(cached)') if cached else '' - h = mlog.green('YES') if result else mlog.red('NO') - mlog.log('Compiler for {} supports function attribute {}:'.format(self.compiler.get_display_language(), args[0]), h, cached) - return result - - @FeatureNew('compiler.get_supported_function_attributes', '0.48.0') - @permittedKwargs({}) - def get_supported_function_attributes_method(self, args, kwargs): - args = mesonlib.stringlistify(args) - return [a for a in args if self.has_func_attribute_method(a, kwargs)] - - @FeatureNew('compiler.get_argument_syntax_method', '0.49.0') - @noPosargs - @noKwargs - def get_argument_syntax_method(self, args, kwargs): - return self.compiler.get_argument_syntax() - - -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) - - -class Summary: - def __init__(self, project_name, project_version): - self.project_name = project_name - self.project_version = project_version - self.sections = collections.defaultdict(dict) - self.max_key_len = 0 - - def add_section(self, section, values, kwargs, subproject): - bool_yn = kwargs.get('bool_yn', False) - if not isinstance(bool_yn, bool): - raise InterpreterException('bool_yn keyword argument must be boolean') - list_sep = kwargs.get('list_sep') - if list_sep is not None and not isinstance(list_sep, str): - raise InterpreterException('list_sep keyword argument must be string') - for k, v in values.items(): - if k in self.sections[section]: - raise InterpreterException(f'Summary section {section!r} already have key {k!r}') - formatted_values = [] - for i in listify(v): - i = unholder(i) - if isinstance(i, bool) and bool_yn: - formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) - elif isinstance(i, (str, int, bool)): - formatted_values.append(str(i)) - elif isinstance(i, (ExternalProgram, Dependency)): - FeatureNew.single_use('dependency or external program in summary', '0.57.0', subproject) - formatted_values.append(i.summary_value()) - elif isinstance(i, coredata.UserOption): - FeatureNew.single_use('feature option in summary', '0.58.0', subproject) - formatted_values.append(i.printable_value()) - else: - m = 'Summary value in section {!r}, key {!r}, must be string, integer, boolean, dependency or external program' - raise InterpreterException(m.format(section, k)) - self.sections[section][k] = (formatted_values, list_sep) - self.max_key_len = max(self.max_key_len, len(k)) - - def dump(self): - mlog.log(self.project_name, mlog.normal_cyan(self.project_version)) - for section, values in self.sections.items(): - mlog.log('') # newline - if section: - mlog.log(' ', mlog.bold(section)) - for k, v in values.items(): - v, list_sep = v - padding = self.max_key_len - len(k) - end = ' ' if v else '' - mlog.log(' ' * 3, k + ' ' * padding + ':', end=end) - indent = self.max_key_len + 6 - self.dump_value(v, list_sep, indent) - mlog.log('') # newline - - def dump_value(self, arr, list_sep, indent): - lines_sep = '\n' + ' ' * indent - if list_sep is None: - mlog.log(*arr, sep=lines_sep) - return - max_len = shutil.get_terminal_size().columns - line = [] - line_len = indent - lines_sep = list_sep.rstrip() + lines_sep - for v in arr: - v_len = len(v) + len(list_sep) - if line and line_len + v_len > max_len: - mlog.log(*line, sep=list_sep, end=lines_sep) - line_len = indent - line = [] - line.append(v) - line_len += v_len - mlog.log(*line, sep=list_sep) - -class MesonMain(InterpreterObject): - def __init__(self, build: 'build.Build', interpreter: 'Interpreter'): - InterpreterObject.__init__(self) - self.build = build - self.interpreter = interpreter - self.methods.update({'get_compiler': self.get_compiler_method, - 'is_cross_build': self.is_cross_build_method, - 'has_exe_wrapper': self.has_exe_wrapper_method, - 'can_run_host_binaries': self.can_run_host_binaries_method, - 'is_unity': self.is_unity_method, - 'is_subproject': self.is_subproject_method, - 'current_source_dir': self.current_source_dir_method, - 'current_build_dir': self.current_build_dir_method, - 'source_root': self.source_root_method, - 'build_root': self.build_root_method, - 'project_source_root': self.project_source_root_method, - 'project_build_root': self.project_build_root_method, - 'add_install_script': self.add_install_script_method, - 'add_postconf_script': self.add_postconf_script_method, - 'add_dist_script': self.add_dist_script_method, - 'install_dependency_manifest': self.install_dependency_manifest_method, - 'override_dependency': self.override_dependency_method, - 'override_find_program': self.override_find_program_method, - 'project_version': self.project_version_method, - 'project_license': self.project_license_method, - 'version': self.version_method, - 'project_name': self.project_name_method, - 'get_cross_property': self.get_cross_property_method, - 'get_external_property': self.get_external_property_method, - 'has_external_property': self.has_external_property_method, - 'backend': self.backend_method, - 'add_devenv': self.add_devenv_method, - }) - - def _find_source_script(self, prog: T.Union[str, mesonlib.File, ExecutableHolder], args): - - if isinstance(prog, (ExecutableHolder, ExternalProgramHolder)): - return self.interpreter.backend.get_executable_serialisation([unholder(prog)] + args) - found = self.interpreter.func_find_program({}, prog, {}).held_object - es = self.interpreter.backend.get_executable_serialisation([found] + args) - es.subproject = self.interpreter.subproject - return es - - def _process_script_args( - self, name: str, args: T.List[T.Union[ - str, mesonlib.File, CustomTargetHolder, - CustomTargetIndexHolder, - ExternalProgramHolder, ExecutableHolder, - ]], allow_built: bool = False) -> T.List[str]: - script_args = [] # T.List[str] - new = False - for a in args: - a = unholder(a) - if isinstance(a, str): - script_args.append(a) - elif isinstance(a, mesonlib.File): - new = True - script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) - elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): - if not allow_built: - raise InterpreterException(f'Arguments to {name} cannot be built') - new = True - script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) - - # This feels really hacky, but I'm not sure how else to fix - # this without completely rewriting install script handling. - # This is complicated by the fact that the install target - # depends on all. - if isinstance(a, build.CustomTargetIndex): - a.target.build_by_default = True - else: - a.build_by_default = True - elif isinstance(a, ExternalProgram): - script_args.extend(a.command) - new = True - else: - raise InterpreterException( - 'Arguments to {} must be strings, Files, or CustomTargets, ' - 'Indexes of CustomTargets'.format(name)) - if new: - FeatureNew.single_use( - 'Calling "{}" with File, CustomTaget, Index of CustomTarget, ' - 'Executable, or ExternalProgram'.format(name), - '0.55.0', self.interpreter.subproject) - return script_args - - @FeatureNewKwargs('add_install_script', '0.57.0', ['skip_if_destdir']) - @permittedKwargs({'skip_if_destdir'}) - def add_install_script_method(self, args: 'T.Tuple[T.Union[str, mesonlib.File, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder], ...]', kwargs): - if len(args) < 1: - raise InterpreterException('add_install_script takes one or more arguments') - if isinstance(args[0], mesonlib.File): - FeatureNew.single_use('Passing file object to script parameter of add_install_script', - '0.57.0', self.interpreter.subproject) - skip_if_destdir = kwargs.get('skip_if_destdir', False) - if not isinstance(skip_if_destdir, bool): - raise InterpreterException('skip_if_destdir keyword argument must be boolean') - script_args = self._process_script_args('add_install_script', args[1:], allow_built=True) - script = self._find_source_script(args[0], script_args) - script.skip_if_destdir = skip_if_destdir - self.build.install_scripts.append(script) - - @permittedKwargs(set()) - def add_postconf_script_method(self, args, kwargs): - if len(args) < 1: - raise InterpreterException('add_postconf_script takes one or more arguments') - if isinstance(args[0], mesonlib.File): - FeatureNew.single_use('Passing file object to script parameter of add_postconf_script', - '0.57.0', self.interpreter.subproject) - script_args = self._process_script_args('add_postconf_script', args[1:], allow_built=True) - script = self._find_source_script(args[0], script_args) - self.build.postconf_scripts.append(script) - - @permittedKwargs(set()) - def add_dist_script_method(self, args, kwargs): - if len(args) < 1: - raise InterpreterException('add_dist_script takes one or more arguments') - if len(args) > 1: - FeatureNew.single_use('Calling "add_dist_script" with multiple arguments', - '0.49.0', self.interpreter.subproject) - if isinstance(args[0], mesonlib.File): - FeatureNew.single_use('Passing file object to script parameter of add_dist_script', - '0.57.0', self.interpreter.subproject) - if self.interpreter.subproject != '': - FeatureNew.single_use('Calling "add_dist_script" in a subproject', - '0.58.0', self.interpreter.subproject) - script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True) - script = self._find_source_script(args[0], script_args) - self.build.dist_scripts.append(script) - - @noPosargs - @permittedKwargs({}) - def current_source_dir_method(self, args, kwargs): - src = self.interpreter.environment.source_dir - sub = self.interpreter.subdir - if sub == '': - return src - return os.path.join(src, sub) - - @noPosargs - @permittedKwargs({}) - def current_build_dir_method(self, args, kwargs): - src = self.interpreter.environment.build_dir - sub = self.interpreter.subdir - if sub == '': - return src - return os.path.join(src, sub) - - @noPosargs - @permittedKwargs({}) - def backend_method(self, args, kwargs): - return self.interpreter.backend.name - - @noPosargs - @permittedKwargs({}) - @FeatureDeprecated('meson.source_root', '0.56.0', 'use meson.current_source_dir instead.') - def source_root_method(self, args, kwargs): - return self.interpreter.environment.source_dir - - @noPosargs - @permittedKwargs({}) - @FeatureDeprecated('meson.build_root', '0.56.0', 'use meson.current_build_dir instead.') - def build_root_method(self, args, kwargs): - return self.interpreter.environment.build_dir - - @noPosargs - @permittedKwargs({}) - @FeatureNew('meson.project_source_root', '0.56.0') - def project_source_root_method(self, args, kwargs): - src = self.interpreter.environment.source_dir - sub = self.interpreter.root_subdir - if sub == '': - return src - return os.path.join(src, sub) - - @noPosargs - @permittedKwargs({}) - @FeatureNew('meson.project_build_root', '0.56.0') - def project_build_root_method(self, args, kwargs): - src = self.interpreter.environment.build_dir - sub = self.interpreter.root_subdir - if sub == '': - return src - return os.path.join(src, sub) - - @noPosargs - @permittedKwargs({}) - @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') - def has_exe_wrapper_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: - return self.can_run_host_binaries_impl(args, kwargs) - - @noPosargs - @permittedKwargs({}) - @FeatureNew('meson.can_run_host_binaries', '0.55.0') - def can_run_host_binaries_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: - return self.can_run_host_binaries_impl(args, kwargs) - - def can_run_host_binaries_impl(self, args, kwargs): - if (self.is_cross_build_method(None, None) and - self.build.environment.need_exe_wrapper()): - if self.build.environment.exe_wrapper is None: - return False - # We return True when exe_wrap is defined, when it's not needed, and - # when we're compiling natively. The last two are semantically confusing. - # Need to revisit this. - return True - - @noPosargs - @permittedKwargs({}) - def is_cross_build_method(self, args, kwargs): - return self.build.environment.is_cross_build() - - @permittedKwargs({'native'}) - def get_compiler_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('get_compiler_method must have one and only one argument.') - cname = args[0] - for_machine = Interpreter.machine_from_native_kwarg(kwargs) - clist = self.interpreter.coredata.compilers[for_machine] - if cname in clist: - return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject) - raise InterpreterException(f'Tried to access compiler for language "{cname}", not specified for {for_machine.get_lower_case_name()} machine.') - - @noPosargs - @permittedKwargs({}) - def is_unity_method(self, args, kwargs): - optval = self.interpreter.environment.coredata.get_option(OptionKey('unity')) - if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()): - return True - return False - - @noPosargs - @permittedKwargs({}) - def is_subproject_method(self, args, kwargs): - return self.interpreter.is_subproject() - - @permittedKwargs({}) - def install_dependency_manifest_method(self, args, kwargs): - if len(args) != 1: - raise InterpreterException('Must specify manifest install file name') - if not isinstance(args[0], str): - raise InterpreterException('Argument must be a string.') - self.build.dep_manifest_name = args[0] - - @FeatureNew('meson.override_find_program', '0.46.0') - @permittedKwargs({}) - def override_find_program_method(self, args, kwargs): - if len(args) != 2: - raise InterpreterException('Override needs two arguments') - name, exe = args - if not isinstance(name, str): - raise InterpreterException('First argument must be a string') - exe = unholder(exe) - if isinstance(exe, mesonlib.File): - abspath = exe.absolute_path(self.interpreter.environment.source_dir, - self.interpreter.environment.build_dir) - if not os.path.exists(abspath): - raise InterpreterException('Tried to override %s with a file that does not exist.' % name) - exe = OverrideProgram(name, abspath) - if not isinstance(exe, (ExternalProgram, build.Executable)): - raise InterpreterException('Second argument must be an external program or executable.') - self.interpreter.add_find_program_override(name, exe) - - @FeatureNew('meson.override_dependency', '0.54.0') - @permittedKwargs({'native'}) - def override_dependency_method(self, args, kwargs): - if len(args) != 2: - raise InterpreterException('Override needs two arguments') - name = args[0] - dep = args[1] - if not isinstance(name, str) or not name: - raise InterpreterException('First argument must be a string and cannot be empty') - dep = unholder(dep) - if not isinstance(dep, dependencies.Dependency): - raise InterpreterException('Second argument must be a dependency object') - identifier = dependencies.get_dep_identifier(name, kwargs) - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) - override = self.build.dependency_overrides[for_machine].get(identifier) - if override: - m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}' - location = mlog.get_error_location_string(override.node.filename, override.node.lineno) - raise InterpreterException(m.format(name, location)) - self.build.dependency_overrides[for_machine][identifier] = \ - build.DependencyOverride(dep, self.interpreter.current_node) - - @noPosargs - @permittedKwargs({}) - def project_version_method(self, args, kwargs): - return self.build.dep_manifest[self.interpreter.active_projectname]['version'] - - @FeatureNew('meson.project_license()', '0.45.0') - @noPosargs - @permittedKwargs({}) - def project_license_method(self, args, kwargs): - return self.build.dep_manifest[self.interpreter.active_projectname]['license'] - - @noPosargs - @permittedKwargs({}) - def version_method(self, args, kwargs): - return MesonVersionString(coredata.version) - - @noPosargs - @permittedKwargs({}) - def project_name_method(self, args, kwargs): - return self.interpreter.active_projectname - - def __get_external_property_impl(self, propname: str, fallback: T.Optional[object], machine: MachineChoice) -> object: - """Shared implementation for get_cross_property and get_external_property.""" - try: - return self.interpreter.environment.properties[machine][propname] - except KeyError: - if fallback is not None: - return fallback - raise InterpreterException(f'Unknown property for {machine.get_lower_case_name()} machine: {propname}') - - @noArgsFlattening - @permittedKwargs({}) - @FeatureDeprecated('meson.get_cross_property', '0.58.0', 'Use meson.get_external_property() instead') - @typed_pos_args('meson.get_cross_property', str, optargs=[object]) - def get_cross_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: T.Dict[str, T.Any]) -> object: - propname, fallback = args - return self.__get_external_property_impl(propname, fallback, MachineChoice.HOST) - - @noArgsFlattening - @permittedKwargs({'native'}) - @FeatureNew('meson.get_external_property', '0.54.0') - @typed_pos_args('meson.get_external_property', str, optargs=[object]) - def get_external_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: T.Dict[str, T.Any]) -> object: - propname, fallback = args - machine = self.interpreter.machine_from_native_kwarg(kwargs) - return self.__get_external_property_impl(propname, fallback, machine) - - - @permittedKwargs({'native'}) - @FeatureNew('meson.has_external_property', '0.58.0') - @typed_pos_args('meson.has_external_property', str) - def has_external_property_method(self, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: - prop_name = args[0] - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) - return prop_name in self.interpreter.environment.properties[for_machine] - - @FeatureNew('add_devenv', '0.58.0') - @noKwargs - @typed_pos_args('add_devenv', (str, list, dict, EnvironmentVariablesHolder)) - def add_devenv_method(self, args: T.Union[str, list, dict, EnvironmentVariablesHolder], kwargs: T.Dict[str, T.Any]) -> None: - env = args[0] - if isinstance(env, (str, list, dict)): - env = EnvironmentVariablesHolder(env) - self.build.devenv.append(env.held_object) - - -known_library_kwargs = ( - build.known_shlib_kwargs | - build.known_stlib_kwargs -) - -known_build_target_kwargs = ( - known_library_kwargs | - build.known_exe_kwargs | - build.known_jar_kwargs | - {'target_type'} -) - -_base_test_args = {'args', 'depends', 'env', 'should_fail', 'timeout', 'workdir', 'suite', 'priority', 'protocol'} - -permitted_kwargs = {'add_global_arguments': {'language', 'native'}, - 'add_global_link_arguments': {'language', 'native'}, - 'add_languages': {'required', 'native'}, - 'add_project_link_arguments': {'language', 'native'}, - 'add_project_arguments': {'language', 'native'}, - 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default', - 'exclude_suites'}, - 'benchmark': _base_test_args, - 'build_target': known_build_target_kwargs, - 'configure_file': {'input', - 'output', - 'configuration', - 'command', - 'copy', - 'depfile', - 'install_dir', - 'install_mode', - 'capture', - 'install', - 'format', - 'output_format', - 'encoding'}, - 'custom_target': {'input', - 'output', - 'command', - 'install', - 'install_dir', - 'install_mode', - 'build_always', - 'capture', - 'depends', - 'depend_files', - 'depfile', - 'build_by_default', - 'build_always_stale', - 'console', - 'env'}, - 'dependency': {'default_options', - 'embed', - 'fallback', - 'language', - 'main', - 'method', - 'modules', - 'components', - 'cmake_module_path', - 'optional_modules', - 'native', - 'not_found_message', - 'required', - 'static', - 'version', - 'private_headers', - 'cmake_args', - 'cmake_package_version', - 'include_type', - }, - 'declare_dependency': {'include_directories', - 'link_with', - 'sources', - 'dependencies', - 'compile_args', - 'link_args', - 'link_whole', - 'version', - 'variables', - }, - 'executable': build.known_exe_kwargs, - 'find_program': {'required', 'native', 'version', 'dirs'}, - 'generator': {'arguments', - 'output', - 'depends', - 'depfile', - 'capture', - 'preserve_path_from'}, - 'include_directories': {'is_system'}, - 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, - 'install_headers': {'install_dir', 'install_mode', 'subdir'}, - 'install_man': {'install_dir', 'install_mode', 'locale'}, - 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, - 'jar': build.known_jar_kwargs, - 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, - 'run_command': {'check', 'capture', 'env'}, - 'run_target': {'command', 'depends', 'env'}, - 'shared_library': build.known_shlib_kwargs, - 'shared_module': build.known_shmod_kwargs, - 'static_library': build.known_stlib_kwargs, - 'both_libraries': known_library_kwargs, - 'library': known_library_kwargs, - 'subdir': {'if_found'}, - 'subproject': {'version', 'default_options', 'required'}, - 'test': set.union(_base_test_args, {'is_parallel'}), - 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, - } - - -class Interpreter(InterpreterBase): - - def __init__( - self, - build: build.Build, - backend: T.Optional[Backend] = None, - subproject: str = '', - subdir: str = '', - subproject_dir: str = 'subprojects', - modules: T.Optional[T.Dict[str, ModuleObject]] = None, - default_project_options: T.Optional[T.Dict[str, str]] = None, - mock: bool = False, - ast: T.Optional[mparser.CodeBlockNode] = None, - is_translated: bool = False, - ) -> None: - super().__init__(build.environment.get_source_dir(), subdir, subproject) - self.an_unpicklable_object = mesonlib.an_unpicklable_object - self.build = build - self.environment = build.environment - self.coredata = self.environment.get_coredata() - self.backend = backend - self.summary = {} - if modules is None: - self.modules = {} - else: - self.modules = modules - # Subproject directory is usually the name of the subproject, but can - # be different for dependencies provided by wrap files. - self.subproject_directory_name = subdir.split(os.path.sep)[-1] - self.subproject_dir = subproject_dir - self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') - if not mock and ast is None: - self.load_root_meson_file() - self.sanity_check_ast() - elif ast is not None: - self.ast = ast - self.sanity_check_ast() - self.builtin.update({'meson': MesonMain(build, self)}) - self.generators = [] - self.processed_buildfiles = set() # type: T.Set[str] - self.project_args_frozen = False - self.global_args_frozen = False # implies self.project_args_frozen - self.subprojects = {} - self.subproject_stack = [] - self.configure_file_outputs = {} - # Passed from the outside, only used in subprojects. - if default_project_options: - self.default_project_options = default_project_options.copy() - else: - self.default_project_options = {} - self.project_default_options = {} - self.build_func_dict() - - # build_def_files needs to be defined before parse_project is called - # - # For non-meson subprojects, we'll be using the ast. Even if it does - # exist we don't want to add a dependency on it, it's autogenerated - # from the actual build files, and is just for reference. - self.build_def_files = [] - build_filename = os.path.join(self.subdir, environment.build_filename) - if not is_translated: - self.build_def_files.append(build_filename) - if not mock: - self.parse_project() - self._redetect_machines() - - def _redetect_machines(self): - # Re-initialize machine descriptions. We can do a better job now because we - # have the compilers needed to gain more knowledge, so wipe out old - # inference and start over. - machines = self.build.environment.machines.miss_defaulting() - machines.build = environment.detect_machine_info(self.coredata.compilers.build) - self.build.environment.machines = machines.default_missing() - assert self.build.environment.machines.build.cpu is not None - assert self.build.environment.machines.host.cpu is not None - assert self.build.environment.machines.target.cpu is not None - - self.builtin['build_machine'] = \ - MachineHolder(self.build.environment.machines.build) - self.builtin['host_machine'] = \ - MachineHolder(self.build.environment.machines.host) - self.builtin['target_machine'] = \ - MachineHolder(self.build.environment.machines.target) - - # TODO: Why is this in interpreter.py and not CoreData or Environment? - def get_non_matching_default_options(self) -> T.Iterator[T.Tuple[str, str, coredata.UserOption]]: - for def_opt_name, def_opt_value in self.project_default_options.items(): - cur_opt_value = self.coredata.options.get(def_opt_name) - try: - if cur_opt_value is not None and cur_opt_value.validate_value(def_opt_value) != cur_opt_value.value: - yield (str(def_opt_name), def_opt_value, cur_opt_value) - except mesonlib.MesonException: - # Since the default value does not validate, it cannot be in use - # Report the user-specified value as non-matching - yield (str(def_opt_name), def_opt_value, cur_opt_value) - - def build_func_dict(self): - self.funcs.update({'add_global_arguments': self.func_add_global_arguments, - 'add_project_arguments': self.func_add_project_arguments, - 'add_global_link_arguments': self.func_add_global_link_arguments, - 'add_project_link_arguments': self.func_add_project_link_arguments, - 'add_test_setup': self.func_add_test_setup, - 'add_languages': self.func_add_languages, - 'alias_target': self.func_alias_target, - 'assert': self.func_assert, - 'benchmark': self.func_benchmark, - 'build_target': self.func_build_target, - 'configuration_data': self.func_configuration_data, - 'configure_file': self.func_configure_file, - 'custom_target': self.func_custom_target, - 'declare_dependency': self.func_declare_dependency, - 'dependency': self.func_dependency, - 'disabler': self.func_disabler, - 'environment': self.func_environment, - 'error': self.func_error, - 'executable': self.func_executable, - 'generator': self.func_generator, - 'gettext': self.func_gettext, - 'get_option': self.func_get_option, - 'get_variable': self.func_get_variable, - 'files': self.func_files, - 'find_library': self.func_find_library, - 'find_program': self.func_find_program, - 'include_directories': self.func_include_directories, - 'import': self.func_import, - 'install_data': self.func_install_data, - 'install_headers': self.func_install_headers, - 'install_man': self.func_install_man, - 'install_subdir': self.func_install_subdir, - 'is_disabler': self.func_is_disabler, - 'is_variable': self.func_is_variable, - 'jar': self.func_jar, - 'join_paths': self.func_join_paths, - 'library': self.func_library, - 'message': self.func_message, - 'warning': self.func_warning, - 'option': self.func_option, - 'project': self.func_project, - 'run_target': self.func_run_target, - 'run_command': self.func_run_command, - 'set_variable': self.func_set_variable, - 'subdir': self.func_subdir, - 'subdir_done': self.func_subdir_done, - 'subproject': self.func_subproject, - 'summary': self.func_summary, - 'shared_library': self.func_shared_lib, - 'shared_module': self.func_shared_module, - 'static_library': self.func_static_lib, - 'both_libraries': self.func_both_lib, - 'test': self.func_test, - 'vcs_tag': self.func_vcs_tag, - 'range': self.func_range, - }) - if 'MESON_UNIT_TEST' in os.environ: - self.funcs.update({'exception': self.func_exception}) - - def holderify(self, item): - if isinstance(item, list): - return [self.holderify(x) for x in item] - if isinstance(item, dict): - return {k: self.holderify(v) for k, v in item.items()} - - if isinstance(item, build.CustomTarget): - return CustomTargetHolder(item, self) - elif isinstance(item, (int, str, bool, Disabler, InterpreterObject)) or item is None: - return item - elif isinstance(item, build.Executable): - return ExecutableHolder(item, self) - elif isinstance(item, build.GeneratedList): - return GeneratedListHolder(item) - elif isinstance(item, build.RunTarget): - raise RuntimeError('This is not a pipe.') - elif isinstance(item, ExecutableSerialisation): - raise RuntimeError('Do not do this.') - elif isinstance(item, build.Data): - return DataHolder(item) - elif isinstance(item, dependencies.Dependency): - return DependencyHolder(item, self.subproject) - elif isinstance(item, ExternalProgram): - return ExternalProgramHolder(item, self.subproject) - elif isinstance(item, ModuleObject): - return ModuleObjectHolder(item, self) - elif isinstance(item, (InterpreterObject, ObjectHolder)): - return item - else: - raise InterpreterException('Module returned a value of unknown type.') - - def process_new_values(self, invalues): - invalues = listify(invalues) - for v in invalues: - if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)): - v = v.held_object - - if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)): - self.add_target(v.name, v) - elif isinstance(v, list): - self.process_new_values(v) - elif isinstance(v, ExecutableSerialisation): - v.subproject = self.subproject - self.build.install_scripts.append(v) - elif isinstance(v, build.Data): - self.build.data.append(v) - elif isinstance(v, dependencies.InternalDependency): - # FIXME: This is special cased and not ideal: - # The first source is our new VapiTarget, the rest are deps - self.process_new_values(v.sources[0]) - elif isinstance(v, build.InstallDir): - self.build.install_dirs.append(v) - elif isinstance(v, Test): - self.build.tests.append(v) - elif isinstance(v, (int, str, bool, Disabler, ObjectHolder, build.GeneratedList, - ExternalProgram)): - pass - else: - raise InterpreterException('Module returned a value of unknown type.') - - def get_build_def_files(self) -> T.List[str]: - return self.build_def_files - - def add_build_def_file(self, f: mesonlib.FileOrString) -> None: - # Use relative path for files within source directory, and absolute path - # for system files. Skip files within build directory. Also skip not regular - # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this - # is especially important to convert '/' to '\' on Windows. - if isinstance(f, mesonlib.File): - if f.is_built: - return - f = os.path.normpath(f.relative_name()) - elif os.path.isfile(f) and not f.startswith('/dev'): - srcdir = Path(self.environment.get_source_dir()) - builddir = Path(self.environment.get_build_dir()) - try: - f = Path(f).resolve() - except OSError: - f = Path(f) - s = f.stat() - if (hasattr(s, 'st_file_attributes') and - s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and - s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK): - # This is a Windows Store link which we can't - # resolve, so just do our best otherwise. - f = f.parent.resolve() / f.name - else: - raise - if builddir in f.parents: - return - if srcdir in f.parents: - f = f.relative_to(srcdir) - f = str(f) - else: - return - if f not in self.build_def_files: - self.build_def_files.append(f) - - def get_variables(self): - return self.variables - - def check_stdlibs(self): - machine_choices = [MachineChoice.HOST] - if self.coredata.is_cross_build(): - machine_choices.append(MachineChoice.BUILD) - for for_machine in machine_choices: - props = self.build.environment.properties[for_machine] - for l in self.coredata.compilers[for_machine].keys(): - try: - di = mesonlib.stringlistify(props.get_stdlib(l)) - except KeyError: - continue - if len(di) == 1: - FeatureNew.single_use('stdlib without variable name', '0.56.0', self.subproject) - kwargs = {'fallback': di, - 'native': for_machine is MachineChoice.BUILD, - } - name = display_name = l + '_stdlib' - dep = self.dependency_impl(name, display_name, kwargs, force_fallback=True) - self.build.stdlibs[for_machine][l] = dep - - def import_module(self, modname): - if modname in self.modules: - return - try: - module = importlib.import_module('mesonbuild.modules.' + modname) - except ImportError: - raise InvalidArguments(f'Module "{modname}" does not exist') - ext_module = module.initialize(self) - assert isinstance(ext_module, ModuleObject) - self.modules[modname] = ext_module - - @stringArgs - @noKwargs - def func_import(self, node, args, kwargs): - if len(args) != 1: - raise InvalidCode('Import takes one argument.') - modname = args[0] - if modname.startswith('unstable-'): - plainname = modname.split('-', 1)[1] - try: - # check if stable module exists - self.import_module(plainname) - mlog.warning(f'Module {modname} is now stable, please use the {plainname} module instead.') - modname = plainname - except InvalidArguments: - mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) - modname = 'unstable_' + plainname - self.import_module(modname) - return ModuleObjectHolder(self.modules[modname], self) - - @stringArgs - @noKwargs - def func_files(self, node, args, kwargs): - return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args] - - # Used by declare_dependency() and pkgconfig.generate() - def extract_variables(self, kwargs, argname='variables', list_new=False, dict_new=False): - variables = kwargs.get(argname, {}) - if isinstance(variables, dict): - if dict_new and variables: - FeatureNew.single_use('variables as dictionary', '0.56.0', self.subproject) - else: - varlist = mesonlib.stringlistify(variables) - if list_new: - FeatureNew.single_use('variables as list of strings', '0.56.0', self.subproject) - variables = collections.OrderedDict() - for v in varlist: - try: - (key, value) = v.split('=', 1) - except ValueError: - raise InterpreterException(f'Variable {v!r} must have a value separated by equals sign.') - variables[key.strip()] = value.strip() - for k, v in variables.items(): - if not k or not v: - raise InterpreterException('Empty variable name or value') - if any(c.isspace() for c in k): - raise InterpreterException(f'Invalid whitespace in variable name "{k}"') - if not isinstance(v, str): - raise InterpreterException('variables values must be strings.') - return variables - - @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole']) - @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables']) - @permittedKwargs(permitted_kwargs['declare_dependency']) - @noPosargs - def func_declare_dependency(self, node, args, kwargs): - version = kwargs.get('version', self.project_version) - if not isinstance(version, str): - raise InterpreterException('Version must be a string.') - incs = self.extract_incdirs(kwargs) - libs = unholder(extract_as_list(kwargs, 'link_with')) - libs_whole = unholder(extract_as_list(kwargs, 'link_whole')) - sources = extract_as_list(kwargs, 'sources') - sources = unholder(listify(self.source_strings_to_files(sources))) - deps = unholder(extract_as_list(kwargs, 'dependencies')) - compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) - link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) - variables = self.extract_variables(kwargs, list_new=True) - final_deps = [] - for d in deps: - try: - d = d.held_object - except Exception: - pass - if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): - raise InterpreterException('Dependencies must be external deps') - final_deps.append(d) - for l in libs: - if isinstance(l, dependencies.Dependency): - raise InterpreterException('''Entries in "link_with" may only be self-built targets, -external dependencies (including libraries) must go to "dependencies".''') - dep = dependencies.InternalDependency(version, incs, compile_args, - link_args, libs, libs_whole, sources, final_deps, - variables) - return DependencyHolder(dep, self.subproject) - - @noKwargs - def func_assert(self, node, args, kwargs): - if len(args) == 1: - FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject) - value = args[0] - message = None - elif len(args) == 2: - value, message = args - if not isinstance(message, str): - raise InterpreterException('Assert message not a string.') - else: - raise InterpreterException('Assert takes between one and two arguments') - if not isinstance(value, bool): - raise InterpreterException('Assert value not bool.') - if not value: - if message is None: - from .ast import AstPrinter - printer = AstPrinter() - node.args.arguments[0].accept(printer) - message = printer.result - raise InterpreterException('Assert failed: ' + message) - - def validate_arguments(self, args, argcount, arg_types): - if argcount is not None: - if argcount != len(args): - raise InvalidArguments('Expected %d arguments, got %d.' % - (argcount, len(args))) - for actual, wanted in zip(args, arg_types): - if wanted is not None: - if not isinstance(actual, wanted): - raise InvalidArguments('Incorrect argument type.') - - @FeatureNewKwargs('run_command', '0.50.0', ['env']) - @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture']) - @permittedKwargs(permitted_kwargs['run_command']) - def func_run_command(self, node, args, kwargs): - return self.run_command_impl(node, args, kwargs) - - def run_command_impl(self, node, args, kwargs, in_builddir=False): - if len(args) < 1: - raise InterpreterException('Not enough arguments') - cmd, *cargs = args - capture = kwargs.get('capture', True) - srcdir = self.environment.get_source_dir() - builddir = self.environment.get_build_dir() - - check = kwargs.get('check', False) - if not isinstance(check, bool): - raise InterpreterException('Check must be boolean.') - - env = self.unpack_env_kwarg(kwargs) - - m = 'must be a string, or the output of find_program(), files() '\ - 'or configure_file(), or a compiler object; not {!r}' - expanded_args = [] - if isinstance(cmd, ExternalProgramHolder): - cmd = cmd.held_object - if isinstance(cmd, build.Executable): - progname = node.args.arguments[0].value - msg = 'Program {!r} was overridden with the compiled executable {!r}'\ - ' and therefore cannot be used during configuration' - raise InterpreterException(msg.format(progname, cmd.description())) - if not cmd.found(): - raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable') - elif isinstance(cmd, CompilerHolder): - exelist = cmd.compiler.get_exelist() - cmd = exelist[0] - prog = ExternalProgram(cmd, silent=True) - if not prog.found(): - raise InterpreterException('Program {!r} not found ' - 'or not executable'.format(cmd)) - cmd = prog - expanded_args = exelist[1:] - else: - if isinstance(cmd, mesonlib.File): - cmd = cmd.absolute_path(srcdir, builddir) - elif not isinstance(cmd, str): - raise InterpreterException('First argument ' + m.format(cmd)) - # Prefer scripts in the current source directory - search_dir = os.path.join(srcdir, self.subdir) - prog = ExternalProgram(cmd, silent=True, search_dir=search_dir) - if not prog.found(): - raise InterpreterException('Program or command {!r} not found ' - 'or not executable'.format(cmd)) - cmd = prog - for a in listify(cargs): - if isinstance(a, str): - expanded_args.append(a) - elif isinstance(a, mesonlib.File): - expanded_args.append(a.absolute_path(srcdir, builddir)) - elif isinstance(a, ExternalProgramHolder): - expanded_args.append(a.held_object.get_path()) - else: - raise InterpreterException('Arguments ' + m.format(a)) - # If any file that was used as an argument to the command - # changes, we must re-run the configuration step. - self.add_build_def_file(cmd.get_path()) - for a in expanded_args: - if not os.path.isabs(a): - a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a) - self.add_build_def_file(a) - return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir, - self.environment.get_build_command() + ['introspect'], - in_builddir=in_builddir, check=check, capture=capture) - - @stringArgs - def func_gettext(self, nodes, args, kwargs): - raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead') - - def func_option(self, nodes, args, kwargs): - raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') - - @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) - @permittedKwargs(permitted_kwargs['subproject']) - @stringArgs - def func_subproject(self, nodes, args, kwargs): - if len(args) != 1: - raise InterpreterException('Subproject takes exactly one argument') - subp_name = args[0] - return self.do_subproject(subp_name, 'meson', kwargs) - - def disabled_subproject(self, subp_name, disabled_feature=None, exception=None): - sub = SubprojectHolder(None, os.path.join(self.subproject_dir, subp_name), - disabled_feature=disabled_feature, exception=exception) - self.subprojects[subp_name] = sub - return sub - - def get_subproject(self, subp_name): - sub = self.subprojects.get(subp_name) - if sub and sub.found(): - return sub - return None - - def do_subproject(self, subp_name: str, method: str, kwargs): - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') - return self.disabled_subproject(subp_name, disabled_feature=feature) - - default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - default_options = coredata.create_options_dict(default_options, subp_name) - - if subp_name == '': - raise InterpreterException('Subproject name must not be empty.') - if subp_name[0] == '.': - raise InterpreterException('Subproject name must not start with a period.') - if '..' in subp_name: - raise InterpreterException('Subproject name must not contain a ".." path segment.') - if os.path.isabs(subp_name): - raise InterpreterException('Subproject name must not be an absolute path.') - if has_path_sep(subp_name): - mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.', - location=self.current_node) - if subp_name in self.subproject_stack: - fullstack = self.subproject_stack + [subp_name] - incpath = ' => '.join(fullstack) - raise InvalidCode('Recursive include of subprojects: %s.' % incpath) - if subp_name in self.subprojects: - subproject = self.subprojects[subp_name] - if required and not subproject.found(): - raise InterpreterException('Subproject "%s" required but not found.' % (subproject.subdir)) - return subproject - - r = self.environment.wrap_resolver - try: - subdir = r.resolve(subp_name, method, self.subproject) - except wrap.WrapException as e: - if not required: - mlog.log(e) - mlog.log('Subproject ', mlog.bold(subp_name), 'is buildable:', mlog.red('NO'), '(disabling)') - return self.disabled_subproject(subp_name, exception=e) - raise e - - subdir_abs = os.path.join(self.environment.get_source_dir(), subdir) - os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) - self.global_args_frozen = True - - stack = ':'.join(self.subproject_stack + [subp_name]) - m = ['\nExecuting subproject', mlog.bold(stack)] - if method != 'meson': - m += ['method', mlog.bold(method)] - mlog.log(*m,'\n', nested=False) - - try: - if method == 'meson': - return self._do_subproject_meson(subp_name, subdir, default_options, kwargs) - elif method == 'cmake': - return self._do_subproject_cmake(subp_name, subdir, subdir_abs, default_options, kwargs) - else: - raise InterpreterException(f'The method {method} is invalid for the subproject {subp_name}') - # Invalid code is always an error - except InvalidCode: - raise - except Exception as e: - if not required: - with mlog.nested(subp_name): - # Suppress the 'ERROR:' prefix because this exception is not - # fatal and VS CI treat any logs with "ERROR:" as fatal. - mlog.exception(e, prefix=mlog.yellow('Exception:')) - mlog.log('\nSubproject', mlog.bold(subdir), 'is buildable:', mlog.red('NO'), '(disabling)') - return self.disabled_subproject(subp_name, exception=e) - raise e - - def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwargs, - ast: T.Optional[mparser.CodeBlockNode] = None, - build_def_files: T.Optional[T.List[str]] = None, - is_translated: bool = False) -> SubprojectHolder: - with mlog.nested(subp_name): - new_build = self.build.copy() - subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir, - self.modules, default_options, ast=ast, is_translated=is_translated) - subi.subprojects = self.subprojects - - subi.subproject_stack = self.subproject_stack + [subp_name] - current_active = self.active_projectname - current_warnings_counter = mlog.log_warnings_counter - mlog.log_warnings_counter = 0 - subi.run() - subi_warnings = mlog.log_warnings_counter - mlog.log_warnings_counter = current_warnings_counter - - mlog.log('Subproject', mlog.bold(subp_name), 'finished.') - - mlog.log() - - if 'version' in kwargs: - pv = subi.project_version - wanted = kwargs['version'] - if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: - raise InterpreterException(f'Subproject {subp_name} version is {pv} but {wanted} required.') - self.active_projectname = current_active - self.subprojects.update(subi.subprojects) - self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings) - # Duplicates are possible when subproject uses files from project root - if build_def_files: - self.build_def_files = list(set(self.build_def_files + build_def_files)) - # We always need the subi.build_def_files, to propgate sub-sub-projects - self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) - self.build.merge(subi.build) - self.build.subprojects[subp_name] = subi.project_version - self.summary.update(subi.summary) - return self.subprojects[subp_name] - - def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, kwargs): - with mlog.nested(subp_name): - new_build = self.build.copy() - prefix = self.coredata.options[OptionKey('prefix')].value - - from .modules.cmake import CMakeSubprojectOptions - options = kwargs.get('options', CMakeSubprojectOptions()) - if not isinstance(options, CMakeSubprojectOptions): - raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions' - ' object (created by cmake.subproject_options())') - - cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', [])) - cmake_options += options.cmake_options - cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend) - cm_int.initialise(cmake_options) - cm_int.analyse() - - # Generate a meson ast and execute it with the normal do_subproject_meson - ast = cm_int.pretend_to_be_meson(options.target_options) - - mlog.log() - with mlog.nested('cmake-ast'): - mlog.log('Processing generated meson AST') - - # Debug print the generated meson file - from .ast import AstIndentationGenerator, AstPrinter - printer = AstPrinter() - ast.accept(AstIndentationGenerator()) - ast.accept(printer) - printer.post_process() - meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build') - with open(meson_filename, "w") as f: - f.write(printer.result) - - mlog.log('Build file:', meson_filename) - mlog.cmd_ci_include(meson_filename) - mlog.log() - - result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files, is_translated=True) - result.cm_interpreter = cm_int - - mlog.log() - return result - - def get_option_internal(self, optname: str): - key = OptionKey.from_string(optname).evolve(subproject=self.subproject) - - if not key.is_project(): - for opts in [self.coredata.options, compilers.base_options]: - v = opts.get(key) - if v is None or v.yielding: - v = opts.get(key.as_root()) - if v is not None: - return v - - try: - opt = self.coredata.options[key] - if opt.yielding and key.subproject and key.as_root() in self.coredata.options: - popt = self.coredata.options[key.as_root()] - if type(opt) is type(popt): - opt = popt - else: - # Get class name, then option type as a string - opt_type = opt.__class__.__name__[4:][:-6].lower() - popt_type = popt.__class__.__name__[4:][:-6].lower() - # This is not a hard error to avoid dependency hell, the workaround - # when this happens is to simply set the subproject's option directly. - mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield ' - 'to parent option of type {3!r}, ignoring parent value. ' - 'Use -D{2}:{0}=value to set the value for this option manually' - '.'.format(optname, opt_type, self.subproject, popt_type), - location=self.current_node) - return opt - except KeyError: - pass - - raise InterpreterException('Tried to access unknown option "%s".' % optname) - - @stringArgs - @noKwargs - def func_get_option(self, nodes, args, kwargs): - if len(args) != 1: - raise InterpreterException('Argument required for get_option.') - optname = args[0] - if ':' in optname: - raise InterpreterException('Having a colon in option name is forbidden, ' - 'projects are not allowed to directly access ' - 'options of other subprojects.') - opt = self.get_option_internal(optname) - if isinstance(opt, coredata.UserFeatureOption): - return FeatureOptionHolder(self.environment, optname, opt) - elif isinstance(opt, coredata.UserOption): - return opt.value - return opt - - @noKwargs - def func_configuration_data(self, node, args, kwargs): - if len(args) > 1: - raise InterpreterException('configuration_data takes only one optional positional arguments') - elif len(args) == 1: - FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject) - initial_values = args[0] - if not isinstance(initial_values, dict): - raise InterpreterException('configuration_data first argument must be a dictionary') - else: - initial_values = {} - return ConfigurationDataHolder(self.subproject, initial_values) - - def set_backend(self): - # The backend is already set when parsing subprojects - if self.backend is not None: - return - backend = self.coredata.get_option(OptionKey('backend')) - from .backend import backends - self.backend = backends.get_backend_from_name(backend, self.build, self) - - if self.backend is None: - raise InterpreterException('Unknown backend "%s".' % backend) - if backend != self.backend.name: - if self.backend.name.startswith('vs'): - mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name)) - self.coredata.set_option(OptionKey('backend'), self.backend.name) - - # Only init backend options on first invocation otherwise it would - # override values previously set from command line. - if self.environment.first_invocation: - self.coredata.init_backend_options(backend) - - options = {k: v for k, v in self.environment.options.items() if k.is_backend()} - self.coredata.set_options(options) - - @stringArgs - @permittedKwargs(permitted_kwargs['project']) - def func_project(self, node, args, kwargs): - if len(args) < 1: - raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') - proj_name, *proj_langs = args - if ':' in proj_name: - raise InvalidArguments(f"Project name {proj_name!r} must not contain ':'") - - # This needs to be evaluated as early as possible, as meson uses this - # for things like deprecation testing. - if 'meson_version' in kwargs: - cv = coredata.version - pv = kwargs['meson_version'] - if not mesonlib.version_compare(cv, pv): - raise InterpreterException(f'Meson version is {cv} but project requires {pv}') - mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version'] - - if os.path.exists(self.option_file): - oi = optinterpreter.OptionInterpreter(self.subproject) - oi.process(self.option_file) - self.coredata.update_project_options(oi.options) - self.add_build_def_file(self.option_file) - - # Do not set default_options on reconfigure otherwise it would override - # values previously set from command line. That means that changing - # default_options in a project will trigger a reconfigure but won't - # have any effect. - self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - self.project_default_options = coredata.create_options_dict(self.project_default_options, self.subproject) - - # If this is the first invocation we alway sneed to initialize - # builtins, if this is a subproject that is new in a re-invocation we - # need to initialize builtins for that - if self.environment.first_invocation or (self.subproject != '' and self.subproject not in self.subprojects): - default_options = self.project_default_options.copy() - default_options.update(self.default_project_options) - self.coredata.init_builtins(self.subproject) - else: - default_options = {} - self.coredata.set_default_options(default_options, self.subproject, self.environment) - - if not self.is_subproject(): - self.build.project_name = proj_name - self.active_projectname = proj_name - version = kwargs.get('version', 'undefined') - if isinstance(version, list): - if len(version) != 1: - raise InvalidCode('Version argument is an array with more than one entry.') - version = version[0] - if isinstance(version, mesonlib.File): - FeatureNew.single_use('version from file', '0.57.0', self.subproject) - self.add_build_def_file(version) - ifname = version.absolute_path(self.environment.source_dir, - self.environment.build_dir) - try: - ver_data = Path(ifname).read_text(encoding='utf-8').split('\n') - except FileNotFoundError: - raise InterpreterException('Version file not found.') - if len(ver_data) == 2 and ver_data[1] == '': - ver_data = ver_data[0:1] - if len(ver_data) != 1: - raise InterpreterException('Version file must contain exactly one line of text.') - self.project_version = ver_data[0] - elif isinstance(version, str): - self.project_version = version - else: - raise InvalidCode('The version keyword argument must be a string or a file.') - if self.build.project_version is None: - self.build.project_version = self.project_version - proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) - self.build.dep_manifest[proj_name] = {'version': self.project_version, - 'license': proj_license} - if self.subproject in self.build.projects: - raise InvalidCode('Second call to project().') - - # spdirname is the subproject_dir for this project, relative to self.subdir. - # self.subproject_dir is the subproject_dir for the main project, relative to top source dir. - spdirname = kwargs.get('subproject_dir') - if spdirname: - if not isinstance(spdirname, str): - raise InterpreterException('Subproject_dir must be a string') - if os.path.isabs(spdirname): - raise InterpreterException('Subproject_dir must not be an absolute path.') - if spdirname.startswith('.'): - raise InterpreterException('Subproject_dir must not begin with a period.') - if '..' in spdirname: - raise InterpreterException('Subproject_dir must not contain a ".." segment.') - if not self.is_subproject(): - self.subproject_dir = spdirname - else: - spdirname = 'subprojects' - self.build.subproject_dir = self.subproject_dir - - # Load wrap files from this (sub)project. - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) - if not self.is_subproject() or wrap_mode != WrapMode.nopromote: - subdir = os.path.join(self.subdir, spdirname) - r = wrap.Resolver(self.environment.get_source_dir(), subdir, wrap_mode) - if self.is_subproject(): - self.environment.wrap_resolver.merge_wraps(r) - else: - self.environment.wrap_resolver = r - - self.build.projects[self.subproject] = proj_name - mlog.log('Project name:', mlog.bold(proj_name)) - mlog.log('Project version:', mlog.bold(self.project_version)) - - self.add_languages(proj_langs, True, MachineChoice.HOST) - self.add_languages(proj_langs, False, MachineChoice.BUILD) - - self.set_backend() - if not self.is_subproject(): - self.check_stdlibs() - - @FeatureNewKwargs('add_languages', '0.54.0', ['native']) - @permittedKwargs(permitted_kwargs['add_languages']) - @stringArgs - def func_add_languages(self, node, args, kwargs): - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - for lang in sorted(args, key=compilers.sort_clink): - mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled') - return False - if 'native' in kwargs: - return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs)) - else: - # absent 'native' means 'both' for backwards compatibility - tv = FeatureNew.get_target_version(self.subproject) - if FeatureNew.check_version(tv, '0.54.0'): - mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.', - location=self.current_node) - - success = self.add_languages(args, False, MachineChoice.BUILD) - success &= self.add_languages(args, required, MachineChoice.HOST) - return success - - @noArgsFlattening - @noKwargs - def func_message(self, node, args, kwargs): - if len(args) > 1: - FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject) - args_str = [stringifyUserArguments(i) for i in args] - self.message_impl(args_str) - - def message_impl(self, args): - mlog.log(mlog.bold('Message:'), *args) - - @noArgsFlattening - @FeatureNewKwargs('summary', '0.54.0', ['list_sep']) - @permittedKwargs({'section', 'bool_yn', 'list_sep'}) - @FeatureNew('summary', '0.53.0') - def func_summary(self, node, args, kwargs): - if len(args) == 1: - if not isinstance(args[0], dict): - raise InterpreterException('Summary first argument must be dictionary.') - values = args[0] - elif len(args) == 2: - if not isinstance(args[0], str): - raise InterpreterException('Summary first argument must be string.') - values = {args[0]: args[1]} - else: - raise InterpreterException('Summary accepts at most 2 arguments.') - section = kwargs.get('section', '') - if not isinstance(section, str): - raise InterpreterException('Summary\'s section keyword argument must be string.') - self.summary_impl(section, values, kwargs) - - def summary_impl(self, section, values, kwargs): - if self.subproject not in self.summary: - self.summary[self.subproject] = Summary(self.active_projectname, self.project_version) - self.summary[self.subproject].add_section(section, values, kwargs, self.subproject) - - def _print_summary(self): - # Add automatic 'Supbrojects' section in main project. - all_subprojects = collections.OrderedDict() - for name, subp in sorted(self.subprojects.items()): - value = subp.found() - if subp.disabled_feature: - value = [value, f'Feature {subp.disabled_feature!r} disabled'] - elif subp.exception: - value = [value, str(subp.exception)] - elif subp.warnings > 0: - value = [value, f'{subp.warnings} warnings'] - all_subprojects[name] = value - if all_subprojects: - self.summary_impl('Subprojects', all_subprojects, - {'bool_yn': True, - 'list_sep': ' ', - }) - # Print all summaries, main project last. - mlog.log('') # newline - main_summary = self.summary.pop('', None) - for _, summary in sorted(self.summary.items()): - summary.dump() - if main_summary: - main_summary.dump() - - @noArgsFlattening - @FeatureNew('warning', '0.44.0') - @noKwargs - def func_warning(self, node, args, kwargs): - if len(args) > 1: - FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject) - args_str = [stringifyUserArguments(i) for i in args] - mlog.warning(*args_str, location=node) - - @noArgsFlattening - @noKwargs - def func_error(self, node, args, kwargs): - if len(args) > 1: - FeatureNew.single_use('error with more than one argument', '0.58.0', self.subproject) - args_str = [stringifyUserArguments(i) for i in args] - raise InterpreterException('Problem encountered: ' + ' '.join(args_str)) - - @noKwargs - @noPosargs - def func_exception(self, node, args, kwargs): - raise Exception() - - def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool: - success = self.add_languages_for(args, required, for_machine) - if not self.coredata.is_cross_build(): - self.coredata.copy_build_options_from_regular_ones() - self._redetect_machines() - return success - - def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool: - should = self.environment.properties.host.get('skip_sanity_check', False) - if not isinstance(should, bool): - raise InterpreterException('Option skip_sanity_check must be a boolean.') - if for_machine != MachineChoice.HOST and not should: - return False - if not self.environment.is_cross_build() and not should: - return False - return should - - def add_languages_for(self, args, required, for_machine: MachineChoice): - args = [a.lower() for a in args] - langs = set(self.coredata.compilers[for_machine].keys()) - langs.update(args) - if 'vala' in langs: - if 'c' not in langs: - raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') - - success = True - for lang in sorted(args, key=compilers.sort_clink): - clist = self.coredata.compilers[for_machine] - machine_name = for_machine.get_lower_case_name() - if lang in clist: - comp = clist[lang] - else: - try: - comp = self.environment.detect_compiler_for(lang, for_machine) - if comp is None: - raise InvalidArguments('Tried to use unknown language "%s".' % lang) - if self.should_skip_sanity_check(for_machine): - mlog.log_once('Cross compiler sanity tests disabled via the cross file.') - else: - comp.sanity_check(self.environment.get_scratch_dir(), self.environment) - except Exception: - if not required: - mlog.log('Compiler for language', - mlog.bold(lang), 'for the', machine_name, - 'machine not found.') - success = False - continue - else: - raise - - if for_machine == MachineChoice.HOST or self.environment.is_cross_build(): - logger_fun = mlog.log - else: - logger_fun = mlog.debug - logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:', - mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string()) - if comp.linker is not None: - logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:', - mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version) - self.build.ensure_static_linker(comp) - - return success - - def program_from_file_for(self, for_machine, prognames): - for p in unholder(prognames): - if isinstance(p, mesonlib.File): - continue # Always points to a local (i.e. self generated) file. - if not isinstance(p, str): - raise InterpreterException('Executable name must be a string') - prog = ExternalProgram.from_bin_list(self.environment, for_machine, p) - if prog.found(): - return ExternalProgramHolder(prog, self.subproject) - return None - - def program_from_system(self, args, search_dirs, extra_info): - # Search for scripts relative to current subdir. - # Do not cache found programs because find_program('foobar') - # might give different results when run from different source dirs. - source_dir = os.path.join(self.environment.get_source_dir(), self.subdir) - for exename in args: - if isinstance(exename, mesonlib.File): - if exename.is_built: - search_dir = os.path.join(self.environment.get_build_dir(), - exename.subdir) - else: - search_dir = os.path.join(self.environment.get_source_dir(), - exename.subdir) - exename = exename.fname - extra_search_dirs = [] - elif isinstance(exename, str): - search_dir = source_dir - extra_search_dirs = search_dirs - else: - raise InvalidArguments('find_program only accepts strings and ' - 'files, not {!r}'.format(exename)) - extprog = ExternalProgram(exename, search_dir=search_dir, - extra_search_dirs=extra_search_dirs, - silent=True) - progobj = ExternalProgramHolder(extprog, self.subproject) - if progobj.found(): - extra_info.append(f"({' '.join(progobj.get_command())})") - return progobj - - def program_from_overrides(self, command_names, extra_info): - for name in command_names: - if not isinstance(name, str): - continue - if name in self.build.find_overrides: - exe = self.build.find_overrides[name] - extra_info.append(mlog.blue('(overridden)')) - return ExternalProgramHolder(exe, self.subproject, self.backend) - return None - - def store_name_lookups(self, command_names): - for name in command_names: - if isinstance(name, str): - self.build.searched_programs.add(name) - - def add_find_program_override(self, name, exe): - if name in self.build.searched_programs: - raise InterpreterException('Tried to override finding of executable "%s" which has already been found.' - % name) - if name in self.build.find_overrides: - raise InterpreterException('Tried to override executable "%s" which has already been overridden.' - % name) - self.build.find_overrides[name] = exe - - def notfound_program(self, args): - return ExternalProgramHolder(NonExistingExternalProgram(' '.join(args)), self.subproject) - - # TODO update modules to always pass `for_machine`. It is bad-form to assume - # the host machine. - def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, - required=True, silent=True, wanted='', search_dirs=None, - version_func=None): - args = mesonlib.listify(args) - - extra_info = [] - progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info) - if progobj is None: - progobj = self.notfound_program(args) - - if not progobj.found(): - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO')) - if required: - m = 'Program {!r} not found' - raise InterpreterException(m.format(progobj.get_name())) - return progobj - - if wanted: - if version_func: - version = version_func(progobj) - else: - version = progobj.get_version(self) - is_found, not_found, found = mesonlib.version_compare_many(version, wanted) - if not is_found: - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), - 'found', mlog.normal_cyan(version), 'but need:', - mlog.bold(', '.join([f"'{e}'" for e in not_found])), *extra_info) - if required: - m = 'Invalid version of program, need {!r} {!r} found {!r}.' - raise InterpreterException(m.format(progobj.get_name(), not_found, version)) - return self.notfound_program(args) - extra_info.insert(0, mlog.normal_cyan(version)) - - # Only store successful lookups - self.store_name_lookups(args) - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.green('YES'), *extra_info) - return progobj - - def program_lookup(self, args, for_machine, required, search_dirs, extra_info): - progobj = self.program_from_overrides(args, extra_info) - if progobj: - return progobj - - fallback = None - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) - if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver: - fallback = self.environment.wrap_resolver.find_program_provider(args) - if fallback and wrap_mode == WrapMode.forcefallback: - return self.find_program_fallback(fallback, args, required, extra_info) - - progobj = self.program_from_file_for(for_machine, args) - if progobj is None: - progobj = self.program_from_system(args, search_dirs, extra_info) - if progobj is None and args[0].endswith('python3'): - prog = ExternalProgram('python3', mesonlib.python_command, silent=True) - progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None - if progobj is None and fallback and required: - progobj = self.find_program_fallback(fallback, args, required, extra_info) - - return progobj - - def find_program_fallback(self, fallback, args, required, extra_info): - mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', - mlog.bold(' '.join(args))) - sp_kwargs = { 'required': required } - self.do_subproject(fallback, 'meson', sp_kwargs) - return self.program_from_overrides(args, extra_info) - - @FeatureNewKwargs('find_program', '0.53.0', ['dirs']) - @FeatureNewKwargs('find_program', '0.52.0', ['version']) - @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) - @disablerIfNotFound - @permittedKwargs(permitted_kwargs['find_program']) - def func_find_program(self, node, args, kwargs): - if not args: - raise InterpreterException('No program name specified.') - - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled') - return self.notfound_program(args) - - search_dirs = extract_search_dirs(kwargs) - wanted = mesonlib.stringlistify(kwargs.get('version', [])) - for_machine = self.machine_from_native_kwarg(kwargs) - return self.find_program_impl(args, for_machine, required=required, - silent=False, wanted=wanted, - search_dirs=search_dirs) - - def func_find_library(self, node, args, kwargs): - raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n' - 'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n' - 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n' - ) - - def _find_cached_dep(self, name, display_name, kwargs): - # Check if we want this as a build-time / build machine or runt-time / - # host machine dep. - for_machine = self.machine_from_native_kwarg(kwargs) - identifier = dependencies.get_dep_identifier(name, kwargs) - wanted_vers = mesonlib.stringlistify(kwargs.get('version', [])) - - override = self.build.dependency_overrides[for_machine].get(identifier) - if override: - info = [mlog.blue('(overridden)' if override.explicit else '(cached)')] - cached_dep = override.dep - # We don't implicitly override not-found dependencies, but user could - # have explicitly called meson.override_dependency() with a not-found - # dep. - if not cached_dep.found(): - mlog.log('Dependency', mlog.bold(display_name), - 'found:', mlog.red('NO'), *info) - return identifier, cached_dep - found_vers = cached_dep.get_version() - if not self.check_version(wanted_vers, found_vers): - mlog.log('Dependency', mlog.bold(name), - 'found:', mlog.red('NO'), - 'found', mlog.normal_cyan(found_vers), 'but need:', - mlog.bold(', '.join([f"'{e}'" for e in wanted_vers])), - *info) - return identifier, NotFoundDependency(self.environment) - else: - info = [mlog.blue('(cached)')] - cached_dep = self.coredata.deps[for_machine].get(identifier) - if cached_dep: - found_vers = cached_dep.get_version() - if not self.check_version(wanted_vers, found_vers): - return identifier, None - - if cached_dep: - if found_vers: - info = [mlog.normal_cyan(found_vers), *info] - mlog.log('Dependency', mlog.bold(display_name), - 'found:', mlog.green('YES'), *info) - return identifier, cached_dep - - return identifier, None - - @staticmethod - def check_version(wanted, found): - if not wanted: - return True - if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]: - return False - return True - - def notfound_dependency(self): - return DependencyHolder(NotFoundDependency(self.environment), self.subproject) - - def verify_fallback_consistency(self, subp_name, varname, cached_dep): - subi = self.get_subproject(subp_name) - if not cached_dep or not varname or not subi or not cached_dep.found(): - return - dep = subi.get_variable_method([varname], {}) - if dep.held_object != cached_dep: - m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}' - raise DependencyException(m.format(varname)) - - def get_subproject_dep(self, name, display_name, subp_name, varname, kwargs): - required = kwargs.get('required', True) - wanted = mesonlib.stringlistify(kwargs.get('version', [])) - dep = self.notfound_dependency() - - # Verify the subproject is found - subproject = self.subprojects.get(subp_name) - if not subproject or not subproject.found(): - mlog.log('Dependency', mlog.bold(display_name), 'from subproject', - mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), - mlog.blue('(subproject failed to configure)')) - if required: - m = 'Subproject {} failed to configure for dependency {}' - raise DependencyException(m.format(subproject.subdir, display_name)) - return dep - - extra_info = [] - try: - # Check if the subproject overridden the dependency - _, cached_dep = self._find_cached_dep(name, display_name, kwargs) - if cached_dep: - if varname: - self.verify_fallback_consistency(subp_name, varname, cached_dep) - if required and not cached_dep.found(): - m = 'Dependency {!r} is not satisfied' - raise DependencyException(m.format(display_name)) - return DependencyHolder(cached_dep, self.subproject) - elif varname is None: - mlog.log('Dependency', mlog.bold(display_name), 'from subproject', - mlog.bold(subproject.subdir), 'found:', mlog.red('NO')) - if required: - m = 'Subproject {} did not override dependency {}' - raise DependencyException(m.format(subproject.subdir, display_name)) - return self.notfound_dependency() - else: - # The subproject did not override the dependency, but we know the - # variable name to take. - dep = subproject.get_variable_method([varname], {}) - except InvalidArguments: - # This is raised by get_variable_method() if varname does no exist - # in the subproject. Just add the reason in the not-found message - # that will be printed later. - extra_info.append(mlog.blue(f'(Variable {varname!r} not found)')) - - if not isinstance(dep, DependencyHolder): - raise InvalidCode('Fetched variable {!r} in the subproject {!r} is ' - 'not a dependency object.'.format(varname, subp_name)) - - if not dep.found(): - mlog.log('Dependency', mlog.bold(display_name), 'from subproject', - mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), *extra_info) - if required: - raise DependencyException('Could not find dependency {} in subproject {}' - ''.format(varname, subp_name)) - return dep - - found = dep.held_object.get_version() - if not self.check_version(wanted, found): - mlog.log('Dependency', mlog.bold(display_name), 'from subproject', - mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), - 'found', mlog.normal_cyan(found), 'but need:', - mlog.bold(', '.join([f"'{e}'" for e in wanted]))) - if required: - raise DependencyException('Version {} of subproject dependency {} already ' - 'cached, requested incompatible version {} for ' - 'dep {}'.format(found, subp_name, wanted, display_name)) - return self.notfound_dependency() - - found = mlog.normal_cyan(found) if found else None - mlog.log('Dependency', mlog.bold(display_name), 'from subproject', - mlog.bold(subproject.subdir), 'found:', mlog.green('YES'), found) - return dep - - def _handle_featurenew_dependencies(self, name): - 'Do a feature check on dependencies used by this subproject' - if name == 'mpi': - FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject) - elif name == 'pcap': - FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject) - elif name == 'vulkan': - FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject) - elif name == 'libwmf': - FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject) - elif name == 'openmp': - FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject) - - @FeatureNewKwargs('dependency', '0.57.0', ['cmake_package_version']) - @FeatureNewKwargs('dependency', '0.54.0', ['components']) - @FeatureNewKwargs('dependency', '0.52.0', ['include_type']) - @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args']) - @FeatureNewKwargs('dependency', '0.49.0', ['disabler']) - @FeatureNewKwargs('dependency', '0.40.0', ['method']) - @FeatureNewKwargs('dependency', '0.38.0', ['default_options']) - @disablerIfNotFound - @permittedKwargs(permitted_kwargs['dependency']) - def func_dependency(self, node, args, kwargs): - self.validate_arguments(args, 1, [str]) - name = args[0] - display_name = name if name else '(anonymous)' - mods = extract_as_list(kwargs, 'modules') - if mods: - display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods)) - not_found_message = kwargs.get('not_found_message', '') - if not isinstance(not_found_message, str): - raise InvalidArguments('The not_found_message must be a string.') - try: - d = self.dependency_impl(name, display_name, kwargs) - except Exception: - if not_found_message: - self.message_impl([not_found_message]) - raise - assert isinstance(d, DependencyHolder) - if not d.found() and not_found_message: - self.message_impl([not_found_message]) - self.message_impl([not_found_message]) - # Override this dependency to have consistent results in subsequent - # dependency lookups. - if name and d.found(): - for_machine = self.machine_from_native_kwarg(kwargs) - identifier = dependencies.get_dep_identifier(name, kwargs) - if identifier not in self.build.dependency_overrides[for_machine]: - self.build.dependency_overrides[for_machine][identifier] = \ - build.DependencyOverride(d.held_object, node, explicit=False) - # Ensure the correct include type - if 'include_type' in kwargs: - wanted = kwargs['include_type'] - actual = d.include_type_method([], {}) - if wanted != actual: - mlog.debug(f'Current include type of {name} is {actual}. Converting to requested {wanted}') - d = d.as_system_method([wanted], {}) - return d - - def dependency_impl(self, name, display_name, kwargs, force_fallback=False): - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled') - return self.notfound_dependency() - - fallback = kwargs.get('fallback', None) - allow_fallback = kwargs.get('allow_fallback', None) - if allow_fallback is not None: - FeatureNew.single_use('"allow_fallback" keyword argument for dependency', '0.56.0', self.subproject) - if fallback is not None: - raise InvalidArguments('"fallback" and "allow_fallback" arguments are mutually exclusive') - if not isinstance(allow_fallback, bool): - raise InvalidArguments('"allow_fallback" argument must be boolean') - - # If "fallback" is absent, look for an implicit fallback. - if name and fallback is None and allow_fallback is not False: - # Add an implicit fallback if we have a wrap file or a directory with the same name, - # but only if this dependency is required. It is common to first check for a pkg-config, - # then fallback to use find_library() and only afterward check again the dependency - # with a fallback. If the fallback has already been configured then we have to use it - # even if the dependency is not required. - provider = self.environment.wrap_resolver.find_dep_provider(name) - if not provider and allow_fallback is True: - raise InvalidArguments('Fallback wrap or subproject not found for dependency \'%s\'' % name) - subp_name = mesonlib.listify(provider)[0] - if provider and (allow_fallback is True or required or self.get_subproject(subp_name)): - fallback = provider - - if 'default_options' in kwargs and not fallback: - mlog.warning('The "default_options" keyword argument does nothing without a fallback subproject.', - location=self.current_node) - - # writing just "dependency('')" is an error, because it can only fail - if name == '' and required and not fallback: - raise InvalidArguments('Dependency is both required and not-found') - - if '<' in name or '>' in name or '=' in name: - raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify' - 'version\n requirements use the \'version\' keyword argument instead.') - - identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs) - if cached_dep: - if fallback: - subp_name, varname = self.get_subproject_infos(fallback) - self.verify_fallback_consistency(subp_name, varname, cached_dep) - if required and not cached_dep.found(): - m = 'Dependency {!r} was already checked and was not found' - raise DependencyException(m.format(display_name)) - return DependencyHolder(cached_dep, self.subproject) - - if fallback: - # If the dependency has already been configured, possibly by - # a higher level project, try to use it first. - subp_name, varname = self.get_subproject_infos(fallback) - if self.get_subproject(subp_name): - return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs) - - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) - force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) - force_fallback = (force_fallback or - wrap_mode == WrapMode.forcefallback or - name in force_fallback_for or - subp_name in force_fallback_for) - - if name != '' and (not fallback or not force_fallback): - self._handle_featurenew_dependencies(name) - kwargs['required'] = required and not fallback - dep = dependencies.find_external_dependency(name, self.environment, kwargs) - kwargs['required'] = required - # Only store found-deps in the cache - # Never add fallback deps to self.coredata.deps since we - # cannot cache them. They must always be evaluated else - # we won't actually read all the build files. - if dep.found(): - for_machine = self.machine_from_native_kwarg(kwargs) - self.coredata.deps[for_machine].put(identifier, dep) - return DependencyHolder(dep, self.subproject) - - if fallback: - return self.dependency_fallback(name, display_name, fallback, kwargs) - - return self.notfound_dependency() - - @FeatureNew('disabler', '0.44.0') - @noKwargs - @noPosargs - def func_disabler(self, node, args, kwargs): - return Disabler() - - def get_subproject_infos(self, fbinfo): - fbinfo = mesonlib.stringlistify(fbinfo) - if len(fbinfo) == 1: - FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject) - return fbinfo[0], None - elif len(fbinfo) != 2: - raise InterpreterException('Fallback info must have one or two items.') - return fbinfo - - def dependency_fallback(self, name, display_name, fallback, kwargs): - subp_name, varname = self.get_subproject_infos(fallback) - required = kwargs.get('required', True) - - # Explicitly listed fallback preferences for specific subprojects - # take precedence over wrap-mode - force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) - if name in force_fallback_for or subp_name in force_fallback_for: - mlog.log('Looking for a fallback subproject for the dependency', - mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject') - elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.nofallback: - mlog.log('Not looking for a fallback subproject for the dependency', - mlog.bold(display_name), 'because:\nUse of fallback ' - 'dependencies is disabled.') - if required: - m = 'Dependency {!r} not found and fallback is disabled' - raise DependencyException(m.format(display_name)) - return self.notfound_dependency() - elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.forcefallback: - mlog.log('Looking for a fallback subproject for the dependency', - mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.') - else: - mlog.log('Looking for a fallback subproject for the dependency', - mlog.bold(display_name)) - sp_kwargs = { - 'default_options': kwargs.get('default_options', []), - 'required': required, - } - self.do_subproject(subp_name, 'meson', sp_kwargs) - return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs) - - @FeatureNewKwargs('executable', '0.42.0', ['implib']) - @FeatureNewKwargs('executable', '0.56.0', ['win_subsystem']) - @FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.") - @permittedKwargs(permitted_kwargs['executable']) - def func_executable(self, node, args, kwargs): - return self.build_target(node, args, kwargs, ExecutableHolder) - - @permittedKwargs(permitted_kwargs['static_library']) - def func_static_lib(self, node, args, kwargs): - return self.build_target(node, args, kwargs, StaticLibraryHolder) - - @permittedKwargs(permitted_kwargs['shared_library']) - def func_shared_lib(self, node, args, kwargs): - holder = self.build_target(node, args, kwargs, SharedLibraryHolder) - holder.held_object.shared_library_only = True - return holder - - @permittedKwargs(permitted_kwargs['both_libraries']) - def func_both_lib(self, node, args, kwargs): - return self.build_both_libraries(node, args, kwargs) - - @FeatureNew('shared_module', '0.37.0') - @permittedKwargs(permitted_kwargs['shared_module']) - def func_shared_module(self, node, args, kwargs): - return self.build_target(node, args, kwargs, SharedModuleHolder) - - @permittedKwargs(permitted_kwargs['library']) - def func_library(self, node, args, kwargs): - return self.build_library(node, args, kwargs) - - @permittedKwargs(permitted_kwargs['jar']) - def func_jar(self, node, args, kwargs): - return self.build_target(node, args, kwargs, JarHolder) - - @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options']) - @permittedKwargs(permitted_kwargs['build_target']) - def func_build_target(self, node, args, kwargs): - if 'target_type' not in kwargs: - raise InterpreterException('Missing target_type keyword argument') - target_type = kwargs.pop('target_type') - if target_type == 'executable': - return self.build_target(node, args, kwargs, ExecutableHolder) - elif target_type == 'shared_library': - return self.build_target(node, args, kwargs, SharedLibraryHolder) - elif target_type == 'shared_module': - FeatureNew('build_target(target_type: \'shared_module\')', - '0.51.0').use(self.subproject) - return self.build_target(node, args, kwargs, SharedModuleHolder) - elif target_type == 'static_library': - return self.build_target(node, args, kwargs, StaticLibraryHolder) - elif target_type == 'both_libraries': - return self.build_both_libraries(node, args, kwargs) - elif target_type == 'library': - return self.build_library(node, args, kwargs) - elif target_type == 'jar': - return self.build_target(node, args, kwargs, JarHolder) - else: - raise InterpreterException('Unknown target_type.') - - @permittedKwargs(permitted_kwargs['vcs_tag']) - @FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'], - 'combine build_by_default and build_always_stale instead.') - @noPosargs - def func_vcs_tag(self, node, args, kwargs): - if 'input' not in kwargs or 'output' not in kwargs: - raise InterpreterException('Keyword arguments input and output must exist') - if 'fallback' not in kwargs: - FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject) - fallback = kwargs.pop('fallback', self.project_version) - if not isinstance(fallback, str): - raise InterpreterException('Keyword argument fallback must be a string.') - replace_string = kwargs.pop('replace_string', '@VCS_TAG@') - regex_selector = '(.*)' # default regex selector for custom command: use complete output - vcs_cmd = kwargs.get('command', None) - if vcs_cmd and not isinstance(vcs_cmd, list): - vcs_cmd = [vcs_cmd] - source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir)) - if vcs_cmd: - # Is the command an executable in path or maybe a script in the source tree? - vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0]) - else: - vcs = mesonlib.detect_vcs(source_dir) - if vcs: - mlog.log('Found {} repository at {}'.format(vcs['name'], vcs['wc_dir'])) - vcs_cmd = vcs['get_rev'].split() - regex_selector = vcs['rev_regex'] - else: - vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string - # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command... - kwargs['command'] = self.environment.get_build_command() + \ - ['--internal', - 'vcstagger', - '@INPUT0@', - '@OUTPUT0@', - fallback, - source_dir, - replace_string, - regex_selector] + vcs_cmd - kwargs.setdefault('build_by_default', True) - kwargs.setdefault('build_always_stale', True) - return self._func_custom_target_impl(node, [kwargs['output']], kwargs) - - @FeatureNew('subdir_done', '0.46.0') - @noPosargs - @noKwargs - def func_subdir_done(self, node, args, kwargs): - raise SubdirDoneRequest() - - @stringArgs - @FeatureNewKwargs('custom_target', '0.57.0', ['env']) - @FeatureNewKwargs('custom_target', '0.48.0', ['console']) - @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale']) - @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default']) - @permittedKwargs(permitted_kwargs['custom_target']) - def func_custom_target(self, node, args, kwargs): - if len(args) != 1: - raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') - if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): - FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) - return self._func_custom_target_impl(node, args, kwargs) - - def _func_custom_target_impl(self, node, args, kwargs): - 'Implementation-only, without FeatureNew checks, for internal use' - name = args[0] - kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) - if 'input' in kwargs: - try: - kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input')) - except mesonlib.MesonException: - mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s). -This will become a hard error in the future.''' % kwargs['input'], location=self.current_node) - kwargs['env'] = self.unpack_env_kwarg(kwargs) - if 'command' in kwargs and isinstance(kwargs['command'], list) and kwargs['command']: - if isinstance(kwargs['command'][0], str): - kwargs['command'][0] = self.func_find_program(node, kwargs['command'][0], {}) - tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self) - self.add_target(name, tg.held_object) - return tg - - @FeatureNewKwargs('run_target', '0.57.0', ['env']) - @permittedKwargs(permitted_kwargs['run_target']) - def func_run_target(self, node, args, kwargs): - if len(args) > 1: - raise InvalidCode('Run_target takes only one positional argument: the target name.') - elif len(args) == 1: - if 'command' not in kwargs: - raise InterpreterException('Missing "command" keyword argument') - all_args = extract_as_list(kwargs, 'command') - deps = unholder(extract_as_list(kwargs, 'depends')) - else: - raise InterpreterException('Run_target needs at least one positional argument.') - - cleaned_args = [] - for i in unholder(listify(all_args)): - if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, ExternalProgram, mesonlib.File)): - mlog.debug('Wrong type:', str(i)) - raise InterpreterException('Invalid argument to run_target.') - if isinstance(i, ExternalProgram) and not i.found(): - raise InterpreterException(f'Tried to use non-existing executable {i.name!r}') - cleaned_args.append(i) - if isinstance(cleaned_args[0], str): - cleaned_args[0] = self.func_find_program(node, cleaned_args[0], {}) - name = args[0] - if not isinstance(name, str): - raise InterpreterException('First argument must be a string.') - cleaned_deps = [] - for d in deps: - if not isinstance(d, (build.BuildTarget, build.CustomTarget)): - raise InterpreterException('Depends items must be build targets.') - cleaned_deps.append(d) - env = self.unpack_env_kwarg(kwargs) - tg = RunTargetHolder(build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env), self) - self.add_target(name, tg.held_object) - full_name = (self.subproject, name) - assert(full_name not in self.build.run_target_names) - self.build.run_target_names.add(full_name) - return tg - - @FeatureNew('alias_target', '0.52.0') - @noKwargs - def func_alias_target(self, node, args, kwargs): - if len(args) < 2: - raise InvalidCode('alias_target takes at least 2 arguments.') - name = args[0] - if not isinstance(name, str): - raise InterpreterException('First argument must be a string.') - deps = unholder(listify(args[1:])) - for d in deps: - if not isinstance(d, (build.BuildTarget, build.CustomTarget)): - raise InterpreterException('Depends items must be build targets.') - tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self) - self.add_target(name, tg.held_object) - return tg - - @permittedKwargs(permitted_kwargs['generator']) - def func_generator(self, node, args, kwargs): - gen = GeneratorHolder(self, args, kwargs) - self.generators.append(gen) - return gen - - @FeatureNewKwargs('benchmark', '0.46.0', ['depends']) - @FeatureNewKwargs('benchmark', '0.52.0', ['priority']) - @permittedKwargs(permitted_kwargs['benchmark']) - def func_benchmark(self, node, args, kwargs): - # is_parallel isn't valid here, so make sure it isn't passed - if 'is_parallel' in kwargs: - del kwargs['is_parallel'] - self.add_test(node, args, kwargs, False) - - @FeatureNewKwargs('test', '0.46.0', ['depends']) - @FeatureNewKwargs('test', '0.52.0', ['priority']) - @permittedKwargs(permitted_kwargs['test']) - def func_test(self, node, args, kwargs): - if kwargs.get('protocol') == 'gtest': - FeatureNew.single_use('"gtest" protocol for tests', '0.55.0', self.subproject) - self.add_test(node, args, kwargs, True) - - def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables: - envlist = kwargs.get('env', EnvironmentVariablesHolder()) - if isinstance(envlist, EnvironmentVariablesHolder): - env = envlist.held_object - elif isinstance(envlist, dict): - FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject) - env = EnvironmentVariablesHolder(envlist) - env = env.held_object - else: - # Convert from array to environment object - env = EnvironmentVariablesHolder(envlist) - env = env.held_object - return env - - def make_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any]): - if len(args) != 2: - raise InterpreterException('test expects 2 arguments, {} given'.format(len(args))) - name = args[0] - if not isinstance(name, str): - raise InterpreterException('First argument of test must be a string.') - if ':' in name: - mlog.deprecation(f'":" is not allowed in test name "{name}", it has been replaced with "_"', - location=node) - name = name.replace(':', '_') - exe = args[1] - if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)): - if isinstance(exe, mesonlib.File): - exe = self.func_find_program(node, args[1], {}) - else: - raise InterpreterException('Second argument must be executable.') - par = kwargs.get('is_parallel', True) - if not isinstance(par, bool): - raise InterpreterException('Keyword argument is_parallel must be a boolean.') - cmd_args = unholder(extract_as_list(kwargs, 'args')) - for i in cmd_args: - if not isinstance(i, (str, mesonlib.File, build.Target)): - raise InterpreterException('Command line arguments must be strings, files or targets.') - env = self.unpack_env_kwarg(kwargs) - should_fail = kwargs.get('should_fail', False) - if not isinstance(should_fail, bool): - raise InterpreterException('Keyword argument should_fail must be a boolean.') - timeout = kwargs.get('timeout', 30) - if not isinstance(timeout, int): - raise InterpreterException('Timeout must be an integer.') - if timeout <= 0: - FeatureNew('test() timeout <= 0', '0.57.0').use(self.subproject) - if 'workdir' in kwargs: - workdir = kwargs['workdir'] - if not isinstance(workdir, str): - raise InterpreterException('Workdir keyword argument must be a string.') - if not os.path.isabs(workdir): - raise InterpreterException('Workdir keyword argument must be an absolute path.') - else: - workdir = None - protocol = kwargs.get('protocol', 'exitcode') - if protocol not in {'exitcode', 'tap', 'gtest', 'rust'}: - raise InterpreterException('Protocol must be one of "exitcode", "tap", "gtest", or "rust".') - suite = [] - prj = self.subproject if self.is_subproject() else self.build.project_name - for s in mesonlib.stringlistify(kwargs.get('suite', '')): - if len(s) > 0: - s = ':' + s - suite.append(prj.replace(' ', '_').replace(':', '_') + s) - depends = unholder(extract_as_list(kwargs, 'depends')) - for dep in depends: - if not isinstance(dep, (build.CustomTarget, build.BuildTarget)): - raise InterpreterException('Depends items must be build targets.') - priority = kwargs.get('priority', 0) - if not isinstance(priority, int): - raise InterpreterException('Keyword argument priority must be an integer.') - return Test(name, prj, suite, exe.held_object, depends, par, cmd_args, - env, should_fail, timeout, workdir, protocol, priority) - - def add_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any], is_base_test: bool): - t = self.make_test(node, args, kwargs) - if is_base_test: - self.build.tests.append(t) - mlog.debug('Adding test', mlog.bold(t.name, True)) - else: - self.build.benchmarks.append(t) - mlog.debug('Adding benchmark', mlog.bold(t.name, True)) - - @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode']) - @permittedKwargs(permitted_kwargs['install_headers']) - def func_install_headers(self, node, args, kwargs): - source_files = self.source_strings_to_files(args) - install_mode = self._get_kwarg_install_mode(kwargs) - - install_subdir = kwargs.get('subdir', '') - if not isinstance(install_subdir, str): - raise InterpreterException('subdir keyword argument must be a string') - elif os.path.isabs(install_subdir): - mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.') - - install_dir = kwargs.get('install_dir', None) - if install_dir is not None and not isinstance(install_dir, str): - raise InterpreterException('install_dir keyword argument must be a string if provided') - - h = build.Headers(source_files, install_subdir, install_dir, install_mode, self.subproject) - self.build.headers.append(h) - - return HeadersHolder(h) - - @FeatureNewKwargs('install_man', '0.47.0', ['install_mode']) - @FeatureNewKwargs('install_man', '0.58.0', ['locale']) - @permittedKwargs(permitted_kwargs['install_man']) - def func_install_man(self, node, args, kwargs): - sources = self.source_strings_to_files(args) - for s in sources: - try: - num = int(s.split('.')[-1]) - except (IndexError, ValueError): - num = 0 - if num < 1 or num > 8: - raise InvalidArguments('Man file must have a file extension of a number between 1 and 8') - custom_install_mode = self._get_kwarg_install_mode(kwargs) - custom_install_dir = kwargs.get('install_dir', None) - locale = kwargs.get('locale') - if custom_install_dir is not None and not isinstance(custom_install_dir, str): - raise InterpreterException('install_dir must be a string.') - - m = build.Man(sources, custom_install_dir, custom_install_mode, self.subproject, locale) - self.build.man.append(m) - - return ManHolder(m) - - @FeatureNewKwargs('subdir', '0.44.0', ['if_found']) - @permittedKwargs(permitted_kwargs['subdir']) - def func_subdir(self, node, args, kwargs): - self.validate_arguments(args, 1, [str]) - mesonlib.check_direntry_issues(args) - if '..' in args[0]: - raise InvalidArguments('Subdir contains ..') - if self.subdir == '' and args[0] == self.subproject_dir: - raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.') - if self.subdir == '' and args[0].startswith('meson-'): - raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().') - for i in mesonlib.extract_as_list(kwargs, 'if_found'): - if not hasattr(i, 'found_method'): - raise InterpreterException('Object used in if_found does not have a found method.') - if not i.found_method([], {}): - return - prev_subdir = self.subdir - subdir = os.path.join(prev_subdir, args[0]) - if os.path.isabs(subdir): - raise InvalidArguments('Subdir argument must be a relative path.') - absdir = os.path.join(self.environment.get_source_dir(), subdir) - symlinkless_dir = os.path.realpath(absdir) - build_file = os.path.join(symlinkless_dir, 'meson.build') - if build_file in self.processed_buildfiles: - raise InvalidArguments('Tried to enter directory "%s", which has already been visited.' - % subdir) - self.processed_buildfiles.add(build_file) - self.subdir = subdir - os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) - buildfilename = os.path.join(self.subdir, environment.build_filename) - self.build_def_files.append(buildfilename) - absname = os.path.join(self.environment.get_source_dir(), buildfilename) - if not os.path.isfile(absname): - self.subdir = prev_subdir - raise InterpreterException(f"Non-existent build file '{buildfilename!s}'") - with open(absname, encoding='utf8') as f: - code = f.read() - assert(isinstance(code, str)) - try: - codeblock = mparser.Parser(code, absname).parse() - except mesonlib.MesonException as me: - me.file = absname - raise me - try: - self.evaluate_codeblock(codeblock) - except SubdirDoneRequest: - pass - self.subdir = prev_subdir - - def _get_kwarg_install_mode(self, kwargs: T.Dict[str, T.Any]) -> T.Optional[FileMode]: - if kwargs.get('install_mode', None) is None: - return None - install_mode: T.List[str] = [] - mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) - for m in mode: - # We skip any arguments that are set to `false` - if m is False: - m = None - install_mode.append(m) - if len(install_mode) > 3: - raise InvalidArguments('Keyword argument install_mode takes at ' - 'most 3 arguments.') - if len(install_mode) > 0 and install_mode[0] is not None and \ - not isinstance(install_mode[0], str): - raise InvalidArguments('Keyword argument install_mode requires the ' - 'permissions arg to be a string or false') - return FileMode(*install_mode) - - @FeatureNewKwargs('install_data', '0.46.0', ['rename']) - @FeatureNewKwargs('install_data', '0.38.0', ['install_mode']) - @permittedKwargs(permitted_kwargs['install_data']) - def func_install_data(self, node, args: T.List, kwargs: T.Dict[str, T.Any]): - kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) - raw_sources = args + kwsource - sources: T.List[mesonlib.File] = [] - source_strings: T.List[str] = [] - for s in raw_sources: - if isinstance(s, mesonlib.File): - sources.append(s) - elif isinstance(s, str): - source_strings.append(s) - else: - raise InvalidArguments('Argument must be string or file.') - sources += self.source_strings_to_files(source_strings) - install_dir: T.Optional[str] = kwargs.get('install_dir', None) - if install_dir is not None and not isinstance(install_dir, str): - raise InvalidArguments('Keyword argument install_dir not a string.') - install_mode = self._get_kwarg_install_mode(kwargs) - rename: T.Optional[T.List[str]] = kwargs.get('rename', None) - if rename is not None: - rename = mesonlib.stringlistify(rename) - if len(rename) != len(sources): - raise InvalidArguments( - '"rename" and "sources" argument lists must be the same length if "rename" is given. ' - f'Rename has {len(rename)} elements and sources has {len(sources)}.') - - data = DataHolder(build.Data(sources, install_dir, install_mode, self.subproject, rename)) - self.build.data.append(data.held_object) - return data - - @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories']) - @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode']) - @permittedKwargs(permitted_kwargs['install_subdir']) - @stringArgs - def func_install_subdir(self, node, args, kwargs): - if len(args) != 1: - raise InvalidArguments('Install_subdir requires exactly one argument.') - subdir: str = args[0] - if not isinstance(subdir, str): - raise InvalidArguments('install_subdir positional argument 1 must be a string.') - if 'install_dir' not in kwargs: - raise InvalidArguments('Missing keyword argument install_dir') - install_dir: str = kwargs['install_dir'] - if not isinstance(install_dir, str): - raise InvalidArguments('Keyword argument install_dir not a string.') - if 'strip_directory' in kwargs: - strip_directory: bool = kwargs['strip_directory'] - if not isinstance(strip_directory, bool): - raise InterpreterException('"strip_directory" keyword must be a boolean.') - else: - strip_directory = False - if 'exclude_files' in kwargs: - exclude: T.List[str] = extract_as_list(kwargs, 'exclude_files') - for f in exclude: - if not isinstance(f, str): - raise InvalidArguments('Exclude argument not a string.') - elif os.path.isabs(f): - raise InvalidArguments('Exclude argument cannot be absolute.') - exclude_files: T.Set[str] = set(exclude) - else: - exclude_files = set() - if 'exclude_directories' in kwargs: - exclude: T.List[str] = extract_as_list(kwargs, 'exclude_directories') - for d in exclude: - if not isinstance(d, str): - raise InvalidArguments('Exclude argument not a string.') - elif os.path.isabs(d): - raise InvalidArguments('Exclude argument cannot be absolute.') - exclude_directories: T.Set[str] = set(exclude) - else: - exclude_directories = set() - exclude = (exclude_files, exclude_directories) - install_mode = self._get_kwarg_install_mode(kwargs) - idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory, self.subproject) - self.build.install_dirs.append(idir) - return InstallDirHolder(idir) - - @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding']) - @FeatureNewKwargs('configure_file', '0.46.0', ['format']) - @FeatureNewKwargs('configure_file', '0.41.0', ['capture']) - @FeatureNewKwargs('configure_file', '0.50.0', ['install']) - @FeatureNewKwargs('configure_file', '0.52.0', ['depfile']) - @permittedKwargs(permitted_kwargs['configure_file']) - @noPosargs - def func_configure_file(self, node, args, kwargs): - if 'output' not in kwargs: - raise InterpreterException('Required keyword argument "output" not defined.') - actions = {'configuration', 'command', 'copy'}.intersection(kwargs.keys()) - if len(actions) == 0: - raise InterpreterException('Must specify an action with one of these ' - 'keyword arguments: \'configuration\', ' - '\'command\', or \'copy\'.') - elif len(actions) == 2: - raise InterpreterException('Must not specify both {!r} and {!r} ' - 'keyword arguments since they are ' - 'mutually exclusive.'.format(*actions)) - elif len(actions) == 3: - raise InterpreterException('Must specify one of {!r}, {!r}, and ' - '{!r} keyword arguments since they are ' - 'mutually exclusive.'.format(*actions)) - if 'capture' in kwargs: - if not isinstance(kwargs['capture'], bool): - raise InterpreterException('"capture" keyword must be a boolean.') - if 'command' not in kwargs: - raise InterpreterException('"capture" keyword requires "command" keyword.') - - if 'format' in kwargs: - fmt = kwargs['format'] - if not isinstance(fmt, str): - raise InterpreterException('"format" keyword must be a string.') - else: - fmt = 'meson' - - if fmt not in ('meson', 'cmake', 'cmake@'): - raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".') - - if 'output_format' in kwargs: - output_format = kwargs['output_format'] - if not isinstance(output_format, str): - raise InterpreterException('"output_format" keyword must be a string.') - else: - output_format = 'c' - - if output_format not in ('c', 'nasm'): - raise InterpreterException('"format" possible values are "c" or "nasm".') - - if 'depfile' in kwargs: - depfile = kwargs['depfile'] - if not isinstance(depfile, str): - raise InterpreterException('depfile file name must be a string') - else: - depfile = None - - # Validate input - inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input')) - inputs_abs = [] - for f in inputs: - if isinstance(f, mesonlib.File): - inputs_abs.append(f.absolute_path(self.environment.source_dir, - self.environment.build_dir)) - self.add_build_def_file(f) - else: - raise InterpreterException('Inputs can only be strings or file objects') - # Validate output - output = kwargs['output'] - if not isinstance(output, str): - raise InterpreterException('Output file name must be a string') - if inputs_abs: - values = mesonlib.get_filenames_templates_dict(inputs_abs, None) - outputs = mesonlib.substitute_values([output], values) - output = outputs[0] - if depfile: - depfile = mesonlib.substitute_values([depfile], values)[0] - ofile_rpath = os.path.join(self.subdir, output) - if ofile_rpath in self.configure_file_outputs: - mesonbuildfile = os.path.join(self.subdir, 'meson.build') - current_call = f"{mesonbuildfile}:{self.current_lineno}" - first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath]) - mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call) - else: - self.configure_file_outputs[ofile_rpath] = self.current_lineno - if os.path.dirname(output) != '': - raise InterpreterException('Output file name must not contain a subdirectory.') - (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) - ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname) - # Perform the appropriate action - if 'configuration' in kwargs: - conf = kwargs['configuration'] - if isinstance(conf, dict): - FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject) - conf = ConfigurationDataHolder(self.subproject, conf) - elif not isinstance(conf, ConfigurationDataHolder): - raise InterpreterException('Argument "configuration" is not of type configuration_data') - mlog.log('Configuring', mlog.bold(output), 'using configuration') - if len(inputs) > 1: - raise InterpreterException('At most one input file can given in configuration mode') - if inputs: - os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) - file_encoding = kwargs.setdefault('encoding', 'utf-8') - missing_variables, confdata_useless = \ - mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object, - fmt, file_encoding) - if missing_variables: - var_list = ", ".join(map(repr, sorted(missing_variables))) - mlog.warning( - "The variable(s) %s in the input file '%s' are not " - "present in the given configuration data." % ( - var_list, inputs[0]), location=node) - if confdata_useless: - ifbase = os.path.basename(inputs_abs[0]) - mlog.warning('Got an empty configuration_data() object and found no ' - 'substitutions in the input file {!r}. If you want to ' - 'copy a file to the build dir, use the \'copy:\' keyword ' - 'argument added in 0.47.0'.format(ifbase), location=node) - else: - mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format) - conf.mark_used() - elif 'command' in kwargs: - if len(inputs) > 1: - FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject) - # We use absolute paths for input and output here because the cwd - # that the command is run from is 'unspecified', so it could change. - # Currently it's builddir/subdir for in_builddir else srcdir/subdir. - values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs]) - if depfile: - depfile = os.path.join(self.environment.get_scratch_dir(), depfile) - values['@DEPFILE@'] = depfile - # Substitute @INPUT@, @OUTPUT@, etc here. - cmd = mesonlib.substitute_values(kwargs['command'], values) - mlog.log('Configuring', mlog.bold(output), 'with command') - res = self.run_command_impl(node, cmd, {}, True) - if res.returncode != 0: - raise InterpreterException('Running configure command failed.\n%s\n%s' % - (res.stdout, res.stderr)) - if 'capture' in kwargs and kwargs['capture']: - dst_tmp = ofile_abs + '~' - file_encoding = kwargs.setdefault('encoding', 'utf-8') - with open(dst_tmp, 'w', encoding=file_encoding) as f: - f.writelines(res.stdout) - if inputs_abs: - shutil.copymode(inputs_abs[0], dst_tmp) - mesonlib.replace_if_different(ofile_abs, dst_tmp) - if depfile: - mlog.log('Reading depfile:', mlog.bold(depfile)) - with open(depfile) as f: - df = DepFile(f.readlines()) - deps = df.get_all_dependencies(ofile_fname) - for dep in deps: - self.add_build_def_file(dep) - - elif 'copy' in kwargs: - if len(inputs_abs) != 1: - raise InterpreterException('Exactly one input file must be given in copy mode') - os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) - shutil.copy2(inputs_abs[0], ofile_abs) - else: - # Not reachable - raise AssertionError - # Install file if requested, we check for the empty string - # for backwards compatibility. That was the behaviour before - # 0.45.0 so preserve it. - idir = kwargs.get('install_dir', '') - if idir is False: - idir = '' - mlog.deprecation('Please use the new `install:` kwarg instead of passing ' - '`false` to `install_dir:`', location=node) - if not isinstance(idir, str): - if isinstance(idir, list) and len(idir) == 0: - mlog.deprecation('install_dir: kwarg must be a string and not an empty array. ' - 'Please use the install: kwarg to enable or disable installation. ' - 'This will be a hard error in the next release.') - else: - raise InterpreterException('"install_dir" must be a string') - install = kwargs.get('install', idir != '') - if not isinstance(install, bool): - raise InterpreterException('"install" must be a boolean') - if install: - if not idir: - raise InterpreterException('"install_dir" must be specified ' - 'when "install" in a configure_file ' - 'is true') - cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname) - install_mode = self._get_kwarg_install_mode(kwargs) - self.build.data.append(build.Data([cfile], idir, install_mode, self.subproject)) - return mesonlib.File.from_built_file(self.subdir, output) - - def extract_incdirs(self, kwargs): - prospectives = unholder(extract_as_list(kwargs, 'include_directories')) - result = [] - for p in prospectives: - if isinstance(p, build.IncludeDirs): - result.append(p) - elif isinstance(p, str): - result.append(self.build_incdir_object([p]).held_object) - else: - raise InterpreterException('Include directory objects can only be created from strings or include directories.') - return result - - @permittedKwargs(permitted_kwargs['include_directories']) - @stringArgs - def func_include_directories(self, node, args, kwargs): - return self.build_incdir_object(args, kwargs.get('is_system', False)) - - def build_incdir_object(self, incdir_strings, is_system=False): - if not isinstance(is_system, bool): - raise InvalidArguments('Is_system must be boolean.') - src_root = self.environment.get_source_dir() - build_root = self.environment.get_build_dir() - absbase_src = os.path.join(src_root, self.subdir) - absbase_build = os.path.join(build_root, self.subdir) - - for a in incdir_strings: - if a.startswith(src_root): - raise InvalidArguments('Tried to form an absolute path to a source dir. ' - 'You should not do that but use relative paths instead.' - ''' - -To get include path to any directory relative to the current dir do - -incdir = include_directories(dirname) - -After this incdir will contain both the current source dir as well as the -corresponding build dir. It can then be used in any subdirectory and -Meson will take care of all the busywork to make paths work. - -Dirname can even be '.' to mark the current directory. Though you should -remember that the current source and build directories are always -put in the include directories by default so you only need to do -include_directories('.') if you intend to use the result in a -different subdirectory. -''') - else: - try: - self.validate_within_subproject(self.subdir, a) - except InterpreterException: - mlog.warning('include_directories sandbox violation!') - print(f'''The project is trying to access the directory {a} which belongs to a different -subproject. This is a problem as it hardcodes the relative paths of these two projeccts. -This makes it impossible to compile the project in any other directory layout and also -prevents the subproject from changing its own directory layout. - -Instead of poking directly at the internals the subproject should be executed and -it should set a variable that the caller can then use. Something like: - -# In subproject -some_dep = declare_depencency(include_directories: include_directories('include')) - -# In parent project -some_dep = depencency('some') -executable(..., dependencies: [some_dep]) - -This warning will become a hard error in a future Meson release. -''') - absdir_src = os.path.join(absbase_src, a) - absdir_build = os.path.join(absbase_build, a) - if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build): - raise InvalidArguments('Include dir %s does not exist.' % a) - i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system)) - return i - - @permittedKwargs(permitted_kwargs['add_test_setup']) - @stringArgs - def func_add_test_setup(self, node, args, kwargs): - if len(args) != 1: - raise InterpreterException('Add_test_setup needs one argument for the setup name.') - setup_name = args[0] - if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None: - raise InterpreterException('Setup name may only contain alphanumeric characters.') - if ":" not in setup_name: - setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name - try: - inp = unholder(extract_as_list(kwargs, 'exe_wrapper')) - exe_wrapper = [] - for i in inp: - if isinstance(i, str): - exe_wrapper.append(i) - elif isinstance(i, ExternalProgram): - if not i.found(): - raise InterpreterException('Tried to use non-found executable.') - exe_wrapper += i.get_command() - else: - raise InterpreterException('Exe wrapper can only contain strings or external binaries.') - except KeyError: - exe_wrapper = None - gdb = kwargs.get('gdb', False) - if not isinstance(gdb, bool): - raise InterpreterException('Gdb option must be a boolean') - timeout_multiplier = kwargs.get('timeout_multiplier', 1) - if not isinstance(timeout_multiplier, int): - raise InterpreterException('Timeout multiplier must be a number.') - if timeout_multiplier <= 0: - FeatureNew('add_test_setup() timeout_multiplier <= 0', '0.57.0').use(self.subproject) - is_default = kwargs.get('is_default', False) - if not isinstance(is_default, bool): - raise InterpreterException('is_default option must be a boolean') - if is_default: - if self.build.test_setup_default_name is not None: - raise InterpreterException('\'%s\' is already set as default. ' - 'is_default can be set to true only once' % self.build.test_setup_default_name) - self.build.test_setup_default_name = setup_name - exclude_suites = mesonlib.stringlistify(kwargs.get('exclude_suites', [])) - env = self.unpack_env_kwarg(kwargs) - self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env, - exclude_suites) - - @permittedKwargs(permitted_kwargs['add_global_arguments']) - @stringArgs - def func_add_global_arguments(self, node, args, kwargs): - for_machine = self.machine_from_native_kwarg(kwargs) - self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs) - - @permittedKwargs(permitted_kwargs['add_global_link_arguments']) - @stringArgs - def func_add_global_link_arguments(self, node, args, kwargs): - for_machine = self.machine_from_native_kwarg(kwargs) - self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs) - - @permittedKwargs(permitted_kwargs['add_project_arguments']) - @stringArgs - def func_add_project_arguments(self, node, args, kwargs): - for_machine = self.machine_from_native_kwarg(kwargs) - self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs) - - @permittedKwargs(permitted_kwargs['add_project_link_arguments']) - @stringArgs - def func_add_project_link_arguments(self, node, args, kwargs): - for_machine = self.machine_from_native_kwarg(kwargs) - self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs) - - def warn_about_builtin_args(self, args): - # -Wpedantic is deliberately not included, since some people want to use it but not use -Wextra - # see e.g. - # https://github.com/mesonbuild/meson/issues/3275#issuecomment-641354956 - # https://github.com/mesonbuild/meson/issues/3742 - warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra') - optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os') - for arg in args: - if arg in warnargs: - mlog.warning(f'Consider using the built-in warning_level option instead of using "{arg}".', - location=self.current_node) - elif arg in optargs: - mlog.warning(f'Consider using the built-in optimization level instead of using "{arg}".', - location=self.current_node) - elif arg == '-Werror': - mlog.warning(f'Consider using the built-in werror option instead of using "{arg}".', - location=self.current_node) - elif arg == '-g': - mlog.warning(f'Consider using the built-in debug option instead of using "{arg}".', - location=self.current_node) - elif arg.startswith('-fsanitize'): - mlog.warning(f'Consider using the built-in option for sanitizers instead of using "{arg}".', - location=self.current_node) - elif arg.startswith('-std=') or arg.startswith('/std:'): - mlog.warning(f'Consider using the built-in option for language standard version instead of using "{arg}".', - location=self.current_node) - - def add_global_arguments(self, node, argsdict, args, kwargs): - if self.is_subproject(): - msg = 'Function \'{}\' cannot be used in subprojects because ' \ - 'there is no way to make that reliable.\nPlease only call ' \ - 'this if is_subproject() returns false. Alternatively, ' \ - 'define a variable that\ncontains your language-specific ' \ - 'arguments and add it to the appropriate *_args kwarg ' \ - 'in each target.'.format(node.func_name) - raise InvalidCode(msg) - frozen = self.project_args_frozen or self.global_args_frozen - self.add_arguments(node, argsdict, frozen, args, kwargs) - - def add_project_arguments(self, node, argsdict, args, kwargs): - if self.subproject not in argsdict: - argsdict[self.subproject] = {} - self.add_arguments(node, argsdict[self.subproject], - self.project_args_frozen, args, kwargs) - - def add_arguments(self, node, argsdict, args_frozen, args, kwargs): - if args_frozen: - msg = 'Tried to use \'{}\' after a build target has been declared.\n' \ - 'This is not permitted. Please declare all ' \ - 'arguments before your targets.'.format(node.func_name) - raise InvalidCode(msg) - - if 'language' not in kwargs: - raise InvalidCode(f'Missing language definition in {node.func_name}') - - self.warn_about_builtin_args(args) - - for lang in mesonlib.stringlistify(kwargs['language']): - lang = lang.lower() - argsdict[lang] = argsdict.get(lang, []) + args - - @noKwargs - @noArgsFlattening - def func_environment(self, node, args, kwargs): - if len(args) > 1: - raise InterpreterException('environment takes only one optional positional arguments') - elif len(args) == 1: - FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject) - initial_values = args[0] - if not isinstance(initial_values, dict) and not isinstance(initial_values, list): - raise InterpreterException('environment first argument must be a dictionary or a list') - else: - initial_values = {} - return EnvironmentVariablesHolder(initial_values, self.subproject) - - @stringArgs - @noKwargs - def func_join_paths(self, node, args, kwargs): - return self.join_path_strings(args) - - def run(self) -> None: - super().run() - mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets)))) - FeatureNew.report(self.subproject) - FeatureDeprecated.report(self.subproject) - if not self.is_subproject(): - self.print_extra_warnings() - if self.subproject == '': - self._print_summary() - - def print_extra_warnings(self) -> None: - # TODO cross compilation - for c in self.coredata.compilers.host.values(): - if c.get_id() == 'clang': - self.check_clang_asan_lundef() - break - - def check_clang_asan_lundef(self) -> None: - if OptionKey('b_lundef') not in self.coredata.options: - return - if OptionKey('b_sanitize') not in self.coredata.options: - return - if (self.coredata.options[OptionKey('b_lundef')].value and - self.coredata.options[OptionKey('b_sanitize')].value != 'none'): - mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef. -This will probably not work. -Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey('b_sanitize')].value), - location=self.current_node) - - # Check that the indicated file is within the same subproject - # as we currently are. This is to stop people doing - # nasty things like: - # - # f = files('../../master_src/file.c') - # - # Note that this is validated only when the file - # object is generated. The result can be used in a different - # subproject than it is defined in (due to e.g. a - # declare_dependency). - def validate_within_subproject(self, subdir, fname): - srcdir = Path(self.environment.source_dir) - norm = Path(srcdir, subdir, fname).resolve() - if os.path.isdir(norm): - inputtype = 'directory' - else: - inputtype = 'file' - if srcdir not in norm.parents: - # Grabbing files outside the source tree is ok. - # This is for vendor stuff like: - # - # /opt/vendorsdk/src/file_with_license_restrictions.c - return - project_root = Path(srcdir, self.root_subdir) - if norm == project_root: - return - if project_root not in norm.parents: - raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} outside current (sub)project.') - if project_root / self.subproject_dir in norm.parents: - raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.') - - def source_strings_to_files(self, sources: T.List[str]) -> T.List[mesonlib.File]: - mesonlib.check_direntry_issues(sources) - if not isinstance(sources, list): - sources = [sources] - results: T.List[mesonlib.File] = [] - for s in sources: - if isinstance(s, (mesonlib.File, GeneratedListHolder, - TargetHolder, CustomTargetIndexHolder, - GeneratedObjectsHolder)): - pass - elif isinstance(s, str): - self.validate_within_subproject(self.subdir, s) - s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s) - else: - raise InterpreterException('Source item is {!r} instead of ' - 'string or File-type object'.format(s)) - results.append(s) - return results - - def add_target(self, name, tobj): - if name == '': - raise InterpreterException('Target name must not be empty.') - if name.strip() == '': - raise InterpreterException('Target name must not consist only of whitespace.') - if name.startswith('meson-'): - raise InvalidArguments("Target names starting with 'meson-' are reserved " - "for Meson's internal use. Please rename.") - if name in coredata.FORBIDDEN_TARGET_NAMES: - raise InvalidArguments("Target name '%s' is reserved for Meson's " - "internal use. Please rename." % name) - # To permit an executable and a shared library to have the - # same name, such as "foo.exe" and "libfoo.a". - idname = tobj.get_id() - if idname in self.build.targets: - raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name) - self.build.targets[idname] = tobj - if idname not in self.coredata.target_guids: - self.coredata.target_guids[idname] = str(uuid.uuid4()).upper() - - @FeatureNew('both_libraries', '0.46.0') - def build_both_libraries(self, node, args, kwargs): - shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder) - - # Check if user forces non-PIC static library. - pic = True - key = OptionKey('b_staticpic') - if 'pic' in kwargs: - pic = kwargs['pic'] - elif key in self.environment.coredata.options: - pic = self.environment.coredata.options[key].value - - if pic: - # Exclude sources from args and kwargs to avoid building them twice - static_args = [args[0]] - static_kwargs = kwargs.copy() - static_kwargs['sources'] = [] - static_kwargs['objects'] = shared_holder.held_object.extract_all_objects() - else: - static_args = args - static_kwargs = kwargs - - static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder) - - return BothLibrariesHolder(shared_holder, static_holder, self) - - def build_library(self, node, args, kwargs): - default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject)) - if default_library == 'shared': - return self.build_target(node, args, kwargs, SharedLibraryHolder) - elif default_library == 'static': - return self.build_target(node, args, kwargs, StaticLibraryHolder) - elif default_library == 'both': - return self.build_both_libraries(node, args, kwargs) - else: - raise InterpreterException('Unknown default_library value: %s.', default_library) - - def build_target(self, node, args, kwargs, targetholder): - @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories']) - @FeatureNewKwargs('build target', '0.41.0', ['rust_args']) - @FeatureNewKwargs('build target', '0.40.0', ['build_by_default']) - @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility']) - def build_target_decorator_caller(self, node, args, kwargs): - return True - - build_target_decorator_caller(self, node, args, kwargs) - - if not args: - raise InterpreterException('Target does not have a name.') - name, *sources = args - for_machine = self.machine_from_native_kwarg(kwargs) - if 'sources' in kwargs: - sources += listify(kwargs['sources']) - sources = self.source_strings_to_files(sources) - objs = extract_as_list(kwargs, 'objects') - kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') - kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) - if 'extra_files' in kwargs: - ef = extract_as_list(kwargs, 'extra_files') - kwargs['extra_files'] = self.source_strings_to_files(ef) - self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) - if targetholder == ExecutableHolder: - targetclass = build.Executable - elif targetholder == SharedLibraryHolder: - targetclass = build.SharedLibrary - elif targetholder == SharedModuleHolder: - targetclass = build.SharedModule - elif targetholder == StaticLibraryHolder: - targetclass = build.StaticLibrary - elif targetholder == JarHolder: - targetclass = build.Jar - else: - mlog.debug('Unknown target type:', str(targetholder)) - raise RuntimeError('Unreachable code') - self.kwarg_strings_to_includedirs(kwargs) - - # Filter out kwargs from other target types. For example 'soversion' - # passed to library() when default_library == 'static'. - kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs} - - kwargs['include_directories'] = self.extract_incdirs(kwargs) - target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs) - target.project_version = self.project_version - - self.add_stdlib_info(target) - l = targetholder(target, self) - self.add_target(name, l.held_object) - self.project_args_frozen = True - return l - - def kwarg_strings_to_includedirs(self, kwargs): - if 'd_import_dirs' in kwargs: - items = mesonlib.extract_as_list(kwargs, 'd_import_dirs') - cleaned_items = [] - for i in items: - if isinstance(i, str): - # BW compatibility. This was permitted so we must support it - # for a few releases so people can transition to "correct" - # path declarations. - if os.path.normpath(i).startswith(self.environment.get_source_dir()): - mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead. -This will become a hard error in the future.''', location=self.current_node) - i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir)) - i = self.build_incdir_object([i]) - cleaned_items.append(i) - kwargs['d_import_dirs'] = cleaned_items - - def get_used_languages(self, target): - result = set() - for i in target.sources: - for lang, c in self.coredata.compilers[target.for_machine].items(): - if c.can_compile(i): - result.add(lang) - break - return result - - def add_stdlib_info(self, target): - for l in self.get_used_languages(target): - dep = self.build.stdlibs[target.for_machine].get(l, None) - if dep: - target.add_deps(dep) - - def check_sources_exist(self, subdir, sources): - for s in sources: - if not isinstance(s, str): - continue # This means a generated source and they always exist. - fname = os.path.join(subdir, s) - if not os.path.isfile(fname): - raise InterpreterException('Tried to add non-existing source file %s.' % s) - - # Only permit object extraction from the same subproject - def validate_extraction(self, buildtarget: InterpreterObject) -> None: - if self.subproject != buildtarget.subproject: - raise InterpreterException('Tried to extract objects from a different subproject.') - - def is_subproject(self): - return self.subproject != '' - - @noKwargs - @noArgsFlattening - def func_set_variable(self, node, args, kwargs): - if len(args) != 2: - raise InvalidCode('Set_variable takes two arguments.') - varname, value = args - self.set_variable(varname, value) - - @noKwargs - @noArgsFlattening - def func_get_variable(self, node, args, kwargs): - if len(args) < 1 or len(args) > 2: - raise InvalidCode('Get_variable takes one or two arguments.') - varname = args[0] - if isinstance(varname, Disabler): - return varname - if not isinstance(varname, str): - raise InterpreterException('First argument must be a string.') - try: - return self.variables[varname] - except KeyError: - pass - if len(args) == 2: - return args[1] - raise InterpreterException('Tried to get unknown variable "%s".' % varname) - - @stringArgs - @noKwargs - def func_is_variable(self, node, args, kwargs): - if len(args) != 1: - raise InvalidCode('Is_variable takes two arguments.') - varname = args[0] - return varname in self.variables - - @staticmethod - def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice: - native = kwargs.get('native', False) - if not isinstance(native, bool): - raise InvalidArguments('Argument to "native" must be a boolean.') - return MachineChoice.BUILD if native else MachineChoice.HOST - - @FeatureNew('is_disabler', '0.52.0') - @noKwargs - def func_is_disabler(self, node, args, kwargs): - if len(args) != 1: - raise InvalidCode('Is_disabler takes one argument.') - varname = args[0] - return isinstance(varname, Disabler) - - @noKwargs - @FeatureNew('range', '0.58.0') - @typed_pos_args('range', int, optargs=[int, int]) - def func_range(self, node, args: T.Tuple[int, T.Optional[int], T.Optional[int]], kwargs: T.Dict[str, T.Any]) -> RangeHolder: - start, stop, step = args - # Just like Python's range, we allow range(stop), range(start, stop), or - # range(start, stop, step) - if stop is None: - stop = start - start = 0 - if step is None: - step = 1 - # This is more strict than Python's range() - if start < 0: - raise InterpreterException('start cannot be negative') - if stop < start: - raise InterpreterException('stop cannot be less than start') - if step < 1: - raise InterpreterException('step must be >=1') - return RangeHolder(start, stop, step) |