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 | |
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')
-rw-r--r-- | mesonbuild/interpreter/__init__.py | 25 | ||||
-rw-r--r-- | mesonbuild/interpreter/compiler.py | 766 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py (renamed from mesonbuild/interpreter.py) | 2167 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 999 | ||||
-rw-r--r-- | mesonbuild/interpreter/mesonmain.py | 369 | ||||
-rw-r--r-- | mesonbuild/modules/cmake.py | 3 | ||||
-rw-r--r-- | mesonbuild/modules/keyval.py | 3 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_rust.py | 4 |
8 files changed, 2209 insertions, 2127 deletions
diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py new file mode 100644 index 0000000..e571971 --- /dev/null +++ b/mesonbuild/interpreter/__init__.py @@ -0,0 +1,25 @@ +# SPDX-license-identifier: Apache-2.0 +# Copyright 2012-2021 The Meson development team +# Copyright © 2021 Intel Corporation + +# 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. + +"""Meson interpreter.""" + +from .interpreter import Interpreter, permitted_kwargs +from .compiler import CompilerHolder +from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, + CustomTargetIndexHolder, MachineHolder, Test, + ConfigurationDataHolder, SubprojectHolder, DependencyHolder, + GeneratedListHolder, ExternalProgramHolder, + extract_required_kwarg) diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py new file mode 100644 index 0000000..3a3ce34 --- /dev/null +++ b/mesonbuild/interpreter/compiler.py @@ -0,0 +1,766 @@ +import functools + +from .interpreterobjects import (IncludeDirsHolder, ExternalLibraryHolder, + extract_required_kwarg, extract_search_dirs) + +from .. import mesonlib +from .. import mlog +from .. import dependencies +from ..interpreterbase import (InterpreterObject, noPosargs, noKwargs, permittedKwargs, + FeatureNew, FeatureNewKwargs, disablerIfNotFound, + check_stringlist, InterpreterException, InvalidArguments, + InvalidCode) + +import typing as T + +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 + +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 = mesonlib.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 mesonlib.unholder(mesonlib.listify(deps)): + if not isinstance(d, dependencies.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() diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter/interpreter.py index 2b7a36a..2056379 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -11,54 +11,58 @@ # 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 +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, listify, extract_as_list, has_path_sep, unholder +from ..programs import ExternalProgram, NonExistingExternalProgram +from ..dependencies import Dependency, NotFoundDependency, DependencyException +from ..depfile import DepFile +from ..interpreterbase import InterpreterBase, typed_pos_args +from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening +from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest +from ..interpreterbase import InterpreterObject, Disabler, disablerIfNotFound +from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs +from ..interpreterbase import ObjectHolder, RangeHolder +from ..modules import ModuleObject +from ..cmake import CMakeInterpreter +from ..backend.backends import Backend, ExecutableSerialisation + +from .mesonmain import MesonMain +from .compiler import CompilerHolder +from .interpreterobjects import (SubprojectHolder, MachineHolder, EnvironmentVariablesHolder, + FeatureOptionHolder, ExternalProgramHolder, CustomTargetHolder, + RunTargetHolder, IncludeDirsHolder, ConfigurationDataHolder, + DependencyHolder, ModuleObjectHolder, GeneratedListHolder, + TargetHolder, CustomTargetIndexHolder, GeneratedObjectsHolder, + StaticLibraryHolder, ExecutableHolder, SharedLibraryHolder, + SharedModuleHolder, HeadersHolder, BothLibrariesHolder, + BuildTargetHolder, DataHolder, JarHolder, Test, RunProcess, + ManHolder, GeneratorHolder, InstallDirHolder, extract_required_kwarg, + extract_search_dirs) + +from pathlib import Path 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'}, -} + from ..compilers import Compiler + from ..envconfig import MachineInfo + from ..environment import Environment def stringifyUserArguments(args, quote=False): if isinstance(args, list): @@ -71,1736 +75,6 @@ def stringifyUserArguments(args, quote=False): 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 @@ -1871,357 +145,6 @@ class Summary: 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 @@ -2721,7 +644,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Assert value not bool.') if not value: if message is None: - from .ast import AstPrinter + from ..ast import AstPrinter printer = AstPrinter() node.args.arguments[0].accept(printer) message = printer.result @@ -2953,7 +876,7 @@ external dependencies (including libraries) must go to "dependencies".''') new_build = self.build.copy() prefix = self.coredata.options[OptionKey('prefix')].value - from .modules.cmake import CMakeSubprojectOptions + from ..modules.cmake import CMakeSubprojectOptions options = kwargs.get('options', CMakeSubprojectOptions()) if not isinstance(options, CMakeSubprojectOptions): raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions' @@ -2973,7 +896,7 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log('Processing generated meson AST') # Debug print the generated meson file - from .ast import AstIndentationGenerator, AstPrinter + from ..ast import AstIndentationGenerator, AstPrinter printer = AstPrinter() ast.accept(AstIndentationGenerator()) ast.accept(printer) @@ -3061,7 +984,7 @@ external dependencies (including libraries) must go to "dependencies".''') if self.backend is not None: return backend = self.coredata.get_option(OptionKey('backend')) - from .backend import backends + from ..backend import backends self.backend = backends.get_backend_from_name(backend, self.build, self) if self.backend is None: diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py new file mode 100644 index 0000000..7d43752 --- /dev/null +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -0,0 +1,999 @@ +import os +import shlex +import subprocess +import re + +from pathlib import Path, PurePath + +from .. import mesonlib +from .. import coredata +from .. import build +from .. import mlog + +from ..modules import ModuleReturnValue, ModuleObject, ModuleState +from ..backend.backends import TestProtocol +from ..interpreterbase import (InterpreterObject, ObjectHolder, MutableInterpreterObject, + FeatureNewKwargs, FeatureNew, FeatureDeprecated, + typed_pos_args, stringArgs, permittedKwargs, + noArgsFlattening, noPosargs, TYPE_var, TYPE_nkwargs, + flatten, InterpreterException, InvalidArguments, InvalidCode) +from ..dependencies import Dependency, ExternalLibrary, InternalDependency +from ..programs import ExternalProgram +from ..mesonlib import FileMode, OptionKey, listify, Popen_safe + +import typing as T + +def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True): + val = kwargs.get('required', default) + disabled = False + required = False + feature = None + if isinstance(val, FeatureOptionHolder): + if not feature_check: + feature_check = FeatureNew('User option "feature"', '0.47.0') + feature_check.use(subproject) + option = val.held_object + feature = val.name + if option.is_disabled(): + disabled = True + elif option.is_enabled(): + required = True + elif isinstance(val, bool): + required = val + else: + raise InterpreterException('required keyword argument must be boolean or a feature option') + + # Keep boolean value in kwargs to simplify other places where this kwarg is + # checked. + kwargs['required'] = required + + return disabled, required, feature + +def extract_search_dirs(kwargs): + search_dirs = mesonlib.stringlistify(kwargs.get('dirs', [])) + search_dirs = [Path(d).expanduser() for d in search_dirs] + for d in search_dirs: + if mesonlib.is_windows() and d.root.startswith('\\'): + # a Unix-path starting with `/` that is not absolute on Windows. + # discard without failing for end-user ease of cross-platform directory arrays + continue + if not d.is_absolute(): + raise InvalidCode(f'Search directory {d} is not an absolute path.') + return list(map(str, search_dirs)) + +class FeatureOptionHolder(InterpreterObject, ObjectHolder[coredata.UserFeatureOption]): + def __init__(self, env: 'Environment', name: str, option: coredata.UserFeatureOption): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, option) + if option.is_auto(): + # TODO: we need to case here because options is not a TypedDict + self.held_object = T.cast(coredata.UserFeatureOption, env.coredata.options[OptionKey('auto_features')]) + self.name = name + self.methods.update({'enabled': self.enabled_method, + 'disabled': self.disabled_method, + 'auto': self.auto_method, + }) + + @noPosargs + @permittedKwargs({}) + def enabled_method(self, args, kwargs): + return self.held_object.is_enabled() + + @noPosargs + @permittedKwargs({}) + def disabled_method(self, args, kwargs): + return self.held_object.is_disabled() + + @noPosargs + @permittedKwargs({}) + def auto_method(self, args, kwargs): + return self.held_object.is_auto() + +class RunProcess(InterpreterObject): + + def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True): + super().__init__() + if not isinstance(cmd, ExternalProgram): + raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') + self.capture = capture + pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) + self.returncode = pc.returncode + self.methods.update({'returncode': self.returncode_method, + 'stdout': self.stdout_method, + 'stderr': self.stderr_method, + }) + + def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False): + command_array = cmd.get_command() + args + menv = {'MESON_SOURCE_ROOT': source_dir, + 'MESON_BUILD_ROOT': build_dir, + 'MESON_SUBDIR': subdir, + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]), + } + if in_builddir: + cwd = os.path.join(build_dir, subdir) + else: + cwd = os.path.join(source_dir, subdir) + child_env = os.environ.copy() + child_env.update(menv) + child_env = env.get_env(child_env) + stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL + mlog.debug('Running command:', ' '.join(command_array)) + try: + p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd) + if self.capture: + mlog.debug('--- stdout ---') + mlog.debug(o) + else: + o = '' + mlog.debug('--- stdout disabled ---') + mlog.debug('--- stderr ---') + mlog.debug(e) + mlog.debug('') + + if check and p.returncode != 0: + raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode)) + + return p, o, e + except FileNotFoundError: + raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) + + @noPosargs + @permittedKwargs({}) + def returncode_method(self, args, kwargs): + return self.returncode + + @noPosargs + @permittedKwargs({}) + def stdout_method(self, args, kwargs): + return self.stdout + + @noPosargs + @permittedKwargs({}) + def stderr_method(self, args, kwargs): + return self.stderr + +class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.EnvironmentVariables]): + def __init__(self, initial_values=None, subproject: str = ''): + MutableInterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.EnvironmentVariables(), subproject) + self.methods.update({'set': self.set_method, + 'append': self.append_method, + 'prepend': self.prepend_method, + }) + if isinstance(initial_values, dict): + for k, v in initial_values.items(): + self.set_method([k, v], {}) + elif initial_values is not None: + for e in mesonlib.stringlistify(initial_values): + if '=' not in e: + raise InterpreterException('Env var definition must be of type key=val.') + (k, val) = e.split('=', 1) + k = k.strip() + val = val.strip() + if ' ' in k: + raise InterpreterException('Env var key must not have spaces in it.') + self.set_method([k, val], {}) + + def __repr__(self) -> str: + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.held_object.envvars) + + def unpack_separator(self, kwargs: T.Dict[str, T.Any]) -> str: + separator = kwargs.get('separator', os.pathsep) + if not isinstance(separator, str): + raise InterpreterException("EnvironmentVariablesHolder methods 'separator'" + " argument needs to be a string.") + return separator + + def warn_if_has_name(self, name: str) -> None: + # Multiple append/prepend operations was not supported until 0.58.0. + if self.held_object.has_name(name): + m = f'Overriding previous value of environment variable {name!r} with a new one' + FeatureNew('0.58.0', m).use(self.subproject) + + @stringArgs + @permittedKwargs({'separator'}) + @typed_pos_args('environment.set', str, varargs=str, min_varargs=1) + def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: + name, values = args + separator = self.unpack_separator(kwargs) + self.held_object.set(name, values, separator) + + @stringArgs + @permittedKwargs({'separator'}) + @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) + def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: + name, values = args + separator = self.unpack_separator(kwargs) + self.warn_if_has_name(name) + self.held_object.append(name, values, separator) + + @stringArgs + @permittedKwargs({'separator'}) + @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) + def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: + name, values = args + separator = self.unpack_separator(kwargs) + self.warn_if_has_name(name) + self.held_object.prepend(name, values, separator) + + +class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder[build.ConfigurationData]): + def __init__(self, pv, initial_values=None): + MutableInterpreterObject.__init__(self) + self.used = False # These objects become immutable after use in configure_file. + ObjectHolder.__init__(self, build.ConfigurationData(), pv) + self.methods.update({'set': self.set_method, + 'set10': self.set10_method, + 'set_quoted': self.set_quoted_method, + 'has': self.has_method, + 'get': self.get_method, + 'keys': self.keys_method, + 'get_unquoted': self.get_unquoted_method, + 'merge_from': self.merge_from_method, + }) + if isinstance(initial_values, dict): + for k, v in initial_values.items(): + self.set_method([k, v], {}) + elif initial_values: + raise AssertionError('Unsupported ConfigurationDataHolder initial_values') + + def is_used(self): + return self.used + + def mark_used(self): + self.used = True + + def validate_args(self, args, kwargs): + if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: + mlog.deprecation('Passing a list as the single argument to ' + 'configuration_data.set is deprecated. This will ' + 'become a hard error in the future.', + location=self.current_node) + args = args[0] + + if len(args) != 2: + raise InterpreterException("Configuration set requires 2 arguments.") + if self.used: + raise InterpreterException("Can not set values on configuration object that has been used.") + name, val = args + if not isinstance(val, (int, str)): + msg = 'Setting a configuration data value to {!r} is invalid, ' \ + 'and will fail at configure_file(). If you are using it ' \ + 'just to store some values, please use a dict instead.' + mlog.deprecation(msg.format(val), location=self.current_node) + desc = kwargs.get('description', None) + if not isinstance(name, str): + raise InterpreterException("First argument to set must be a string.") + if desc is not None and not isinstance(desc, str): + raise InterpreterException('Description must be a string.') + + return name, val, desc + + @noArgsFlattening + def set_method(self, args, kwargs): + (name, val, desc) = self.validate_args(args, kwargs) + self.held_object.values[name] = (val, desc) + + def set_quoted_method(self, args, kwargs): + (name, val, desc) = self.validate_args(args, kwargs) + if not isinstance(val, str): + raise InterpreterException("Second argument to set_quoted must be a string.") + escaped_val = '\\"'.join(val.split('"')) + self.held_object.values[name] = ('"' + escaped_val + '"', desc) + + def set10_method(self, args, kwargs): + (name, val, desc) = self.validate_args(args, kwargs) + if val: + self.held_object.values[name] = (1, desc) + else: + self.held_object.values[name] = (0, desc) + + def has_method(self, args, kwargs): + return args[0] in self.held_object.values + + @FeatureNew('configuration_data.get()', '0.38.0') + @noArgsFlattening + def get_method(self, args, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get method takes one or two arguments.') + name = args[0] + if name in self.held_object: + return self.held_object.get(name)[0] + if len(args) > 1: + return args[1] + raise InterpreterException('Entry %s not in configuration data.' % name) + + @FeatureNew('configuration_data.get_unquoted()', '0.44.0') + def get_unquoted_method(self, args, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get method takes one or two arguments.') + name = args[0] + if name in self.held_object: + val = self.held_object.get(name)[0] + elif len(args) > 1: + val = args[1] + else: + raise InterpreterException('Entry %s not in configuration data.' % name) + if val[0] == '"' and val[-1] == '"': + return val[1:-1] + return val + + def get(self, name): + return self.held_object.values[name] # (val, desc) + + @FeatureNew('configuration_data.keys()', '0.57.0') + @noPosargs + def keys_method(self, args, kwargs): + return sorted(self.keys()) + + def keys(self): + return self.held_object.values.keys() + + def merge_from_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Merge_from takes one positional argument.') + from_object = args[0] + if not isinstance(from_object, ConfigurationDataHolder): + raise InterpreterException('Merge_from argument must be a configuration data object.') + from_object = from_object.held_object + for k, v in from_object.values.items(): + self.held_object.values[k] = v + +permitted_partial_dependency_kwargs = { + 'compile_args', 'link_args', 'links', 'includes', 'sources' +} + +class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): + def __init__(self, dep: Dependency, pv: str): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, dep, pv) + self.methods.update({'found': self.found_method, + 'type_name': self.type_name_method, + 'version': self.version_method, + 'name': self.name_method, + 'get_pkgconfig_variable': self.pkgconfig_method, + 'get_configtool_variable': self.configtool_method, + 'get_variable': self.variable_method, + 'partial_dependency': self.partial_dependency_method, + 'include_type': self.include_type_method, + 'as_system': self.as_system_method, + 'as_link_whole': self.as_link_whole_method, + }) + + def found(self): + return self.found_method([], {}) + + @noPosargs + @permittedKwargs({}) + def type_name_method(self, args, kwargs): + return self.held_object.type_name + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + if self.held_object.type_name == 'internal': + return True + return self.held_object.found() + + @noPosargs + @permittedKwargs({}) + def version_method(self, args, kwargs): + return self.held_object.get_version() + + @noPosargs + @permittedKwargs({}) + def name_method(self, args, kwargs): + return self.held_object.get_name() + + @FeatureDeprecated('Dependency.get_pkgconfig_variable', '0.56.0', + 'use Dependency.get_variable(pkgconfig : ...) instead') + @permittedKwargs({'define_variable', 'default'}) + def pkgconfig_method(self, args, kwargs): + args = listify(args) + if len(args) != 1: + raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('Variable name must be a string.') + return self.held_object.get_pkgconfig_variable(varname, kwargs) + + @FeatureNew('dep.get_configtool_variable', '0.44.0') + @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0', + 'use Dependency.get_variable(configtool : ...) instead') + @permittedKwargs({}) + def configtool_method(self, args, kwargs): + args = listify(args) + if len(args) != 1: + raise InterpreterException('get_configtool_variable takes exactly one argument.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('Variable name must be a string.') + return self.held_object.get_configtool_variable(varname) + + @FeatureNew('dep.partial_dependency', '0.46.0') + @noPosargs + @permittedKwargs(permitted_partial_dependency_kwargs) + def partial_dependency_method(self, args, kwargs): + pdep = self.held_object.get_partial_dependency(**kwargs) + return DependencyHolder(pdep, self.subproject) + + @FeatureNew('dep.get_variable', '0.51.0') + @typed_pos_args('dep.get_variable', optargs=[str]) + @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'}) + @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal']) + def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> str: + default_varname = args[0] + if default_varname is not None: + FeatureNew('0.58.0', 'Positional argument to dep.get_variable()').use(self.subproject) + for k in ['cmake', 'pkgconfig', 'configtool', 'internal']: + kwargs.setdefault(k, default_varname) + return self.held_object.get_variable(**kwargs) + + @FeatureNew('dep.include_type', '0.52.0') + @noPosargs + @permittedKwargs({}) + def include_type_method(self, args, kwargs): + return self.held_object.get_include_type() + + @FeatureNew('dep.as_system', '0.52.0') + @permittedKwargs({}) + def as_system_method(self, args, kwargs): + args = listify(args) + new_is_system = 'system' + if len(args) > 1: + raise InterpreterException('as_system takes only one optional value') + if len(args) == 1: + new_is_system = args[0] + new_dep = self.held_object.generate_system_dependency(new_is_system) + return DependencyHolder(new_dep, self.subproject) + + @FeatureNew('dep.as_link_whole', '0.56.0') + @permittedKwargs({}) + @noPosargs + def as_link_whole_method(self, args, kwargs): + if not isinstance(self.held_object, InternalDependency): + raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects') + new_dep = self.held_object.generate_link_whole_dependency() + return DependencyHolder(new_dep, self.subproject) + +class ExternalProgramHolder(InterpreterObject, ObjectHolder[ExternalProgram]): + def __init__(self, ep: ExternalProgram, subproject: str, backend=None): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, ep) + self.subproject = subproject + self.backend = backend + self.methods.update({'found': self.found_method, + 'path': self.path_method, + 'full_path': self.full_path_method}) + self.cached_version = None + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + @noPosargs + @permittedKwargs({}) + @FeatureDeprecated('ExternalProgram.path', '0.55.0', + 'use ExternalProgram.full_path() instead') + def path_method(self, args, kwargs): + return self._full_path() + + @noPosargs + @permittedKwargs({}) + @FeatureNew('ExternalProgram.full_path', '0.55.0') + def full_path_method(self, args, kwargs): + return self._full_path() + + def _full_path(self): + exe = self.held_object + if isinstance(exe, build.Executable): + return self.backend.get_target_filename_abs(exe) + return exe.get_path() + + def found(self): + return isinstance(self.held_object, build.Executable) or self.held_object.found() + + def get_command(self): + return self.held_object.get_command() + + def get_name(self): + exe = self.held_object + if isinstance(exe, build.Executable): + return exe.name + return exe.get_name() + + def get_version(self, interpreter): + if isinstance(self.held_object, build.Executable): + return self.held_object.project_version + if not self.cached_version: + raw_cmd = self.get_command() + ['--version'] + cmd = [self, '--version'] + res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) + if res.returncode != 0: + m = 'Running {!r} failed' + raise InterpreterException(m.format(raw_cmd)) + output = res.stdout.strip() + if not output: + output = res.stderr.strip() + match = re.search(r'([0-9][0-9\.]+)', output) + if not match: + m = 'Could not find a version number in output of {!r}' + raise InterpreterException(m.format(raw_cmd)) + self.cached_version = match.group(1) + return self.cached_version + +class ExternalLibraryHolder(InterpreterObject, ObjectHolder[ExternalLibrary]): + def __init__(self, el: ExternalLibrary, pv: str): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, el, pv) + self.methods.update({'found': self.found_method, + 'type_name': self.type_name_method, + 'partial_dependency': self.partial_dependency_method, + }) + + def found(self): + return self.held_object.found() + + @noPosargs + @permittedKwargs({}) + def type_name_method(self, args, kwargs): + return self.held_object.type_name + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + def get_name(self): + return self.held_object.name + + def get_compile_args(self): + return self.held_object.get_compile_args() + + def get_link_args(self): + return self.held_object.get_link_args() + + def get_exe_args(self): + return self.held_object.get_exe_args() + + @FeatureNew('dep.partial_dependency', '0.46.0') + @noPosargs + @permittedKwargs(permitted_partial_dependency_kwargs) + def partial_dependency_method(self, args, kwargs): + pdep = self.held_object.get_partial_dependency(**kwargs) + return DependencyHolder(pdep, self.subproject) + +class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): + @FeatureNewKwargs('generator', '0.43.0', ['capture']) + def __init__(self, interp, args, kwargs): + self.interpreter = interp + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject) + self.methods.update({'process': self.process_method}) + + @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from']) + @permittedKwargs({'extra_args', 'preserve_path_from'}) + def process_method(self, args, kwargs): + extras = mesonlib.stringlistify(kwargs.get('extra_args', [])) + if 'preserve_path_from' in kwargs: + preserve_path_from = kwargs['preserve_path_from'] + if not isinstance(preserve_path_from, str): + raise InvalidArguments('Preserve_path_from must be a string.') + preserve_path_from = os.path.normpath(preserve_path_from) + if not os.path.isabs(preserve_path_from): + # This is a bit of a hack. Fix properly before merging. + raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') + else: + preserve_path_from = None + gl = self.held_object.process_files('Generator', args, self.interpreter, + preserve_path_from, extra_args=extras) + return GeneratedListHolder(gl) + + +class GeneratedListHolder(InterpreterObject, ObjectHolder[build.GeneratedList]): + def __init__(self, arg1, extra_args=None): + InterpreterObject.__init__(self) + if isinstance(arg1, GeneratorHolder): + ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else [])) + else: + ObjectHolder.__init__(self, arg1) + + def __repr__(self): + r = '<{}: {!r}>' + return r.format(self.__class__.__name__, self.held_object.get_outputs()) + + def add_file(self, a): + self.held_object.add_file(a) + +# A machine that's statically known from the cross file +class MachineHolder(InterpreterObject, ObjectHolder['MachineInfo']): + def __init__(self, machine_info: 'MachineInfo'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, machine_info) + self.methods.update({'system': self.system_method, + 'cpu': self.cpu_method, + 'cpu_family': self.cpu_family_method, + 'endian': self.endian_method, + }) + + @noPosargs + @permittedKwargs({}) + def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.cpu_family + + @noPosargs + @permittedKwargs({}) + def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.cpu + + @noPosargs + @permittedKwargs({}) + def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.system + + @noPosargs + @permittedKwargs({}) + def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + return self.held_object.endian + +class IncludeDirsHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): + def __init__(self, idobj: build.IncludeDirs): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, idobj) + +class HeadersHolder(InterpreterObject, ObjectHolder[build.Headers]): + + def __init__(self, obj: build.Headers): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + + def set_install_subdir(self, subdir): + self.held_object.install_subdir = subdir + + def get_install_subdir(self): + return self.held_object.install_subdir + + def get_sources(self): + return self.held_object.sources + + def get_custom_install_dir(self): + return self.held_object.custom_install_dir + + def get_custom_install_mode(self): + return self.held_object.custom_install_mode + +class DataHolder(InterpreterObject, ObjectHolder[build.Data]): + def __init__(self, data: build.Data): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, data) + + def get_source_subdir(self): + return self.held_object.source_subdir + + def get_sources(self): + return self.held_object.sources + + def get_install_dir(self): + return self.held_object.install_dir + +class InstallDirHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): + + def __init__(self, obj: build.InstallDir): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + +class ManHolder(InterpreterObject, ObjectHolder[build.Man]): + + def __init__(self, obj: build.Man): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, obj) + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.held_object.custom_install_dir + + def get_custom_install_mode(self) -> T.Optional[FileMode]: + return self.held_object.custom_install_mode + + def locale(self) -> T.Optional[str]: + return self.held_object.locale + + def get_sources(self) -> T.List[mesonlib.File]: + return self.held_object.sources + +class GeneratedObjectsHolder(InterpreterObject, ObjectHolder[build.ExtractedObjects]): + def __init__(self, held_object: build.ExtractedObjects): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, held_object) + +class Test(InterpreterObject): + def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable, + depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], + is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables, + should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, + priority: int): + InterpreterObject.__init__(self) + self.name = name + self.suite = suite + self.project_name = project + self.exe = exe + self.depends = depends + self.is_parallel = is_parallel + self.cmd_args = cmd_args + self.env = env + self.should_fail = should_fail + self.timeout = timeout + self.workdir = workdir + self.protocol = TestProtocol.from_str(protocol) + self.priority = priority + + def get_exe(self): + return self.exe + + def get_name(self): + return self.name + +class SubprojectHolder(InterpreterObject, ObjectHolder[T.Optional['Interpreter']]): + + def __init__(self, subinterpreter: T.Optional['Interpreter'], subdir: str, warnings=0, disabled_feature=None, + exception=None): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, subinterpreter) + self.warnings = warnings + self.disabled_feature = disabled_feature + self.exception = exception + self.subdir = PurePath(subdir).as_posix() + self.methods.update({'get_variable': self.get_variable_method, + 'found': self.found_method, + }) + + @noPosargs + @permittedKwargs({}) + def found_method(self, args, kwargs): + return self.found() + + def found(self): + return self.held_object is not None + + @permittedKwargs({}) + @noArgsFlattening + def get_variable_method(self, args, kwargs): + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Get_variable takes one or two arguments.') + if not self.found(): + raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir)) + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('Get_variable first argument must be a string.') + try: + return self.held_object.variables[varname] + except KeyError: + pass + + if len(args) == 2: + return args[1] + + raise InvalidArguments(f'Requested variable "{varname}" not found.') + +class ModuleObjectHolder(InterpreterObject, ObjectHolder['ModuleObject']): + def __init__(self, modobj: 'ModuleObject', interpreter: 'Interpreter'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, modobj) + self.interpreter = interpreter + + def method_call(self, method_name, args, kwargs): + modobj = self.held_object + method = modobj.methods.get(method_name) + if not method and not modobj.methods: + # FIXME: Port all modules to use the methods dict. + method = getattr(modobj, method_name, None) + if method_name.startswith('_'): + raise InvalidArguments(f'Method {method_name!r} is private.') + if not method: + raise InvalidCode('Unknown method "%s" in object.' % method_name) + if not getattr(method, 'no-args-flattening', False): + args = flatten(args) + state = ModuleState(self.interpreter) + # Many modules do for example self.interpreter.find_program_impl(), + # so we have to ensure they use the current interpreter and not the one + # that first imported that module, otherwise it will use outdated + # overrides. + modobj.interpreter = self.interpreter + if method_name in modobj.snippets: + ret = method(self.interpreter, state, args, kwargs) + else: + # This is not 100% reliable but we can't use hash() + # because the Build object contains dicts and lists. + num_targets = len(self.interpreter.build.targets) + ret = method(state, args, kwargs) + if num_targets != len(self.interpreter.build.targets): + raise InterpreterException('Extension module altered internal state illegally.') + if isinstance(ret, ModuleReturnValue): + self.interpreter.process_new_values(ret.new_objects) + ret = ret.return_value + return self.interpreter.holderify(ret) + +_Target = T.TypeVar('_Target', bound=build.Target) + + +class TargetHolder(InterpreterObject, ObjectHolder[_Target]): + def __init__(self, target: _Target, interp: 'Interpreter'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, target, interp.subproject) + self.interpreter = interp + + +_BuildTarget = T.TypeVar('_BuildTarget', bound=build.BuildTarget) + +class BuildTargetHolder(TargetHolder[_BuildTarget]): + def __init__(self, target: _BuildTarget, interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'extract_objects': self.extract_objects_method, + 'extract_all_objects': self.extract_all_objects_method, + 'name': self.name_method, + 'get_id': self.get_id_method, + 'outdir': self.outdir_method, + 'full_path': self.full_path_method, + 'private_dir_include': self.private_dir_include_method, + }) + + def __repr__(self): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.filename) + + def is_cross(self): + return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine) + + @noPosargs + @permittedKwargs({}) + def private_dir_include_method(self, args, kwargs): + return IncludeDirsHolder(build.IncludeDirs('', [], False, + [self.interpreter.backend.get_target_private_dir(self.held_object)])) + + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + + @noPosargs + @permittedKwargs({}) + def outdir_method(self, args, kwargs): + return self.interpreter.backend.get_target_dir(self.held_object) + + @permittedKwargs({}) + def extract_objects_method(self, args, kwargs): + gobjs = self.held_object.extract_objects(args) + return GeneratedObjectsHolder(gobjs) + + @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive']) + @noPosargs + @permittedKwargs({'recursive'}) + def extract_all_objects_method(self, args, kwargs): + recursive = kwargs.get('recursive', False) + gobjs = self.held_object.extract_all_objects(recursive) + if gobjs.objlist and 'recursive' not in kwargs: + mlog.warning('extract_all_objects called without setting recursive ' + 'keyword argument. Meson currently defaults to ' + 'non-recursive to maintain backward compatibility but ' + 'the default will be changed in the future.', + location=self.current_node) + return GeneratedObjectsHolder(gobjs) + + @noPosargs + @permittedKwargs({}) + def get_id_method(self, args, kwargs): + return self.held_object.get_id() + + @FeatureNew('name', '0.54.0') + @noPosargs + @permittedKwargs({}) + def name_method(self, args, kwargs): + return self.held_object.name + +class ExecutableHolder(BuildTargetHolder[build.Executable]): + pass + +class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]): + pass + +class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): + def __init__(self, target: build.SharedLibrary, interp: 'Interpreter'): + super().__init__(target, interp) + # Set to True only when called from self.func_shared_lib(). + target.shared_library_only = False + +class BothLibrariesHolder(BuildTargetHolder): + def __init__(self, shared_holder, static_holder, interp): + # FIXME: This build target always represents the shared library, but + # that should be configurable. + super().__init__(shared_holder.held_object, interp) + self.shared_holder = shared_holder + self.static_holder = static_holder + self.methods.update({'get_shared_lib': self.get_shared_lib_method, + 'get_static_lib': self.get_static_lib_method, + }) + + def __repr__(self): + r = '<{} {}: {}, {}: {}>' + h1 = self.shared_holder.held_object + h2 = self.static_holder.held_object + return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) + + @noPosargs + @permittedKwargs({}) + def get_shared_lib_method(self, args, kwargs): + return self.shared_holder + + @noPosargs + @permittedKwargs({}) + def get_static_lib_method(self, args, kwargs): + return self.static_holder + +class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): + pass + +class JarHolder(BuildTargetHolder[build.Jar]): + pass + +class CustomTargetIndexHolder(TargetHolder[build.CustomTargetIndex]): + def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'full_path': self.full_path_method, + }) + + @FeatureNew('custom_target[i].full_path', '0.54.0') + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + +class CustomTargetHolder(TargetHolder): + def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'): + super().__init__(target, interp) + self.methods.update({'full_path': self.full_path_method, + 'to_list': self.to_list_method, + }) + + def __repr__(self): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.command) + + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + + @FeatureNew('custom_target.to_list', '0.54.0') + @noPosargs + @permittedKwargs({}) + def to_list_method(self, args, kwargs): + result = [] + for i in self.held_object: + result.append(CustomTargetIndexHolder(i, self.interpreter)) + return result + + def __getitem__(self, index): + return CustomTargetIndexHolder(self.held_object[index], self.interpreter) + + def __setitem__(self, index, value): # lgtm[py/unexpected-raise-in-special-method] + raise InterpreterException('Cannot set a member of a CustomTarget') + + def __delitem__(self, index): # lgtm[py/unexpected-raise-in-special-method] + raise InterpreterException('Cannot delete a member of a CustomTarget') + + def outdir_include(self): + return IncludeDirsHolder(build.IncludeDirs('', [], False, + [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))])) + +class RunTargetHolder(TargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + + def __repr__(self): + r = '<{} {}: {}>' + h = self.held_object + return r.format(self.__class__.__name__, h.get_id(), h.command) diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py new file mode 100644 index 0000000..eef1ccf --- /dev/null +++ b/mesonbuild/interpreter/mesonmain.py @@ -0,0 +1,369 @@ +import os + +from .. import mesonlib +from .. import dependencies +from .. import build +from .. import mlog + +from ..mesonlib import unholder, MachineChoice, OptionKey +from ..programs import OverrideProgram, ExternalProgram +from ..interpreterbase import (InterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated, + typed_pos_args, permittedKwargs, noArgsFlattening, noPosargs, noKwargs, + MesonVersionString, InterpreterException) + +from .compiler import CompilerHolder +from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder, + CustomTargetHolder, CustomTargetIndexHolder, + EnvironmentVariablesHolder) + +import typing as T + +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 = self.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(self.interpreter.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) diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index 84fe658..ee09359 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -20,8 +20,9 @@ from . import ExtensionModule, ModuleReturnValue from .. import build, mesonlib, mlog from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args -from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder, DependencyHolder +from ..interpreter import ConfigurationDataHolder, SubprojectHolder, DependencyHolder from ..interpreterbase import ( + InterpreterException, InterpreterObject, ObjectHolder, diff --git a/mesonbuild/modules/keyval.py b/mesonbuild/modules/keyval.py index 8123a57..5fc4d52 100644 --- a/mesonbuild/modules/keyval.py +++ b/mesonbuild/modules/keyval.py @@ -16,8 +16,7 @@ from . import ExtensionModule from .. import mesonlib from ..mesonlib import typeslistify -from ..interpreterbase import FeatureNew, noKwargs -from ..interpreter import InvalidCode +from ..interpreterbase import FeatureNew, noKwargs, InvalidCode import os diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py index 91f7146..36d3164 100644 --- a/mesonbuild/modules/unstable_rust.py +++ b/mesonbuild/modules/unstable_rust.py @@ -19,8 +19,8 @@ from . import ExtensionModule, ModuleReturnValue from .. import mlog from ..build import BuildTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, IncludeDirs, CustomTarget from ..dependencies import Dependency, ExternalLibrary -from ..interpreter import ExecutableHolder, BuildTargetHolder, CustomTargetHolder, permitted_kwargs, noPosargs -from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, typed_pos_args +from ..interpreter import ExecutableHolder, BuildTargetHolder, CustomTargetHolder, permitted_kwargs +from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, typed_pos_args, noPosargs from ..mesonlib import stringlistify, unholder, listify, typeslistify, File if T.TYPE_CHECKING: |