aboutsummaryrefslogtreecommitdiffstats
path: root/meson/mesonbuild/interpreter
diff options
context:
space:
mode:
Diffstat (limited to 'meson/mesonbuild/interpreter')
-rw-r--r--meson/mesonbuild/interpreter/__init__.py25
-rw-r--r--meson/mesonbuild/interpreter/compiler.py785
-rw-r--r--meson/mesonbuild/interpreter/dependencyfallbacks.py351
-rw-r--r--meson/mesonbuild/interpreter/interpreter.py2794
-rw-r--r--meson/mesonbuild/interpreter/interpreterobjects.py996
-rw-r--r--meson/mesonbuild/interpreter/kwargs.py139
-rw-r--r--meson/mesonbuild/interpreter/mesonmain.py382
7 files changed, 5472 insertions, 0 deletions
diff --git a/meson/mesonbuild/interpreter/__init__.py b/meson/mesonbuild/interpreter/__init__.py
new file mode 100644
index 000000000..62b09bf37
--- /dev/null
+++ b/meson/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_dependency_kwargs
+from .compiler import CompilerHolder
+from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder,
+ CustomTargetIndexHolder, MachineHolder, Test,
+ ConfigurationDataObject, SubprojectHolder, DependencyHolder,
+ GeneratedListHolder, ExternalProgramHolder,
+ extract_required_kwarg)
diff --git a/meson/mesonbuild/interpreter/compiler.py b/meson/mesonbuild/interpreter/compiler.py
new file mode 100644
index 000000000..b1eef2fe5
--- /dev/null
+++ b/meson/mesonbuild/interpreter/compiler.py
@@ -0,0 +1,785 @@
+import functools
+
+from ..interpreterbase.decorators import typed_kwargs, KwargInfo
+
+from .interpreterobjects import (extract_required_kwarg, extract_search_dirs)
+
+from .. import mesonlib
+from .. import mlog
+from .. import dependencies
+from ..interpreterbase import (ObjectHolder, noPosargs, noKwargs, permittedKwargs,
+ FeatureNew, FeatureNewKwargs, disablerIfNotFound,
+ check_stringlist, InterpreterException, InvalidArguments)
+
+import typing as T
+import os
+
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
+ from ..compilers import Compiler, RunResult
+
+class TryRunResultHolder(ObjectHolder['RunResult']):
+ def __init__(self, res: 'RunResult', interpreter: 'Interpreter'):
+ super().__init__(res, interpreter)
+ 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.held_object.returncode
+
+ @noPosargs
+ @permittedKwargs({})
+ def compiled_method(self, args, kwargs):
+ return self.held_object.compiled
+
+ @noPosargs
+ @permittedKwargs({})
+ def stdout_method(self, args, kwargs):
+ return self.held_object.stdout
+
+ @noPosargs
+ @permittedKwargs({})
+ def stderr_method(self, args, kwargs):
+ return self.held_object.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(ObjectHolder['Compiler']):
+ def __init__(self, compiler: 'Compiler', interpreter: 'Interpreter'):
+ super().__init__(compiler, interpreter)
+ self.environment = self.env
+ 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,
+ })
+
+ @property
+ def compiler(self) -> 'Compiler':
+ return self.held_object
+
+ 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:
+ from ..build import IncludeDirs
+ if not isinstance(i, IncludeDirs):
+ raise InterpreterException('Include directories argument must be an include_directories object.')
+ for idir in i.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.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 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 lib
+
+ @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 lib
+
+ @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')
+ @typed_kwargs(
+ 'compiler.get_supported_arguments',
+ KwargInfo('checked', str, default='off', since='0.59.0',
+ validator=lambda s: 'must be one of "warn", "require" or "off"' if s not in ['warn', 'require', 'off'] else None)
+ )
+ def get_supported_arguments_method(self, args: T.Sequence[str], kwargs: T.Dict[str, T.Any]):
+ args = mesonlib.stringlistify(args)
+ supported_args = []
+ checked = kwargs.pop('checked')
+
+ for arg in args:
+ if not self.has_argument_method(arg, kwargs):
+ msg = f'Compiler for {self.compiler.get_display_language()} does not support "{arg}"'
+ if checked == 'warn':
+ mlog.warning(msg)
+ elif checked == 'require':
+ raise mesonlib.MesonException(msg)
+ else:
+ 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/meson/mesonbuild/interpreter/dependencyfallbacks.py b/meson/mesonbuild/interpreter/dependencyfallbacks.py
new file mode 100644
index 000000000..180209aa1
--- /dev/null
+++ b/meson/mesonbuild/interpreter/dependencyfallbacks.py
@@ -0,0 +1,351 @@
+from .interpreterobjects import SubprojectHolder, extract_required_kwarg
+
+from .. import mlog
+from .. import dependencies
+from .. import build
+from ..wrap import WrapMode
+from ..mesonlib import OptionKey, extract_as_list, stringlistify, version_compare_many
+from ..dependencies import Dependency, DependencyException, NotFoundDependency
+from ..interpreterbase import (MesonInterpreterObject, FeatureNew,
+ InterpreterException, InvalidArguments,
+ TYPE_nkwargs, TYPE_nvar)
+
+import typing as T
+if T.TYPE_CHECKING:
+ from .interpreter import Interpreter
+
+
+class DependencyFallbacksHolder(MesonInterpreterObject):
+ def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None,
+ default_options: T.Optional[T.List[str]] = None) -> None:
+ super().__init__(subproject=interpreter.subproject)
+ self.interpreter = interpreter
+ self.subproject = interpreter.subproject
+ self.coredata = interpreter.coredata
+ self.build = interpreter.build
+ self.environment = interpreter.environment
+ self.wrap_resolver = interpreter.environment.wrap_resolver
+ self.allow_fallback = allow_fallback
+ self.subproject_name = None
+ self.subproject_varname = None
+ self.subproject_kwargs = {'default_options': default_options or []}
+ self.names: T.List[str] = []
+ for name in names:
+ if not name:
+ raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed')
+ if '<' in name or '>' in name or '=' in name:
+ raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
+ 'version\n requirements use the \'version\' keyword argument instead.')
+ if name in self.names:
+ raise InterpreterException('dependency_fallbacks name {name!r} is duplicated')
+ self.names.append(name)
+
+ def set_fallback(self, fbinfo: T.Optional[T.Union[T.List[str], str]]) -> None:
+ # Legacy: This converts dependency()'s fallback kwargs.
+ if fbinfo is None:
+ return
+ if self.allow_fallback is not None:
+ raise InvalidArguments('"fallback" and "allow_fallback" arguments are mutually exclusive')
+ fbinfo = stringlistify(fbinfo)
+ if len(fbinfo) == 0:
+ # dependency('foo', fallback: []) is the same as dependency('foo', allow_fallback: false)
+ self.allow_fallback = False
+ return
+ if len(fbinfo) == 1:
+ FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject)
+ subp_name, varname = fbinfo[0], None
+ elif len(fbinfo) == 2:
+ subp_name, varname = fbinfo
+ else:
+ raise InterpreterException('Fallback info must have one or two items.')
+ self._subproject_impl(subp_name, varname)
+
+ def _subproject_impl(self, subp_name: str, varname: str) -> None:
+ if not varname:
+ # If no variable name is specified, check if the wrap file has one.
+ # If the wrap file has a variable name, better use it because the
+ # subproject most probably is not using meson.override_dependency().
+ for name in self.names:
+ varname = self.wrap_resolver.get_varname(subp_name, name)
+ if varname:
+ break
+ assert self.subproject_name is None
+ self.subproject_name = subp_name
+ self.subproject_varname = varname
+
+ def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ name = func_args[0]
+ cached_dep = self._get_cached_dep(name, kwargs)
+ if cached_dep:
+ self._verify_fallback_consistency(cached_dep)
+ return cached_dep
+
+ def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ # Note that there is no df.dependency() method, this is called for names
+ # given as positional arguments to dependency_fallbacks(name1, ...).
+ # We use kwargs from the dependency() function, for things like version,
+ # module, etc.
+ name = func_args[0]
+ self._handle_featurenew_dependencies(name)
+ dep = dependencies.find_external_dependency(name, self.environment, kwargs)
+ if dep.found():
+ for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
+ identifier = dependencies.get_dep_identifier(name, kwargs)
+ self.coredata.deps[for_machine].put(identifier, dep)
+ return dep
+ return None
+
+ def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ subp_name = func_args[0]
+ varname = self.subproject_varname
+ if subp_name and self._get_subproject(subp_name):
+ return self._get_subproject_dep(subp_name, varname, kwargs)
+ return None
+
+ def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ if self.forcefallback:
+ mlog.log('Looking for a fallback subproject for the dependency',
+ mlog.bold(self.display_name), 'because:\nUse of fallback dependencies is forced.')
+ elif self.nofallback:
+ mlog.log('Not looking for a fallback subproject for the dependency',
+ mlog.bold(self.display_name), 'because:\nUse of fallback dependencies is disabled.')
+ return None
+ else:
+ mlog.log('Looking for a fallback subproject for the dependency',
+ mlog.bold(self.display_name))
+
+ # Configure the subproject
+ subp_name = self.subproject_name
+ varname = self.subproject_varname
+ self.interpreter.do_subproject(subp_name, 'meson', func_kwargs)
+ return self._get_subproject_dep(subp_name, varname, kwargs)
+
+ def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
+ sub = self.interpreter.subprojects.get(subp_name)
+ if sub and sub.found():
+ return sub
+ return None
+
+ def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ # Verify the subproject is found
+ subproject = self._get_subproject(subp_name)
+ if not subproject:
+ mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
+ mlog.bold(subp_name), 'found:', mlog.red('NO'),
+ mlog.blue('(subproject failed to configure)'))
+ return None
+
+ # The subproject has been configured. If for any reason the dependency
+ # cannot be found in this subproject we have to return not-found object
+ # instead of None, because we don't want to continue the lookup on the
+ # system.
+
+ # Check if the subproject overridden at least one of the names we got.
+ cached_dep = None
+ for name in self.names:
+ cached_dep = self._get_cached_dep(name, kwargs)
+ if cached_dep:
+ break
+
+ # If we have cached_dep we did all the checks and logging already in
+ # self._get_cached_dep().
+ if cached_dep:
+ self._verify_fallback_consistency(cached_dep)
+ return cached_dep
+
+ # Legacy: Use the variable name if provided instead of relying on the
+ # subproject to override one of our dependency names
+ if not varname:
+ mlog.warning(f'Subproject {subp_name!r} did not override {self.display_name!r} dependency and no variable name specified')
+ mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
+ return self._notfound_dependency()
+
+ var_dep = self._get_subproject_variable(subproject, varname) or self._notfound_dependency()
+ if not var_dep.found():
+ mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
+ return var_dep
+
+ wanted = stringlistify(kwargs.get('version', []))
+ found = var_dep.get_version()
+ if not self._check_version(wanted, found):
+ mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
+ 'found', mlog.normal_cyan(found), 'but need:',
+ mlog.bold(', '.join([f"'{e}'" for e in wanted])))
+ return self._notfound_dependency()
+
+ mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
+ mlog.bold(subproject.subdir), 'found:', mlog.green('YES'),
+ mlog.normal_cyan(found) if found else None)
+ return var_dep
+
+ def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
+ # Unlike other methods, this one returns not-found dependency instead
+ # of None in the case the dependency is cached as not-found, or if cached
+ # version does not match. In that case we don't want to continue with
+ # other candidates.
+ for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
+ identifier = dependencies.get_dep_identifier(name, kwargs)
+ wanted_vers = stringlistify(kwargs.get('version', []))
+
+ override = self.build.dependency_overrides[for_machine].get(identifier)
+ if override:
+ info = [mlog.blue('(overridden)' if override.explicit else '(cached)')]
+ cached_dep = override.dep
+ # We don't implicitly override not-found dependencies, but user could
+ # have explicitly called meson.override_dependency() with a not-found
+ # dep.
+ if not cached_dep.found():
+ mlog.log('Dependency', mlog.bold(self.display_name),
+ 'found:', mlog.red('NO'), *info)
+ return cached_dep
+ else:
+ info = [mlog.blue('(cached)')]
+ cached_dep = self.coredata.deps[for_machine].get(identifier)
+
+ if cached_dep:
+ found_vers = cached_dep.get_version()
+ if not self._check_version(wanted_vers, found_vers):
+ mlog.log('Dependency', mlog.bold(name),
+ 'found:', mlog.red('NO'),
+ 'found', mlog.normal_cyan(found_vers), 'but need:',
+ mlog.bold(', '.join([f"'{e}'" for e in wanted_vers])),
+ *info)
+ return self._notfound_dependency()
+ if found_vers:
+ info = [mlog.normal_cyan(found_vers), *info]
+ mlog.log('Dependency', mlog.bold(self.display_name),
+ 'found:', mlog.green('YES'), *info)
+ return cached_dep
+ return None
+
+ def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[Dependency]:
+ try:
+ var_dep = subproject.get_variable_method([varname], {})
+ except InvalidArguments:
+ var_dep = None
+ if not isinstance(var_dep, Dependency):
+ mlog.warning(f'Variable {varname!r} in the subproject {subproject.subdir!r} is',
+ 'not found' if var_dep is None else 'not a dependency object')
+ return None
+ return var_dep
+
+ def _verify_fallback_consistency(self, cached_dep: Dependency):
+ subp_name = self.subproject_name
+ varname = self.subproject_varname
+ subproject = self._get_subproject(subp_name)
+ if subproject and varname:
+ var_dep = self._get_subproject_variable(subproject, varname)
+ if var_dep and cached_dep.found() and var_dep != cached_dep:
+ mlog.warning(f'Inconsistency: Subproject has overridden the dependency with another variable than {varname!r}')
+
+ def _handle_featurenew_dependencies(self, name: str) -> None:
+ 'Do a feature check on dependencies used by this subproject'
+ if name == 'mpi':
+ FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
+ elif name == 'pcap':
+ FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
+ elif name == 'vulkan':
+ FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
+ elif name == 'libwmf':
+ FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
+ elif name == 'openmp':
+ FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
+
+ def _notfound_dependency(self) -> NotFoundDependency:
+ return NotFoundDependency(self.environment)
+
+ @staticmethod
+ def _check_version(wanted: T.Optional[str], found: str) -> bool:
+ if not wanted:
+ return True
+ if found == 'undefined' or not version_compare_many(found, wanted)[0]:
+ return False
+ return True
+
+ def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], TYPE_nvar, TYPE_nkwargs]]:
+ candidates = []
+ # 1. check if any of the names is cached already.
+ for name in self.names:
+ candidates.append((self._do_dependency_cache, [name], {}))
+ # 2. check if the subproject fallback has already been configured.
+ if self.subproject_name:
+ candidates.append((self._do_existing_subproject, [self.subproject_name], self.subproject_kwargs))
+ # 3. check external dependency if we are not forced to use subproject
+ if not self.forcefallback or not self.subproject_name:
+ for name in self.names:
+ candidates.append((self._do_dependency, [name], {}))
+ # 4. configure the subproject
+ if self.subproject_name:
+ candidates.append((self._do_subproject, [self.subproject_name], self.subproject_kwargs))
+ return candidates
+
+ def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency:
+ self.display_name = self.names[0] if self.names else '(anonymous)'
+ mods = extract_as_list(kwargs, 'modules')
+ if mods:
+ self.display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
+
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
+ if disabled:
+ mlog.log('Dependency', mlog.bold(self.display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return self._notfound_dependency()
+
+ # Check if usage of the subproject fallback is forced
+ wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
+ force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for'))
+ self.nofallback = wrap_mode == WrapMode.nofallback
+ self.forcefallback = (force_fallback or
+ wrap_mode == WrapMode.forcefallback or
+ any(name in force_fallback_for for name in self.names) or
+ self.subproject_name in force_fallback_for)
+
+ # Add an implicit subproject fallback if none has been set explicitly,
+ # unless implicit fallback is not allowed.
+ # Legacy: self.allow_fallback can be None when that kwarg is not defined
+ # in dependency('name'). In that case we don't want to use implicit
+ # fallback when required is false because user will typically fallback
+ # manually using cc.find_library() for example.
+ if not self.subproject_name and self.allow_fallback is not False:
+ for name in self.names:
+ subp_name, varname = self.wrap_resolver.find_dep_provider(name)
+ if subp_name:
+ self.forcefallback |= subp_name in force_fallback_for
+ if self.forcefallback or self.allow_fallback is True or required or self._get_subproject(subp_name):
+ self._subproject_impl(subp_name, varname)
+ break
+
+ candidates = self._get_candidates()
+
+ # writing just "dependency('')" is an error, because it can only fail
+ if not candidates and required:
+ raise InvalidArguments('Dependency is required but has no candidates.')
+
+ # Try all candidates, only the last one is really required.
+ last = len(candidates) - 1
+ for i, item in enumerate(candidates):
+ func, func_args, func_kwargs = item
+ func_kwargs['required'] = required and (i == last)
+ kwargs['required'] = required and (i == last)
+ dep = func(kwargs, func_args, func_kwargs)
+ if dep and dep.found():
+ # Override this dependency to have consistent results in subsequent
+ # dependency lookups.
+ for name in self.names:
+ for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
+ identifier = dependencies.get_dep_identifier(name, kwargs)
+ if identifier not in self.build.dependency_overrides[for_machine]:
+ self.build.dependency_overrides[for_machine][identifier] = \
+ build.DependencyOverride(dep, self.interpreter.current_node, explicit=False)
+ return dep
+ elif required and (dep or i == last):
+ # This was the last candidate or the dependency has been cached
+ # as not-found, or cached dependency version does not match,
+ # otherwise func() would have returned None instead.
+ raise DependencyException(f'Dependency {self.display_name!r} is required but not found.')
+ elif dep:
+ # Same as above, but the dependency is not required.
+ return dep
+ return self._notfound_dependency()
diff --git a/meson/mesonbuild/interpreter/interpreter.py b/meson/mesonbuild/interpreter/interpreter.py
new file mode 100644
index 000000000..4a60ff485
--- /dev/null
+++ b/meson/mesonbuild/interpreter/interpreter.py
@@ -0,0 +1,2794 @@
+# Copyright 2012-2021 The Meson development team
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .. import mparser
+from .. import environment
+from .. import coredata
+from .. import dependencies
+from .. import mlog
+from .. import build
+from .. import optinterpreter
+from .. import compilers
+from ..wrap import wrap, WrapMode
+from .. import mesonlib
+from ..mesonlib import HoldableObject, FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep
+from ..programs import ExternalProgram, NonExistingExternalProgram
+from ..dependencies import Dependency
+from ..depfile import DepFile
+from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args
+from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, permissive_unholder_return
+from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
+from ..interpreterbase import Disabler, disablerIfNotFound
+from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
+from ..interpreterbase import ObjectHolder, RangeHolder
+from ..interpreterbase import TYPE_nkwargs, TYPE_nvar, TYPE_var
+from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
+from ..cmake import CMakeInterpreter
+from ..backend.backends import Backend, ExecutableSerialisation
+
+from . import interpreterobjects as OBJ
+from . import compiler as compilerOBJ
+from .mesonmain import MesonMain
+from .dependencyfallbacks import DependencyFallbacksHolder
+from .interpreterobjects import (
+ SubprojectHolder,
+ EnvironmentVariablesObject,
+ ConfigurationDataObject,
+ Test,
+ RunProcess,
+ extract_required_kwarg,
+ extract_search_dirs,
+ NullSubprojectInterpreter,
+)
+
+from pathlib import Path
+import os
+import shutil
+import uuid
+import re
+import stat
+import collections
+import typing as T
+import textwrap
+import importlib
+
+if T.TYPE_CHECKING:
+ from . import kwargs
+
+ # Input source types passed to Targets
+ SourceInputs = T.Union[mesonlib.File, build.GeneratedList, build.BuildTarget, build.BothLibraries,
+ build.CustomTargetIndex, build.CustomTarget, build.GeneratedList, str]
+ # Input source types passed to the build.Target5 classes
+ SourceOutputs = T.Union[mesonlib.File, build.GeneratedList,
+ build.BuildTarget, build.CustomTargetIndex, build.CustomTarget,
+ build.GeneratedList]
+
+
+def _language_validator(l: T.List[str]) -> T.Optional[str]:
+ """Validate language keyword argument.
+
+ Particularly for functions like `add_compiler()`, and `add_*_args()`
+ """
+ diff = {a.lower() for a in l}.difference(compilers.all_languages)
+ if diff:
+ return f'unknown languages: {", ".join(diff)}'
+ return None
+
+
+def _install_mode_validator(mode: T.List[T.Union[str, bool, int]]) -> T.Optional[str]:
+ """Validate the `install_mode` keyword argument.
+
+ This is a rather odd thing, it's a scalar, or an array of 3 values in the form:
+ [(str | False), (str | int | False) = False, (str | int | False) = False]
+ Where the second and third arguments are not required, and are considered to
+ default to False.
+ """
+ if not mode:
+ return None
+ if True in mode:
+ return 'can only be a string or false, not true'
+ if len(mode) > 3:
+ return 'may have at most 3 elements'
+
+ perms = mode[0]
+ if not isinstance(perms, (str, bool)):
+ return 'permissions part must be a string or false'
+
+ if isinstance(perms, str):
+ if not len(perms) == 9:
+ return (f'permissions string must be exactly 9 characters, got "{len(perms)}" '
+ 'in the form rwxr-xr-x')
+ for i in [0, 3, 6]:
+ if perms[i] not in {'-', 'r'}:
+ return f'bit {i} must be "-" or "r", not {perms[i]}'
+ for i in [1, 4, 7]:
+ if perms[i] not in {'-', 'w'}:
+ return f'bit {i} must be "-" or "w", not {perms[i]}'
+ for i in [2, 5]:
+ if perms[i] not in {'-', 'x', 's', 'S'}:
+ return f'bit {i} must be "-", "s", "S", or "x", not {perms[i]}'
+ if perms[8] not in {'-', 'x', 't', 'T'}:
+ return f'bit 8 must be "-", "t", "T", or "x", not {perms[8]}'
+
+ if len(mode) >= 2 and not isinstance(mode[1], (int, str, bool)):
+ return 'second componenent must be a string, number, or False if provided'
+ if len(mode) >= 3 and not isinstance(mode[2], (int, str, bool)):
+ return 'third componenent must be a string, number, or False if provided'
+
+ return None
+
+
+def _install_mode_convertor(mode: T.Optional[T.List[T.Union[str, bool, int]]]) -> FileMode:
+ """Convert the DSL form of the `install_mode` keyword arugment to `FileMode`
+
+ This is not required, and if not required returns None
+
+ TODO: It's not clear to me why this needs to be None and not just return an
+ emtpy FileMode.
+ """
+ # this has already been validated by the validator
+ return FileMode(*[m if isinstance(m, str) else None for m in mode])
+
+
+_NATIVE_KW = KwargInfo(
+ 'native', bool,
+ default=False,
+ convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST)
+
+_LANGUAGE_KW = KwargInfo(
+ 'language', ContainerTypeInfo(list, str, allow_empty=False),
+ listify=True,
+ required=True,
+ validator=_language_validator,
+ convertor=lambda x: [i.lower() for i in x])
+
+_INSTALL_MODE_KW = KwargInfo(
+ 'install_mode',
+ ContainerTypeInfo(list, (str, bool, int)),
+ listify=True,
+ default=[],
+ validator=_install_mode_validator,
+ convertor=_install_mode_convertor,
+)
+
+_REQUIRED_KW = KwargInfo(
+ 'required',
+ (bool, coredata.UserFeatureOption),
+ default=True,
+ # TODO: extract_required_kwarg could be converted to a convertor
+)
+
+
+def stringifyUserArguments(args, quote=False):
+ if isinstance(args, list):
+ return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args])
+ elif isinstance(args, dict):
+ return '{%s}' % ', '.join(['{} : {}'.format(stringifyUserArguments(k, True), stringifyUserArguments(v, True)) for k, v in args.items()])
+ elif isinstance(args, int):
+ return str(args)
+ elif isinstance(args, str):
+ return f"'{args}'" if quote else args
+ raise InvalidArguments('Function accepts only strings, integers, lists, dictionaries and lists thereof.')
+
+class Summary:
+ def __init__(self, project_name, project_version):
+ self.project_name = project_name
+ self.project_version = project_version
+ self.sections = collections.defaultdict(dict)
+ self.max_key_len = 0
+
+ def add_section(self, section, values, kwargs, subproject):
+ bool_yn = kwargs.get('bool_yn', False)
+ if not isinstance(bool_yn, bool):
+ raise InterpreterException('bool_yn keyword argument must be boolean')
+ list_sep = kwargs.get('list_sep')
+ if list_sep is not None and not isinstance(list_sep, str):
+ raise InterpreterException('list_sep keyword argument must be string')
+ for k, v in values.items():
+ if k in self.sections[section]:
+ raise InterpreterException(f'Summary section {section!r} already have key {k!r}')
+ formatted_values = []
+ for i in listify(v):
+ if isinstance(i, bool) and bool_yn:
+ formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
+ elif isinstance(i, (str, int, bool)):
+ formatted_values.append(str(i))
+ elif isinstance(i, (ExternalProgram, Dependency)):
+ FeatureNew.single_use('dependency or external program in summary', '0.57.0', subproject)
+ formatted_values.append(i.summary_value())
+ elif isinstance(i, coredata.UserOption):
+ FeatureNew.single_use('feature option in summary', '0.58.0', subproject)
+ formatted_values.append(i.printable_value())
+ else:
+ m = 'Summary value in section {!r}, key {!r}, must be string, integer, boolean, dependency or external program'
+ raise InterpreterException(m.format(section, k))
+ self.sections[section][k] = (formatted_values, list_sep)
+ self.max_key_len = max(self.max_key_len, len(k))
+
+ def dump(self):
+ mlog.log(self.project_name, mlog.normal_cyan(self.project_version))
+ for section, values in self.sections.items():
+ mlog.log('') # newline
+ if section:
+ mlog.log(' ', mlog.bold(section))
+ for k, v in values.items():
+ v, list_sep = v
+ padding = self.max_key_len - len(k)
+ end = ' ' if v else ''
+ mlog.log(' ' * 3, k + ' ' * padding + ':', end=end)
+ indent = self.max_key_len + 6
+ self.dump_value(v, list_sep, indent)
+ mlog.log('') # newline
+
+ def dump_value(self, arr, list_sep, indent):
+ lines_sep = '\n' + ' ' * indent
+ if list_sep is None:
+ mlog.log(*arr, sep=lines_sep)
+ return
+ max_len = shutil.get_terminal_size().columns
+ line = []
+ line_len = indent
+ lines_sep = list_sep.rstrip() + lines_sep
+ for v in arr:
+ v_len = len(v) + len(list_sep)
+ if line and line_len + v_len > max_len:
+ mlog.log(*line, sep=list_sep, end=lines_sep)
+ line_len = indent
+ line = []
+ line.append(v)
+ line_len += v_len
+ mlog.log(*line, sep=list_sep)
+
+known_library_kwargs = (
+ build.known_shlib_kwargs |
+ build.known_stlib_kwargs
+)
+
+known_build_target_kwargs = (
+ known_library_kwargs |
+ build.known_exe_kwargs |
+ build.known_jar_kwargs |
+ {'target_type'}
+)
+
+TEST_KWARGS: T.List[KwargInfo] = [
+ KwargInfo('args', ContainerTypeInfo(list, (str, mesonlib.File, build.Target)),
+ listify=True, default=[]),
+ KwargInfo('should_fail', bool, default=False),
+ KwargInfo('timeout', int, default=30),
+ KwargInfo('workdir', str, default=None,
+ validator=lambda x: 'must be an absolute path' if not os.path.isabs(x) else None),
+ KwargInfo('protocol', str,
+ default='exitcode',
+ validator=lambda x: 'value must be one of "exitcode", "tap", "gtest", "rust"' if x not in {'exitcode', 'tap', 'gtest', 'rust'} else None,
+ since_values={'gtest': '0.55.0', 'rust': '0.57.0'}),
+ KwargInfo('depends', ContainerTypeInfo(list, (build.CustomTarget, build.BuildTarget)),
+ listify=True, default=[], since='0.46.0'),
+ KwargInfo('priority', int, default=0, since='0.52.0'),
+ # TODO: env needs reworks of the way the environment variable holder itself works probably
+ KwargInfo('env', (EnvironmentVariablesObject, list, dict, str)),
+ KwargInfo('suite', ContainerTypeInfo(list, str), listify=True, default=['']), # yes, a list of empty string
+]
+
+permitted_dependency_kwargs = {
+ 'allow_fallback',
+ 'cmake_args',
+ 'cmake_module_path',
+ 'cmake_package_version',
+ 'components',
+ 'default_options',
+ 'fallback',
+ 'include_type',
+ 'language',
+ 'main',
+ 'method',
+ 'modules',
+ 'native',
+ 'not_found_message',
+ 'optional_modules',
+ 'private_headers',
+ 'required',
+ 'static',
+ 'version',
+}
+
+class Interpreter(InterpreterBase, HoldableObject):
+
+ def __init__(
+ self,
+ _build: build.Build,
+ backend: T.Optional[Backend] = None,
+ subproject: str = '',
+ subdir: str = '',
+ subproject_dir: str = 'subprojects',
+ default_project_options: T.Optional[T.Dict[str, str]] = None,
+ mock: bool = False,
+ ast: T.Optional[mparser.CodeBlockNode] = None,
+ is_translated: bool = False,
+ ) -> None:
+ super().__init__(_build.environment.get_source_dir(), subdir, subproject)
+ self.an_unpicklable_object = mesonlib.an_unpicklable_object
+ self.build = _build
+ self.environment = self.build.environment
+ self.coredata = self.environment.get_coredata()
+ self.backend = backend
+ self.summary = {}
+ self.modules = {}
+ # Subproject directory is usually the name of the subproject, but can
+ # be different for dependencies provided by wrap files.
+ self.subproject_directory_name = subdir.split(os.path.sep)[-1]
+ self.subproject_dir = subproject_dir
+ self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
+ if not mock and ast is None:
+ self.load_root_meson_file()
+ self.sanity_check_ast()
+ elif ast is not None:
+ self.ast = ast
+ self.sanity_check_ast()
+ self.builtin.update({'meson': MesonMain(self.build, self)})
+ self.generators: T.List[build.Generator] = []
+ self.processed_buildfiles = set() # type: T.Set[str]
+ self.project_args_frozen = False
+ self.global_args_frozen = False # implies self.project_args_frozen
+ self.subprojects: T.Dict[str, SubprojectHolder] = {}
+ self.subproject_stack = []
+ self.configure_file_outputs = {}
+ # Passed from the outside, only used in subprojects.
+ if default_project_options:
+ self.default_project_options = default_project_options.copy()
+ else:
+ self.default_project_options = {}
+ self.project_default_options = {}
+ self.build_func_dict()
+ self.build_holder_map()
+
+ # build_def_files needs to be defined before parse_project is called
+ #
+ # For non-meson subprojects, we'll be using the ast. Even if it does
+ # exist we don't want to add a dependency on it, it's autogenerated
+ # from the actual build files, and is just for reference.
+ self.build_def_files = []
+ build_filename = os.path.join(self.subdir, environment.build_filename)
+ if not is_translated:
+ self.build_def_files.append(build_filename)
+ if not mock:
+ self.parse_project()
+ self._redetect_machines()
+
+ def _redetect_machines(self):
+ # Re-initialize machine descriptions. We can do a better job now because we
+ # have the compilers needed to gain more knowledge, so wipe out old
+ # inference and start over.
+ machines = self.build.environment.machines.miss_defaulting()
+ machines.build = environment.detect_machine_info(self.coredata.compilers.build)
+ self.build.environment.machines = machines.default_missing()
+ assert self.build.environment.machines.build.cpu is not None
+ assert self.build.environment.machines.host.cpu is not None
+ assert self.build.environment.machines.target.cpu is not None
+
+ self.builtin['build_machine'] = \
+ OBJ.MachineHolder(self.build.environment.machines.build, self)
+ self.builtin['host_machine'] = \
+ OBJ.MachineHolder(self.build.environment.machines.host, self)
+ self.builtin['target_machine'] = \
+ OBJ.MachineHolder(self.build.environment.machines.target, self)
+
+ # TODO: Why is this in interpreter.py and not CoreData or Environment?
+ def get_non_matching_default_options(self) -> T.Iterator[T.Tuple[str, str, coredata.UserOption]]:
+ for def_opt_name, def_opt_value in self.project_default_options.items():
+ cur_opt_value = self.coredata.options.get(def_opt_name)
+ try:
+ if cur_opt_value is not None and cur_opt_value.validate_value(def_opt_value) != cur_opt_value.value:
+ yield (str(def_opt_name), def_opt_value, cur_opt_value)
+ except mesonlib.MesonException:
+ # Since the default value does not validate, it cannot be in use
+ # Report the user-specified value as non-matching
+ yield (str(def_opt_name), def_opt_value, cur_opt_value)
+
+ def build_func_dict(self):
+ self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
+ 'add_project_arguments': self.func_add_project_arguments,
+ 'add_global_link_arguments': self.func_add_global_link_arguments,
+ 'add_project_link_arguments': self.func_add_project_link_arguments,
+ 'add_test_setup': self.func_add_test_setup,
+ 'add_languages': self.func_add_languages,
+ 'alias_target': self.func_alias_target,
+ 'assert': self.func_assert,
+ 'benchmark': self.func_benchmark,
+ 'build_target': self.func_build_target,
+ 'configuration_data': self.func_configuration_data,
+ 'configure_file': self.func_configure_file,
+ 'custom_target': self.func_custom_target,
+ 'declare_dependency': self.func_declare_dependency,
+ 'dependency': self.func_dependency,
+ 'disabler': self.func_disabler,
+ 'environment': self.func_environment,
+ 'error': self.func_error,
+ 'executable': self.func_executable,
+ 'generator': self.func_generator,
+ 'gettext': self.func_gettext,
+ 'get_option': self.func_get_option,
+ 'get_variable': self.func_get_variable,
+ 'files': self.func_files,
+ 'find_library': self.func_find_library,
+ 'find_program': self.func_find_program,
+ 'include_directories': self.func_include_directories,
+ 'import': self.func_import,
+ 'install_data': self.func_install_data,
+ 'install_headers': self.func_install_headers,
+ 'install_man': self.func_install_man,
+ 'install_subdir': self.func_install_subdir,
+ 'is_disabler': self.func_is_disabler,
+ 'is_variable': self.func_is_variable,
+ 'jar': self.func_jar,
+ 'join_paths': self.func_join_paths,
+ 'library': self.func_library,
+ 'message': self.func_message,
+ 'warning': self.func_warning,
+ 'option': self.func_option,
+ 'project': self.func_project,
+ 'run_target': self.func_run_target,
+ 'run_command': self.func_run_command,
+ 'set_variable': self.func_set_variable,
+ 'subdir': self.func_subdir,
+ 'subdir_done': self.func_subdir_done,
+ 'subproject': self.func_subproject,
+ 'summary': self.func_summary,
+ 'shared_library': self.func_shared_lib,
+ 'shared_module': self.func_shared_module,
+ 'static_library': self.func_static_lib,
+ 'both_libraries': self.func_both_lib,
+ 'test': self.func_test,
+ 'vcs_tag': self.func_vcs_tag,
+ 'range': self.func_range,
+ })
+ if 'MESON_UNIT_TEST' in os.environ:
+ self.funcs.update({'exception': self.func_exception})
+
+ def build_holder_map(self) -> None:
+ '''
+ Build a mapping of `HoldableObject` types to their corresponding
+ `ObjectHolder`s. This mapping is used in `InterpreterBase` to automatically
+ holderify all returned values from methods and functions.
+ '''
+ self.holder_map.update({
+ mesonlib.File: OBJ.FileHolder,
+ build.SharedLibrary: OBJ.SharedLibraryHolder,
+ build.StaticLibrary: OBJ.StaticLibraryHolder,
+ build.BothLibraries: OBJ.BothLibrariesHolder,
+ build.SharedModule: OBJ.SharedModuleHolder,
+ build.Executable: OBJ.ExecutableHolder,
+ build.Jar: OBJ.JarHolder,
+ build.CustomTarget: OBJ.CustomTargetHolder,
+ build.CustomTargetIndex: OBJ.CustomTargetIndexHolder,
+ build.Generator: OBJ.GeneratorHolder,
+ build.GeneratedList: OBJ.GeneratedListHolder,
+ build.ExtractedObjects: OBJ.GeneratedObjectsHolder,
+ build.RunTarget: OBJ.RunTargetHolder,
+ build.AliasTarget: OBJ.AliasTargetHolder,
+ build.Headers: OBJ.HeadersHolder,
+ build.Man: OBJ.ManHolder,
+ build.Data: OBJ.DataHolder,
+ build.InstallDir: OBJ.InstallDirHolder,
+ build.IncludeDirs: OBJ.IncludeDirsHolder,
+ compilers.RunResult: compilerOBJ.TryRunResultHolder,
+ dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder,
+ coredata.UserFeatureOption: OBJ.FeatureOptionHolder,
+ })
+
+ '''
+ Build a mapping of `HoldableObject` base classes to their
+ corresponding `ObjectHolder`s. The difference to `self.holder_map`
+ is that the keys here define an upper bound instead of requireing an
+ exact match.
+
+ The mappings defined here are only used when there was no direct hit
+ found in `self.holder_map`.
+ '''
+ self.bound_holder_map.update({
+ dependencies.Dependency: OBJ.DependencyHolder,
+ ExternalProgram: OBJ.ExternalProgramHolder,
+ compilers.Compiler: compilerOBJ.CompilerHolder,
+ ModuleObject: OBJ.ModuleObjectHolder,
+ MutableModuleObject: OBJ.MutableModuleObjectHolder,
+ })
+
+ def append_holder_map(self, held_type: T.Type[mesonlib.HoldableObject], holder_type: T.Type[ObjectHolder]) -> None:
+ '''
+ Adds one additional mapping to the `holder_map`.
+
+ The intended use for this function is in the `initialize` method of
+ modules to register custom object holders.
+ '''
+ self.holder_map.update({
+ held_type: holder_type
+ })
+
+ def process_new_values(self, invalues: T.List[TYPE_var]) -> None:
+ invalues = listify(invalues)
+ for v in invalues:
+ if isinstance(v, ObjectHolder):
+ raise InterpreterException('Modules must not return ObjectHolders')
+ if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)):
+ self.add_target(v.name, v)
+ elif isinstance(v, list):
+ self.process_new_values(v)
+ elif isinstance(v, ExecutableSerialisation):
+ v.subproject = self.subproject
+ self.build.install_scripts.append(v)
+ elif isinstance(v, build.Data):
+ self.build.data.append(v)
+ elif isinstance(v, dependencies.InternalDependency):
+ # FIXME: This is special cased and not ideal:
+ # The first source is our new VapiTarget, the rest are deps
+ self.process_new_values(v.sources[0])
+ elif isinstance(v, build.InstallDir):
+ self.build.install_dirs.append(v)
+ elif isinstance(v, Test):
+ self.build.tests.append(v)
+ elif isinstance(v, (int, str, bool, Disabler, ObjectHolder, build.GeneratedList,
+ ExternalProgram)):
+ pass
+ else:
+ raise InterpreterException('Module returned a value of unknown type.')
+
+ def get_build_def_files(self) -> T.List[str]:
+ return self.build_def_files
+
+ def add_build_def_file(self, f: mesonlib.FileOrString) -> None:
+ # Use relative path for files within source directory, and absolute path
+ # for system files. Skip files within build directory. Also skip not regular
+ # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this
+ # is especially important to convert '/' to '\' on Windows.
+ if isinstance(f, mesonlib.File):
+ if f.is_built:
+ return
+ f = os.path.normpath(f.relative_name())
+ elif os.path.isfile(f) and not f.startswith('/dev'):
+ srcdir = Path(self.environment.get_source_dir())
+ builddir = Path(self.environment.get_build_dir())
+ try:
+ f = Path(f).resolve()
+ except OSError:
+ f = Path(f)
+ s = f.stat()
+ if (hasattr(s, 'st_file_attributes') and
+ s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and
+ s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK):
+ # This is a Windows Store link which we can't
+ # resolve, so just do our best otherwise.
+ f = f.parent.resolve() / f.name
+ else:
+ raise
+ if builddir in f.parents:
+ return
+ if srcdir in f.parents:
+ f = f.relative_to(srcdir)
+ f = str(f)
+ else:
+ return
+ if f not in self.build_def_files:
+ self.build_def_files.append(f)
+
+ def get_variables(self):
+ return self.variables
+
+ def check_stdlibs(self):
+ machine_choices = [MachineChoice.HOST]
+ if self.coredata.is_cross_build():
+ machine_choices.append(MachineChoice.BUILD)
+ for for_machine in machine_choices:
+ props = self.build.environment.properties[for_machine]
+ for l in self.coredata.compilers[for_machine].keys():
+ try:
+ di = mesonlib.stringlistify(props.get_stdlib(l))
+ except KeyError:
+ continue
+ if len(di) == 1:
+ FeatureNew.single_use('stdlib without variable name', '0.56.0', self.subproject)
+ kwargs = {'native': for_machine is MachineChoice.BUILD,
+ }
+ name = l + '_stdlib'
+ df = DependencyFallbacksHolder(self, [name])
+ df.set_fallback(di)
+ dep = df.lookup(kwargs, force_fallback=True)
+ self.build.stdlibs[for_machine][l] = dep
+
+ def _import_module(self, modname: str, required: bool) -> T.Union[ExtensionModule, NewExtensionModule, NotFoundExtensionModule]:
+ if modname in self.modules:
+ return self.modules[modname]
+ try:
+ module = importlib.import_module('mesonbuild.modules.' + modname)
+ except ImportError:
+ if required:
+ raise InvalidArguments(f'Module "{modname}" does not exist')
+ ext_module = NotFoundExtensionModule()
+ else:
+ ext_module = module.initialize(self)
+ assert isinstance(ext_module, (ExtensionModule, NewExtensionModule))
+ self.modules[modname] = ext_module
+ return ext_module
+
+ @typed_pos_args('import', str)
+ @typed_kwargs(
+ 'import',
+ _REQUIRED_KW.evolve(since='0.59.0'),
+ KwargInfo('disabler', bool, default=False, since='0.59.0'),
+ )
+ @disablerIfNotFound
+ def func_import(self, node: mparser.BaseNode, args: T.Tuple[str],
+ kwargs: 'kwargs.FuncImportModule') -> T.Union[ExtensionModule, NewExtensionModule, NotFoundExtensionModule]:
+ modname = args[0]
+ disabled, required, _ = extract_required_kwarg(kwargs, self.subproject)
+ if disabled:
+ return NotFoundExtensionModule()
+
+ if modname.startswith('unstable-'):
+ plainname = modname.split('-', 1)[1]
+ try:
+ # check if stable module exists
+ mod = self._import_module(plainname, required)
+ # XXX: this is acutally not helpful, since it doesn't do a version check
+ mlog.warning(f'Module {modname} is now stable, please use the {plainname} module instead.')
+ return mod
+ except InvalidArguments:
+ mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node)
+ modname = 'unstable_' + plainname
+ return self._import_module(modname, required)
+
+ @stringArgs
+ @noKwargs
+ def func_files(self, node, args, kwargs):
+ return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args]
+
+ # Used by declare_dependency() and pkgconfig.generate()
+ def extract_variables(self, kwargs, argname='variables', list_new=False, dict_new=False):
+ variables = kwargs.get(argname, {})
+ if isinstance(variables, dict):
+ if dict_new and variables:
+ FeatureNew.single_use('variables as dictionary', '0.56.0', self.subproject)
+ else:
+ varlist = mesonlib.stringlistify(variables)
+ if list_new:
+ FeatureNew.single_use('variables as list of strings', '0.56.0', self.subproject)
+ variables = collections.OrderedDict()
+ for v in varlist:
+ try:
+ (key, value) = v.split('=', 1)
+ except ValueError:
+ raise InterpreterException(f'Variable {v!r} must have a value separated by equals sign.')
+ variables[key.strip()] = value.strip()
+ for k, v in variables.items():
+ if not k or not v:
+ raise InterpreterException('Empty variable name or value')
+ if any(c.isspace() for c in k):
+ raise InterpreterException(f'Invalid whitespace in variable name "{k}"')
+ if not isinstance(v, str):
+ raise InterpreterException('variables values must be strings.')
+ return variables
+
+ @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole'])
+ @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables'])
+ @permittedKwargs({'include_directories', 'link_with', 'sources', 'dependencies',
+ 'compile_args', 'link_args', 'link_whole', 'version',
+ 'variables' })
+ @noPosargs
+ def func_declare_dependency(self, node, args, kwargs):
+ version = kwargs.get('version', self.project_version)
+ if not isinstance(version, str):
+ raise InterpreterException('Version must be a string.')
+ incs = self.extract_incdirs(kwargs)
+ libs = extract_as_list(kwargs, 'link_with')
+ libs_whole = extract_as_list(kwargs, 'link_whole')
+ sources = extract_as_list(kwargs, 'sources')
+ sources = listify(self.source_strings_to_files(sources))
+ deps = extract_as_list(kwargs, 'dependencies')
+ compile_args = mesonlib.stringlistify(kwargs.get('compile_args', []))
+ link_args = mesonlib.stringlistify(kwargs.get('link_args', []))
+ variables = self.extract_variables(kwargs, list_new=True)
+ final_deps = []
+ for d in deps:
+ if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)):
+ raise InterpreterException('Dependencies must be external deps')
+ final_deps.append(d)
+ for l in libs:
+ if isinstance(l, dependencies.Dependency):
+ raise InterpreterException('''Entries in "link_with" may only be self-built targets,
+external dependencies (including libraries) must go to "dependencies".''')
+ dep = dependencies.InternalDependency(version, incs, compile_args,
+ link_args, libs, libs_whole, sources, final_deps,
+ variables)
+ return dep
+
+ @noKwargs
+ def func_assert(self, node, args, kwargs):
+ if len(args) == 1:
+ FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject)
+ value = args[0]
+ message = None
+ elif len(args) == 2:
+ value, message = args
+ if not isinstance(message, str):
+ raise InterpreterException('Assert message not a string.')
+ else:
+ raise InterpreterException('Assert takes between one and two arguments')
+ if not isinstance(value, bool):
+ raise InterpreterException('Assert value not bool.')
+ if not value:
+ if message is None:
+ from ..ast import AstPrinter
+ printer = AstPrinter()
+ node.args.arguments[0].accept(printer)
+ message = printer.result
+ raise InterpreterException('Assert failed: ' + message)
+
+ def validate_arguments(self, args, argcount, arg_types):
+ if argcount is not None:
+ if argcount != len(args):
+ raise InvalidArguments('Expected %d arguments, got %d.' %
+ (argcount, len(args)))
+ for actual, wanted in zip(args, arg_types):
+ if wanted is not None:
+ if not isinstance(actual, wanted):
+ raise InvalidArguments('Incorrect argument type.')
+
+ @FeatureNewKwargs('run_command', '0.50.0', ['env'])
+ @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture'])
+ @permittedKwargs({'check', 'capture', 'env'})
+ def func_run_command(self, node, args, kwargs):
+ return self.run_command_impl(node, args, kwargs)
+
+ def run_command_impl(self,
+ node: mparser.BaseNode,
+ args: T.Sequence[TYPE_nvar],
+ kwargs: TYPE_nkwargs,
+ in_builddir: bool = False) -> RunProcess:
+ if len(args) < 1:
+ raise InterpreterException('Not enough arguments')
+ cmd, *cargs = args
+ capture = kwargs.get('capture', True)
+ srcdir = self.environment.get_source_dir()
+ builddir = self.environment.get_build_dir()
+
+ check = kwargs.get('check', False)
+ if not isinstance(check, bool):
+ raise InterpreterException('Check must be boolean.')
+
+ env = self.unpack_env_kwarg(kwargs)
+
+ m = 'must be a string, or the output of find_program(), files() '\
+ 'or configure_file(), or a compiler object; not {!r}'
+ expanded_args = []
+ if isinstance(cmd, build.Executable):
+ progname = node.args.arguments[0].value
+ msg = 'Program {!r} was overridden with the compiled executable {!r}'\
+ ' and therefore cannot be used during configuration'
+ raise InterpreterException(msg.format(progname, cmd.description()))
+ if isinstance(cmd, ExternalProgram):
+ if not cmd.found():
+ raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable')
+ elif isinstance(cmd, compilers.Compiler):
+ exelist = cmd.get_exelist()
+ cmd = exelist[0]
+ prog = ExternalProgram(cmd, silent=True)
+ if not prog.found():
+ raise InterpreterException(f'Program {cmd!r} not found or not executable')
+ cmd = prog
+ expanded_args = exelist[1:]
+ else:
+ if isinstance(cmd, mesonlib.File):
+ cmd = cmd.absolute_path(srcdir, builddir)
+ elif not isinstance(cmd, str):
+ raise InterpreterException('First argument ' + m.format(cmd))
+ # Prefer scripts in the current source directory
+ search_dir = os.path.join(srcdir, self.subdir)
+ prog = ExternalProgram(cmd, silent=True, search_dir=search_dir)
+ if not prog.found():
+ raise InterpreterException(f'Program or command {cmd!r} not found or not executable')
+ cmd = prog
+ for a in listify(cargs):
+ if isinstance(a, str):
+ expanded_args.append(a)
+ elif isinstance(a, mesonlib.File):
+ expanded_args.append(a.absolute_path(srcdir, builddir))
+ elif isinstance(a, ExternalProgram):
+ expanded_args.append(a.get_path())
+ else:
+ raise InterpreterException('Arguments ' + m.format(a))
+ # If any file that was used as an argument to the command
+ # changes, we must re-run the configuration step.
+ self.add_build_def_file(cmd.get_path())
+ for a in expanded_args:
+ if not os.path.isabs(a):
+ a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a)
+ self.add_build_def_file(a)
+ return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir,
+ self.environment.get_build_command() + ['introspect'],
+ in_builddir=in_builddir, check=check, capture=capture)
+
+ @stringArgs
+ def func_gettext(self, nodes, args, kwargs):
+ raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead')
+
+ def func_option(self, nodes, args, kwargs):
+ raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.')
+
+ @FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
+ @permittedKwargs({'version', 'default_options', 'required'})
+ @stringArgs
+ def func_subproject(self, nodes, args, kwargs):
+ if len(args) != 1:
+ raise InterpreterException('Subproject takes exactly one argument')
+ return self.do_subproject(args[0], 'meson', kwargs)
+
+ def disabled_subproject(self, subp_name, disabled_feature=None, exception=None):
+ sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name),
+ disabled_feature=disabled_feature, exception=exception)
+ self.subprojects[subp_name] = sub
+ self.coredata.initialized_subprojects.add(subp_name)
+ return sub
+
+ def do_subproject(self, subp_name: str, method: str, kwargs):
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
+ if disabled:
+ mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
+ return self.disabled_subproject(subp_name, disabled_feature=feature)
+
+ default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
+ default_options = coredata.create_options_dict(default_options, subp_name)
+
+ if subp_name == '':
+ raise InterpreterException('Subproject name must not be empty.')
+ if subp_name[0] == '.':
+ raise InterpreterException('Subproject name must not start with a period.')
+ if '..' in subp_name:
+ raise InterpreterException('Subproject name must not contain a ".." path segment.')
+ if os.path.isabs(subp_name):
+ raise InterpreterException('Subproject name must not be an absolute path.')
+ if has_path_sep(subp_name):
+ mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.',
+ location=self.current_node)
+ if subp_name in self.subproject_stack:
+ fullstack = self.subproject_stack + [subp_name]
+ incpath = ' => '.join(fullstack)
+ raise InvalidCode('Recursive include of subprojects: %s.' % incpath)
+ if subp_name in self.subprojects:
+ subproject = self.subprojects[subp_name]
+ if required and not subproject.found():
+ raise InterpreterException('Subproject "%s" required but not found.' % (subproject.subdir))
+ return subproject
+
+ r = self.environment.wrap_resolver
+ try:
+ subdir = r.resolve(subp_name, method, self.subproject)
+ except wrap.WrapException as e:
+ if not required:
+ mlog.log(e)
+ mlog.log('Subproject ', mlog.bold(subp_name), 'is buildable:', mlog.red('NO'), '(disabling)')
+ return self.disabled_subproject(subp_name, exception=e)
+ raise e
+
+ subdir_abs = os.path.join(self.environment.get_source_dir(), subdir)
+ os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
+ self.global_args_frozen = True
+
+ stack = ':'.join(self.subproject_stack + [subp_name])
+ m = ['\nExecuting subproject', mlog.bold(stack)]
+ if method != 'meson':
+ m += ['method', mlog.bold(method)]
+ mlog.log(*m,'\n', nested=False)
+
+ try:
+ if method == 'meson':
+ return self._do_subproject_meson(subp_name, subdir, default_options, kwargs)
+ elif method == 'cmake':
+ return self._do_subproject_cmake(subp_name, subdir, subdir_abs, default_options, kwargs)
+ else:
+ raise mesonlib.MesonBugException(f'The method {method} is invalid for the subproject {subp_name}')
+ # Invalid code is always an error
+ except InvalidCode:
+ raise
+ except Exception as e:
+ if not required:
+ with mlog.nested(subp_name):
+ # Suppress the 'ERROR:' prefix because this exception is not
+ # fatal and VS CI treat any logs with "ERROR:" as fatal.
+ mlog.exception(e, prefix=mlog.yellow('Exception:'))
+ mlog.log('\nSubproject', mlog.bold(subdir), 'is buildable:', mlog.red('NO'), '(disabling)')
+ return self.disabled_subproject(subp_name, exception=e)
+ raise e
+
+ def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwargs,
+ ast: T.Optional[mparser.CodeBlockNode] = None,
+ build_def_files: T.Optional[T.List[str]] = None,
+ is_translated: bool = False) -> SubprojectHolder:
+ with mlog.nested(subp_name):
+ new_build = self.build.copy()
+ subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir,
+ default_options, ast=ast, is_translated=is_translated)
+ # Those lists are shared by all interpreters. That means that
+ # even if the subproject fails, any modification that the subproject
+ # made to those lists will affect the parent project.
+ subi.subprojects = self.subprojects
+ subi.modules = self.modules
+ subi.holder_map = self.holder_map
+ subi.bound_holder_map = self.bound_holder_map
+ subi.summary = self.summary
+
+ subi.subproject_stack = self.subproject_stack + [subp_name]
+ current_active = self.active_projectname
+ current_warnings_counter = mlog.log_warnings_counter
+ mlog.log_warnings_counter = 0
+ subi.run()
+ subi_warnings = mlog.log_warnings_counter
+ mlog.log_warnings_counter = current_warnings_counter
+
+ mlog.log('Subproject', mlog.bold(subp_name), 'finished.')
+
+ mlog.log()
+
+ if 'version' in kwargs:
+ pv = subi.project_version
+ wanted = kwargs['version']
+ if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
+ raise InterpreterException(f'Subproject {subp_name} version is {pv} but {wanted} required.')
+ self.active_projectname = current_active
+ self.subprojects.update(subi.subprojects)
+ self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings)
+ # Duplicates are possible when subproject uses files from project root
+ if build_def_files:
+ self.build_def_files = list(set(self.build_def_files + build_def_files))
+ # We always need the subi.build_def_files, to propgate sub-sub-projects
+ self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
+ self.build.merge(subi.build)
+ self.build.subprojects[subp_name] = subi.project_version
+ self.coredata.initialized_subprojects.add(subp_name)
+ return self.subprojects[subp_name]
+
+ def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, kwargs):
+ with mlog.nested(subp_name):
+ new_build = self.build.copy()
+ prefix = self.coredata.options[OptionKey('prefix')].value
+
+ from ..modules.cmake import CMakeSubprojectOptions
+ options = kwargs.get('options', CMakeSubprojectOptions())
+ if not isinstance(options, CMakeSubprojectOptions):
+ raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions'
+ ' object (created by cmake.subproject_options())')
+
+ cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
+ cmake_options += options.cmake_options
+ cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend)
+ cm_int.initialise(cmake_options)
+ cm_int.analyse()
+
+ # Generate a meson ast and execute it with the normal do_subproject_meson
+ ast = cm_int.pretend_to_be_meson(options.target_options)
+
+ mlog.log()
+ with mlog.nested('cmake-ast'):
+ mlog.log('Processing generated meson AST')
+
+ # Debug print the generated meson file
+ from ..ast import AstIndentationGenerator, AstPrinter
+ printer = AstPrinter()
+ ast.accept(AstIndentationGenerator())
+ ast.accept(printer)
+ printer.post_process()
+ meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build')
+ with open(meson_filename, "w", encoding='utf-8') as f:
+ f.write(printer.result)
+
+ mlog.log('Build file:', meson_filename)
+ mlog.cmd_ci_include(meson_filename)
+ mlog.log()
+
+ result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files, is_translated=True)
+ result.cm_interpreter = cm_int
+
+ mlog.log()
+ return result
+
+ def get_option_internal(self, optname: str):
+ key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
+
+ if not key.is_project():
+ for opts in [self.coredata.options, compilers.base_options]:
+ v = opts.get(key)
+ if v is None or v.yielding:
+ v = opts.get(key.as_root())
+ if v is not None:
+ return v
+
+ try:
+ opt = self.coredata.options[key]
+ if opt.yielding and key.subproject and key.as_root() in self.coredata.options:
+ popt = self.coredata.options[key.as_root()]
+ if type(opt) is type(popt):
+ opt = popt
+ else:
+ # Get class name, then option type as a string
+ opt_type = opt.__class__.__name__[4:][:-6].lower()
+ popt_type = popt.__class__.__name__[4:][:-6].lower()
+ # This is not a hard error to avoid dependency hell, the workaround
+ # when this happens is to simply set the subproject's option directly.
+ mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield '
+ 'to parent option of type {3!r}, ignoring parent value. '
+ 'Use -D{2}:{0}=value to set the value for this option manually'
+ '.'.format(optname, opt_type, self.subproject, popt_type),
+ location=self.current_node)
+ return opt
+ except KeyError:
+ pass
+
+ raise InterpreterException('Tried to access unknown option "%s".' % optname)
+
+ @stringArgs
+ @noKwargs
+ def func_get_option(self, nodes, args, kwargs):
+ if len(args) != 1:
+ raise InterpreterException('Argument required for get_option.')
+ optname = args[0]
+ if ':' in optname:
+ raise InterpreterException('Having a colon in option name is forbidden, '
+ 'projects are not allowed to directly access '
+ 'options of other subprojects.')
+ opt = self.get_option_internal(optname)
+ if isinstance(opt, coredata.UserFeatureOption):
+ opt.name = optname
+ return opt
+ elif isinstance(opt, coredata.UserOption):
+ return opt.value
+ return opt
+
+ @noKwargs
+ def func_configuration_data(self, node, args, kwargs):
+ if len(args) > 1:
+ raise InterpreterException('configuration_data takes only one optional positional arguments')
+ elif len(args) == 1:
+ FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject)
+ initial_values = args[0]
+ if not isinstance(initial_values, dict):
+ raise InterpreterException('configuration_data first argument must be a dictionary')
+ else:
+ initial_values = {}
+ return ConfigurationDataObject(self.subproject, initial_values)
+
+ def set_backend(self):
+ # The backend is already set when parsing subprojects
+ if self.backend is not None:
+ return
+ backend = self.coredata.get_option(OptionKey('backend'))
+ from ..backend import backends
+ self.backend = backends.get_backend_from_name(backend, self.build, self)
+
+ if self.backend is None:
+ raise InterpreterException('Unknown backend "%s".' % backend)
+ if backend != self.backend.name:
+ if self.backend.name.startswith('vs'):
+ mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
+ self.coredata.set_option(OptionKey('backend'), self.backend.name)
+
+ # Only init backend options on first invocation otherwise it would
+ # override values previously set from command line.
+ if self.environment.first_invocation:
+ self.coredata.init_backend_options(backend)
+
+ options = {k: v for k, v in self.environment.options.items() if k.is_backend()}
+ self.coredata.set_options(options)
+
+ @stringArgs
+ @permittedKwargs({'version', 'meson_version', 'default_options', 'license', 'subproject_dir'})
+ def func_project(self, node, args, kwargs):
+ if len(args) < 1:
+ raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
+ proj_name, *proj_langs = args
+ if ':' in proj_name:
+ raise InvalidArguments(f"Project name {proj_name!r} must not contain ':'")
+
+ # This needs to be evaluated as early as possible, as meson uses this
+ # for things like deprecation testing.
+ if 'meson_version' in kwargs:
+ cv = coredata.version
+ pv = kwargs['meson_version']
+ if not mesonlib.version_compare(cv, pv):
+ raise InterpreterException(f'Meson version is {cv} but project requires {pv}')
+ mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
+
+ if os.path.exists(self.option_file):
+ oi = optinterpreter.OptionInterpreter(self.subproject)
+ oi.process(self.option_file)
+ self.coredata.update_project_options(oi.options)
+ self.add_build_def_file(self.option_file)
+
+ # Do not set default_options on reconfigure otherwise it would override
+ # values previously set from command line. That means that changing
+ # default_options in a project will trigger a reconfigure but won't
+ # have any effect.
+ self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
+ self.project_default_options = coredata.create_options_dict(self.project_default_options, self.subproject)
+
+ # If this is the first invocation we alway sneed to initialize
+ # builtins, if this is a subproject that is new in a re-invocation we
+ # need to initialize builtins for that
+ if self.environment.first_invocation or (self.subproject != '' and self.subproject not in self.coredata.initialized_subprojects):
+ default_options = self.project_default_options.copy()
+ default_options.update(self.default_project_options)
+ self.coredata.init_builtins(self.subproject)
+ else:
+ default_options = {}
+ self.coredata.set_default_options(default_options, self.subproject, self.environment)
+
+ if not self.is_subproject():
+ self.build.project_name = proj_name
+ self.active_projectname = proj_name
+ version = kwargs.get('version', 'undefined')
+ if isinstance(version, list):
+ if len(version) != 1:
+ raise InvalidCode('Version argument is an array with more than one entry.')
+ version = version[0]
+ if isinstance(version, mesonlib.File):
+ FeatureNew.single_use('version from file', '0.57.0', self.subproject)
+ self.add_build_def_file(version)
+ ifname = version.absolute_path(self.environment.source_dir,
+ self.environment.build_dir)
+ try:
+ ver_data = Path(ifname).read_text(encoding='utf-8').split('\n')
+ except FileNotFoundError:
+ raise InterpreterException('Version file not found.')
+ if len(ver_data) == 2 and ver_data[1] == '':
+ ver_data = ver_data[0:1]
+ if len(ver_data) != 1:
+ raise InterpreterException('Version file must contain exactly one line of text.')
+ self.project_version = ver_data[0]
+ elif isinstance(version, str):
+ self.project_version = version
+ else:
+ raise InvalidCode('The version keyword argument must be a string or a file.')
+ if self.build.project_version is None:
+ self.build.project_version = self.project_version
+ proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown'))
+ self.build.dep_manifest[proj_name] = {'version': self.project_version,
+ 'license': proj_license}
+ if self.subproject in self.build.projects:
+ raise InvalidCode('Second call to project().')
+
+ # spdirname is the subproject_dir for this project, relative to self.subdir.
+ # self.subproject_dir is the subproject_dir for the main project, relative to top source dir.
+ spdirname = kwargs.get('subproject_dir')
+ if spdirname:
+ if not isinstance(spdirname, str):
+ raise InterpreterException('Subproject_dir must be a string')
+ if os.path.isabs(spdirname):
+ raise InterpreterException('Subproject_dir must not be an absolute path.')
+ if spdirname.startswith('.'):
+ raise InterpreterException('Subproject_dir must not begin with a period.')
+ if '..' in spdirname:
+ raise InterpreterException('Subproject_dir must not contain a ".." segment.')
+ if not self.is_subproject():
+ self.subproject_dir = spdirname
+ else:
+ spdirname = 'subprojects'
+ self.build.subproject_dir = self.subproject_dir
+
+ # Load wrap files from this (sub)project.
+ wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
+ if not self.is_subproject() or wrap_mode != WrapMode.nopromote:
+ subdir = os.path.join(self.subdir, spdirname)
+ r = wrap.Resolver(self.environment.get_source_dir(), subdir, wrap_mode)
+ if self.is_subproject():
+ self.environment.wrap_resolver.merge_wraps(r)
+ else:
+ self.environment.wrap_resolver = r
+
+ self.build.projects[self.subproject] = proj_name
+ mlog.log('Project name:', mlog.bold(proj_name))
+ mlog.log('Project version:', mlog.bold(self.project_version))
+
+ self.add_languages(proj_langs, True, MachineChoice.HOST)
+ self.add_languages(proj_langs, False, MachineChoice.BUILD)
+
+ self.set_backend()
+ if not self.is_subproject():
+ self.check_stdlibs()
+
+ @FeatureNewKwargs('add_languages', '0.54.0', ['native'])
+ @permittedKwargs({'required', 'native'})
+ @stringArgs
+ def func_add_languages(self, node, args, kwargs):
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
+ if disabled:
+ for lang in sorted(args, key=compilers.sort_clink):
+ mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return False
+ if 'native' in kwargs:
+ return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs))
+ else:
+ # absent 'native' means 'both' for backwards compatibility
+ tv = FeatureNew.get_target_version(self.subproject)
+ if FeatureNew.check_version(tv, '0.54.0'):
+ mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.',
+ location=self.current_node)
+
+ success = self.add_languages(args, False, MachineChoice.BUILD)
+ success &= self.add_languages(args, required, MachineChoice.HOST)
+ return success
+
+ @noArgsFlattening
+ @noKwargs
+ def func_message(self, node, args, kwargs):
+ if len(args) > 1:
+ FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject)
+ args_str = [stringifyUserArguments(i) for i in args]
+ self.message_impl(args_str)
+
+ def message_impl(self, args):
+ mlog.log(mlog.bold('Message:'), *args)
+
+ @noArgsFlattening
+ @FeatureNewKwargs('summary', '0.54.0', ['list_sep'])
+ @permittedKwargs({'section', 'bool_yn', 'list_sep'})
+ @FeatureNew('summary', '0.53.0')
+ def func_summary(self, node, args, kwargs):
+ if len(args) == 1:
+ if not isinstance(args[0], dict):
+ raise InterpreterException('Summary first argument must be dictionary.')
+ values = args[0]
+ elif len(args) == 2:
+ if not isinstance(args[0], str):
+ raise InterpreterException('Summary first argument must be string.')
+ values = {args[0]: args[1]}
+ else:
+ raise InterpreterException('Summary accepts at most 2 arguments.')
+ section = kwargs.get('section', '')
+ if not isinstance(section, str):
+ raise InterpreterException('Summary\'s section keyword argument must be string.')
+ self.summary_impl(section, values, kwargs)
+
+ def summary_impl(self, section, values, kwargs):
+ if self.subproject not in self.summary:
+ self.summary[self.subproject] = Summary(self.active_projectname, self.project_version)
+ self.summary[self.subproject].add_section(section, values, kwargs, self.subproject)
+
+ def _print_summary(self):
+ # Add automatic 'Supbrojects' section in main project.
+ all_subprojects = collections.OrderedDict()
+ for name, subp in sorted(self.subprojects.items()):
+ value = subp.found()
+ if subp.disabled_feature:
+ value = [value, f'Feature {subp.disabled_feature!r} disabled']
+ elif subp.exception:
+ value = [value, str(subp.exception)]
+ elif subp.warnings > 0:
+ value = [value, f'{subp.warnings} warnings']
+ all_subprojects[name] = value
+ if all_subprojects:
+ self.summary_impl('Subprojects', all_subprojects,
+ {'bool_yn': True,
+ 'list_sep': ' ',
+ })
+ # Print all summaries, main project last.
+ mlog.log('') # newline
+ main_summary = self.summary.pop('', None)
+ for subp_name, summary in sorted(self.summary.items()):
+ if self.subprojects[subp_name].found():
+ summary.dump()
+ if main_summary:
+ main_summary.dump()
+
+ @noArgsFlattening
+ @FeatureNew('warning', '0.44.0')
+ @noKwargs
+ def func_warning(self, node, args, kwargs):
+ if len(args) > 1:
+ FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject)
+ args_str = [stringifyUserArguments(i) for i in args]
+ mlog.warning(*args_str, location=node)
+
+ @noArgsFlattening
+ @noKwargs
+ def func_error(self, node, args, kwargs):
+ if len(args) > 1:
+ FeatureNew.single_use('error with more than one argument', '0.58.0', self.subproject)
+ args_str = [stringifyUserArguments(i) for i in args]
+ raise InterpreterException('Problem encountered: ' + ' '.join(args_str))
+
+ @noKwargs
+ @noPosargs
+ def func_exception(self, node, args, kwargs):
+ raise Exception()
+
+ def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool:
+ success = self.add_languages_for(args, required, for_machine)
+ if not self.coredata.is_cross_build():
+ self.coredata.copy_build_options_from_regular_ones()
+ self._redetect_machines()
+ return success
+
+ def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool:
+ should = self.environment.properties.host.get('skip_sanity_check', False)
+ if not isinstance(should, bool):
+ raise InterpreterException('Option skip_sanity_check must be a boolean.')
+ if for_machine != MachineChoice.HOST and not should:
+ return False
+ if not self.environment.is_cross_build() and not should:
+ return False
+ return should
+
+ def add_languages_for(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> None:
+ args = [a.lower() for a in args]
+ langs = set(self.coredata.compilers[for_machine].keys())
+ langs.update(args)
+ if ('vala' in langs or 'cython' in langs) and 'c' not in langs:
+ if 'vala' in langs:
+ FeatureNew.single_use('Adding Vala language without C', '0.59.0', self.subproject)
+ args.append('c')
+
+ success = True
+ for lang in sorted(args, key=compilers.sort_clink):
+ clist = self.coredata.compilers[for_machine]
+ machine_name = for_machine.get_lower_case_name()
+ if lang in clist:
+ comp = clist[lang]
+ else:
+ try:
+ comp = compilers.detect_compiler_for(self.environment, lang, for_machine)
+ if comp is None:
+ raise InvalidArguments('Tried to use unknown language "%s".' % lang)
+ if self.should_skip_sanity_check(for_machine):
+ mlog.log_once('Cross compiler sanity tests disabled via the cross file.')
+ else:
+ comp.sanity_check(self.environment.get_scratch_dir(), self.environment)
+ except Exception:
+ if not required:
+ mlog.log('Compiler for language',
+ mlog.bold(lang), 'for the', machine_name,
+ 'machine not found.')
+ success = False
+ continue
+ else:
+ raise
+
+ if for_machine == MachineChoice.HOST or self.environment.is_cross_build():
+ logger_fun = mlog.log
+ else:
+ logger_fun = mlog.debug
+ logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:',
+ mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string())
+ if comp.linker is not None:
+ logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:',
+ mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version)
+ self.build.ensure_static_linker(comp)
+
+ return success
+
+ def program_from_file_for(self, for_machine, prognames):
+ for p in prognames:
+ if isinstance(p, mesonlib.File):
+ continue # Always points to a local (i.e. self generated) file.
+ if not isinstance(p, str):
+ raise InterpreterException('Executable name must be a string')
+ prog = ExternalProgram.from_bin_list(self.environment, for_machine, p)
+ if prog.found():
+ return prog
+ return None
+
+ def program_from_system(self, args, search_dirs, extra_info):
+ # Search for scripts relative to current subdir.
+ # Do not cache found programs because find_program('foobar')
+ # might give different results when run from different source dirs.
+ source_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
+ for exename in args:
+ if isinstance(exename, mesonlib.File):
+ if exename.is_built:
+ search_dir = os.path.join(self.environment.get_build_dir(),
+ exename.subdir)
+ else:
+ search_dir = os.path.join(self.environment.get_source_dir(),
+ exename.subdir)
+ exename = exename.fname
+ extra_search_dirs = []
+ elif isinstance(exename, str):
+ search_dir = source_dir
+ extra_search_dirs = search_dirs
+ else:
+ raise InvalidArguments(f'find_program only accepts strings and files, not {exename!r}')
+ extprog = ExternalProgram(exename, search_dir=search_dir,
+ extra_search_dirs=extra_search_dirs,
+ silent=True)
+ if extprog.found():
+ extra_info.append(f"({' '.join(extprog.get_command())})")
+ return extprog
+
+ def program_from_overrides(self, command_names, extra_info):
+ for name in command_names:
+ if not isinstance(name, str):
+ continue
+ if name in self.build.find_overrides:
+ exe = self.build.find_overrides[name]
+ extra_info.append(mlog.blue('(overridden)'))
+ return exe
+ return None
+
+ def store_name_lookups(self, command_names):
+ for name in command_names:
+ if isinstance(name, str):
+ self.build.searched_programs.add(name)
+
+ def add_find_program_override(self, name, exe):
+ if name in self.build.searched_programs:
+ raise InterpreterException(f'Tried to override finding of executable "{name}" which has already been found.')
+ if name in self.build.find_overrides:
+ raise InterpreterException(f'Tried to override executable "{name}" which has already been overridden.')
+ self.build.find_overrides[name] = exe
+
+ def notfound_program(self, args):
+ return NonExistingExternalProgram(' '.join(args))
+
+ # TODO update modules to always pass `for_machine`. It is bad-form to assume
+ # the host machine.
+ def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST,
+ required=True, silent=True, wanted='', search_dirs=None,
+ version_func=None):
+ args = mesonlib.listify(args)
+
+ extra_info = []
+ progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info)
+ if progobj is None:
+ progobj = self.notfound_program(args)
+
+ if isinstance(progobj, ExternalProgram) and not progobj.found():
+ mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'))
+ if required:
+ m = 'Program {!r} not found'
+ raise InterpreterException(m.format(progobj.get_name()))
+ return progobj
+
+ if wanted:
+ if version_func:
+ version = version_func(progobj)
+ elif isinstance(progobj, build.Executable):
+ interp = self
+ if progobj.subproject:
+ interp = self.subprojects[progobj.subproject].held_object
+ assert isinstance(interp, Interpreter)
+ version = interp.project_version
+ elif isinstance(progobj, ExternalProgram):
+ version = progobj.get_version(self)
+ is_found, not_found, found = mesonlib.version_compare_many(version, wanted)
+ if not is_found:
+ mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'),
+ 'found', mlog.normal_cyan(version), 'but need:',
+ mlog.bold(', '.join([f"'{e}'" for e in not_found])), *extra_info)
+ if required:
+ m = 'Invalid version of program, need {!r} {!r} found {!r}.'
+ raise InterpreterException(m.format(progobj.name, not_found, version))
+ return self.notfound_program(args)
+ extra_info.insert(0, mlog.normal_cyan(version))
+
+ # Only store successful lookups
+ self.store_name_lookups(args)
+ mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.green('YES'), *extra_info)
+ if isinstance(progobj, build.Executable):
+ progobj.was_returned_by_find_program = True
+ return progobj
+
+ def program_lookup(self, args, for_machine, required, search_dirs, extra_info):
+ progobj = self.program_from_overrides(args, extra_info)
+ if progobj:
+ return progobj
+
+ fallback = None
+ wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
+ if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver:
+ fallback = self.environment.wrap_resolver.find_program_provider(args)
+ if fallback and wrap_mode == WrapMode.forcefallback:
+ return self.find_program_fallback(fallback, args, required, extra_info)
+
+ progobj = self.program_from_file_for(for_machine, args)
+ if progobj is None:
+ progobj = self.program_from_system(args, search_dirs, extra_info)
+ if progobj is None and args[0].endswith('python3'):
+ prog = ExternalProgram('python3', mesonlib.python_command, silent=True)
+ progobj = prog if prog.found() else None
+ if progobj is None and fallback and required:
+ progobj = self.find_program_fallback(fallback, args, required, extra_info)
+
+ return progobj
+
+ def find_program_fallback(self, fallback, args, required, extra_info):
+ mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program',
+ mlog.bold(' '.join(args)))
+ sp_kwargs = { 'required': required }
+ self.do_subproject(fallback, 'meson', sp_kwargs)
+ return self.program_from_overrides(args, extra_info)
+
+ @FeatureNewKwargs('find_program', '0.53.0', ['dirs'])
+ @FeatureNewKwargs('find_program', '0.52.0', ['version'])
+ @FeatureNewKwargs('find_program', '0.49.0', ['disabler'])
+ @disablerIfNotFound
+ @permittedKwargs({'required', 'native', 'version', 'dirs'})
+ def func_find_program(self, node, args, kwargs):
+ if not args:
+ raise InterpreterException('No program name specified.')
+
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
+ if disabled:
+ mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return self.notfound_program(args)
+
+ search_dirs = extract_search_dirs(kwargs)
+ wanted = mesonlib.stringlistify(kwargs.get('version', []))
+ for_machine = self.machine_from_native_kwarg(kwargs)
+ return self.find_program_impl(args, for_machine, required=required,
+ silent=False, wanted=wanted,
+ search_dirs=search_dirs)
+
+ def func_find_library(self, node, args, kwargs):
+ raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
+ 'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n'
+ 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
+ )
+
+ # When adding kwargs, please check if they make sense in dependencies.get_dep_identifier()
+ @FeatureNewKwargs('dependency', '0.57.0', ['cmake_package_version'])
+ @FeatureNewKwargs('dependency', '0.56.0', ['allow_fallback'])
+ @FeatureNewKwargs('dependency', '0.54.0', ['components'])
+ @FeatureNewKwargs('dependency', '0.52.0', ['include_type'])
+ @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args'])
+ @FeatureNewKwargs('dependency', '0.49.0', ['disabler'])
+ @FeatureNewKwargs('dependency', '0.40.0', ['method'])
+ @FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
+ @disablerIfNotFound
+ @permittedKwargs(permitted_dependency_kwargs)
+ @typed_pos_args('dependency', str)
+ def func_dependency(self, node, args, kwargs):
+ # Replace '' by empty list of names
+ names = [args[0]] if args[0] else []
+ allow_fallback = kwargs.get('allow_fallback')
+ if allow_fallback is not None and not isinstance(allow_fallback, bool):
+ raise InvalidArguments('"allow_fallback" argument must be boolean')
+ fallback = kwargs.get('fallback')
+ default_options = kwargs.get('default_options')
+ df = DependencyFallbacksHolder(self, names, allow_fallback, default_options)
+ df.set_fallback(fallback)
+ not_found_message = kwargs.get('not_found_message', '')
+ if not isinstance(not_found_message, str):
+ raise InvalidArguments('The not_found_message must be a string.')
+ try:
+ d = df.lookup(kwargs)
+ except Exception:
+ if not_found_message:
+ self.message_impl([not_found_message])
+ raise
+ assert isinstance(d, Dependency)
+ if not d.found() and not_found_message:
+ self.message_impl([not_found_message])
+ self.message_impl([not_found_message])
+ # Ensure the correct include type
+ if 'include_type' in kwargs:
+ wanted = kwargs['include_type']
+ if not isinstance(wanted, str):
+ raise InvalidArguments('The `include_type` kwarg must be a string')
+ actual = d.get_include_type()
+ if wanted != actual:
+ mlog.debug(f'Current include type of {args[0]} is {actual}. Converting to requested {wanted}')
+ d = d.generate_system_dependency(wanted)
+ return d
+
+ @FeatureNew('disabler', '0.44.0')
+ @noKwargs
+ @noPosargs
+ def func_disabler(self, node, args, kwargs):
+ return Disabler()
+
+ @FeatureNewKwargs('executable', '0.42.0', ['implib'])
+ @FeatureNewKwargs('executable', '0.56.0', ['win_subsystem'])
+ @FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.")
+ @permittedKwargs(build.known_exe_kwargs)
+ def func_executable(self, node, args, kwargs):
+ return self.build_target(node, args, kwargs, build.Executable)
+
+ @permittedKwargs(build.known_stlib_kwargs)
+ def func_static_lib(self, node, args, kwargs):
+ return self.build_target(node, args, kwargs, build.StaticLibrary)
+
+ @permittedKwargs(build.known_shlib_kwargs)
+ def func_shared_lib(self, node, args, kwargs):
+ holder = self.build_target(node, args, kwargs, build.SharedLibrary)
+ holder.shared_library_only = True
+ return holder
+
+ @permittedKwargs(known_library_kwargs)
+ def func_both_lib(self, node, args, kwargs):
+ return self.build_both_libraries(node, args, kwargs)
+
+ @FeatureNew('shared_module', '0.37.0')
+ @permittedKwargs(build.known_shmod_kwargs)
+ def func_shared_module(self, node, args, kwargs):
+ return self.build_target(node, args, kwargs, build.SharedModule)
+
+ @permittedKwargs(known_library_kwargs)
+ def func_library(self, node, args, kwargs):
+ return self.build_library(node, args, kwargs)
+
+ @permittedKwargs(build.known_jar_kwargs)
+ def func_jar(self, node, args, kwargs):
+ return self.build_target(node, args, kwargs, build.Jar)
+
+ @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
+ @permittedKwargs(known_build_target_kwargs)
+ def func_build_target(self, node, args, kwargs):
+ if 'target_type' not in kwargs:
+ raise InterpreterException('Missing target_type keyword argument')
+ target_type = kwargs.pop('target_type')
+ if target_type == 'executable':
+ return self.build_target(node, args, kwargs, build.Executable)
+ elif target_type == 'shared_library':
+ return self.build_target(node, args, kwargs, build.SharedLibrary)
+ elif target_type == 'shared_module':
+ FeatureNew('build_target(target_type: \'shared_module\')',
+ '0.51.0').use(self.subproject)
+ return self.build_target(node, args, kwargs, build.SharedModule)
+ elif target_type == 'static_library':
+ return self.build_target(node, args, kwargs, build.StaticLibrary)
+ elif target_type == 'both_libraries':
+ return self.build_both_libraries(node, args, kwargs)
+ elif target_type == 'library':
+ return self.build_library(node, args, kwargs)
+ elif target_type == 'jar':
+ return self.build_target(node, args, kwargs, build.Jar)
+ else:
+ raise InterpreterException('Unknown target_type.')
+
+ @permittedKwargs({'input', 'output', 'fallback', 'command', 'replace_string'})
+ @FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'],
+ 'combine build_by_default and build_always_stale instead.')
+ @noPosargs
+ def func_vcs_tag(self, node, args, kwargs):
+ if 'input' not in kwargs or 'output' not in kwargs:
+ raise InterpreterException('Keyword arguments input and output must exist')
+ if 'fallback' not in kwargs:
+ FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject)
+ fallback = kwargs.pop('fallback', self.project_version)
+ if not isinstance(fallback, str):
+ raise InterpreterException('Keyword argument fallback must be a string.')
+ replace_string = kwargs.pop('replace_string', '@VCS_TAG@')
+ regex_selector = '(.*)' # default regex selector for custom command: use complete output
+ vcs_cmd = kwargs.get('command', None)
+ if vcs_cmd and not isinstance(vcs_cmd, list):
+ vcs_cmd = [vcs_cmd]
+ source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir))
+ if vcs_cmd:
+ # Is the command an executable in path or maybe a script in the source tree?
+ vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0])
+ else:
+ vcs = mesonlib.detect_vcs(source_dir)
+ if vcs:
+ mlog.log('Found {} repository at {}'.format(vcs['name'], vcs['wc_dir']))
+ vcs_cmd = vcs['get_rev'].split()
+ regex_selector = vcs['rev_regex']
+ else:
+ vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string
+ # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command...
+ kwargs['command'] = self.environment.get_build_command() + \
+ ['--internal',
+ 'vcstagger',
+ '@INPUT0@',
+ '@OUTPUT0@',
+ fallback,
+ source_dir,
+ replace_string,
+ regex_selector] + vcs_cmd
+ kwargs.setdefault('build_by_default', True)
+ kwargs.setdefault('build_always_stale', True)
+ return self._func_custom_target_impl(node, [kwargs['output']], kwargs)
+
+ @FeatureNew('subdir_done', '0.46.0')
+ @noPosargs
+ @noKwargs
+ def func_subdir_done(self, node, args, kwargs):
+ raise SubdirDoneRequest()
+
+ @stringArgs
+ @FeatureNewKwargs('custom_target', '0.57.0', ['env'])
+ @FeatureNewKwargs('custom_target', '0.48.0', ['console'])
+ @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale'])
+ @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default'])
+ @FeatureNewKwargs('custom_target', '0.59.0', ['feed'])
+ @permittedKwargs({'input', 'output', 'command', 'install', 'install_dir', 'install_mode',
+ 'build_always', 'capture', 'depends', 'depend_files', 'depfile',
+ 'build_by_default', 'build_always_stale', 'console', 'env',
+ 'feed'})
+ def func_custom_target(self, node, args, kwargs):
+ if len(args) != 1:
+ raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
+ if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
+ FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject)
+ return self._func_custom_target_impl(node, args, kwargs)
+
+ def _func_custom_target_impl(self, node, args, kwargs):
+ 'Implementation-only, without FeatureNew checks, for internal use'
+ name = args[0]
+ kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
+ if 'input' in kwargs:
+ try:
+ kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
+ except mesonlib.MesonException:
+ mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s).
+This will become a hard error in the future.''' % kwargs['input'], location=self.current_node)
+ kwargs['env'] = self.unpack_env_kwarg(kwargs)
+ if 'command' in kwargs and isinstance(kwargs['command'], list) and kwargs['command']:
+ if isinstance(kwargs['command'][0], str):
+ kwargs['command'][0] = self.func_find_program(node, kwargs['command'][0], {})
+ tg = build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend)
+ self.add_target(name, tg)
+ return tg
+
+ @FeatureNewKwargs('run_target', '0.57.0', ['env'])
+ @permittedKwargs({'command', 'depends', 'env'})
+ def func_run_target(self, node, args, kwargs):
+ if len(args) > 1:
+ raise InvalidCode('Run_target takes only one positional argument: the target name.')
+ elif len(args) == 1:
+ if 'command' not in kwargs:
+ raise InterpreterException('Missing "command" keyword argument')
+ all_args = extract_as_list(kwargs, 'command')
+ deps = extract_as_list(kwargs, 'depends')
+ else:
+ raise InterpreterException('Run_target needs at least one positional argument.')
+
+ cleaned_args = []
+ for i in listify(all_args):
+ if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, ExternalProgram, mesonlib.File)):
+ mlog.debug('Wrong type:', str(i))
+ raise InterpreterException('Invalid argument to run_target.')
+ if isinstance(i, ExternalProgram) and not i.found():
+ raise InterpreterException(f'Tried to use non-existing executable {i.name!r}')
+ cleaned_args.append(i)
+ if isinstance(cleaned_args[0], str):
+ cleaned_args[0] = self.func_find_program(node, cleaned_args[0], {})
+ name = args[0]
+ if not isinstance(name, str):
+ raise InterpreterException('First argument must be a string.')
+ cleaned_deps = []
+ for d in deps:
+ if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
+ raise InterpreterException('Depends items must be build targets.')
+ cleaned_deps.append(d)
+ env = self.unpack_env_kwarg(kwargs)
+ tg = build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env)
+ self.add_target(name, tg)
+ full_name = (self.subproject, name)
+ assert(full_name not in self.build.run_target_names)
+ self.build.run_target_names.add(full_name)
+ return tg
+
+ @FeatureNew('alias_target', '0.52.0')
+ @noKwargs
+ def func_alias_target(self, node, args, kwargs):
+ if len(args) < 2:
+ raise InvalidCode('alias_target takes at least 2 arguments.')
+ name = args[0]
+ if not isinstance(name, str):
+ raise InterpreterException('First argument must be a string.')
+ deps = listify(args[1:])
+ for d in deps:
+ if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
+ raise InterpreterException('Depends items must be build targets.')
+ tg = build.AliasTarget(name, deps, self.subdir, self.subproject)
+ self.add_target(name, tg)
+ return tg
+
+ @permittedKwargs({'arguments', 'output', 'depends', 'depfile', 'capture',
+ 'preserve_path_from'})
+ @typed_pos_args('generator', (build.Executable, ExternalProgram))
+ @typed_kwargs(
+ 'generator',
+ KwargInfo('arguments', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True),
+ KwargInfo('output', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True),
+ KwargInfo('depfile', str, validator=lambda x: 'Depfile must be a plain filename with a subdirectory' if has_path_sep(x) else None),
+ KwargInfo('capture', bool, default=False, since='0.43.0'),
+ KwargInfo('depends', ContainerTypeInfo(list, (build.BuildTarget, build.CustomTarget)), default=[], listify=True),
+ )
+ def func_generator(self, node: mparser.FunctionNode,
+ args: T.Tuple[T.Union[build.Executable, ExternalProgram]],
+ kwargs: 'kwargs.FuncGenerator') -> build.Generator:
+ for rule in kwargs['output']:
+ if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule:
+ raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.')
+ if has_path_sep(rule):
+ raise InvalidArguments('"output" must not contain a directory separator.')
+ if len(kwargs['output']) > 1:
+ for o in kwargs['output']:
+ if '@OUTPUT@' in o:
+ raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.')
+
+ gen = build.Generator(args[0], **kwargs)
+ self.generators.append(gen)
+ return gen
+
+ @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File))
+ @typed_kwargs('benchmark', *TEST_KWARGS)
+ def func_benchmark(self, node: mparser.BaseNode,
+ args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]],
+ kwargs: 'kwargs.FuncBenchmark') -> None:
+ self.add_test(node, args, kwargs, False)
+
+ @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File))
+ @typed_kwargs('benchmark', *TEST_KWARGS, KwargInfo('is_parallel', bool, default=True))
+ def func_test(self, node: mparser.BaseNode,
+ args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]],
+ kwargs: 'kwargs.FuncTest') -> None:
+ self.add_test(node, args, kwargs, True)
+
+ def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariablesObject, T.Dict[str, str], T.List[str]]) -> build.EnvironmentVariables:
+ envlist = kwargs.get('env', EnvironmentVariablesObject())
+ if isinstance(envlist, EnvironmentVariablesObject):
+ env = envlist.vars
+ elif isinstance(envlist, dict):
+ FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject)
+ env = EnvironmentVariablesObject(envlist)
+ env = env.vars
+ else:
+ # Convert from array to environment object
+ env = EnvironmentVariablesObject(envlist)
+ env = env.vars
+ return env
+
+ def make_test(self, node: mparser.BaseNode,
+ args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]],
+ kwargs: 'kwargs.BaseTest') -> Test:
+ name = args[0]
+ if ':' in name:
+ mlog.deprecation(f'":" is not allowed in test name "{name}", it has been replaced with "_"',
+ location=node)
+ name = name.replace(':', '_')
+ exe = args[1]
+ if isinstance(exe, mesonlib.File):
+ exe = self.func_find_program(node, args[1], {})
+
+ env = self.unpack_env_kwarg(kwargs)
+
+ if kwargs['timeout'] <= 0:
+ FeatureNew.single_use('test() timeout <= 0', '0.57.0', self.subproject)
+
+ prj = self.subproject if self.is_subproject() else self.build.project_name
+
+ suite: T.List[str] = []
+ for s in kwargs['suite']:
+ if s:
+ s = ':' + s
+ suite.append(prj.replace(' ', '_').replace(':', '_') + s)
+
+ return Test(name,
+ prj,
+ suite,
+ exe,
+ kwargs['depends'],
+ kwargs.get('is_parallel', False),
+ kwargs['args'],
+ env,
+ kwargs['should_fail'],
+ kwargs['timeout'],
+ kwargs['workdir'],
+ kwargs['protocol'],
+ kwargs['priority'])
+
+ def add_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any], is_base_test: bool):
+ t = self.make_test(node, args, kwargs)
+ if is_base_test:
+ self.build.tests.append(t)
+ mlog.debug('Adding test', mlog.bold(t.name, True))
+ else:
+ self.build.benchmarks.append(t)
+ mlog.debug('Adding benchmark', mlog.bold(t.name, True))
+
+ @typed_pos_args('install_headers', varargs=(str, mesonlib.File))
+ @typed_kwargs(
+ 'install_headers',
+ KwargInfo('install_dir', (str, None)),
+ KwargInfo('subdir', (str, None)),
+ _INSTALL_MODE_KW.evolve(since='0.47.0'),
+ )
+ def func_install_headers(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallHeaders') -> build.Headers:
+ source_files = self.source_strings_to_files(args[0])
+ install_subdir = kwargs['subdir']
+ if install_subdir is not None and os.path.isabs(install_subdir):
+ mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.')
+
+ h = build.Headers(source_files, install_subdir, kwargs['install_dir'],
+ kwargs['install_mode'], self.subproject)
+ self.build.headers.append(h)
+
+ return h
+
+ @typed_pos_args('install_man', varargs=(str, mesonlib.File))
+ @typed_kwargs(
+ 'install_man',
+ KwargInfo('install_dir', (str, None)),
+ KwargInfo('locale', (str, None), since='0.58.0'),
+ _INSTALL_MODE_KW.evolve(since='0.47.0')
+ )
+ def func_install_man(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallMan') -> build.Man:
+ # We just need to narrow this, because the input is limited to files and
+ # Strings as inputs, so only Files will be returned
+ sources = self.source_strings_to_files(args[0])
+ for s in sources:
+ try:
+ num = int(s.rsplit('.', 1)[-1])
+ except (IndexError, ValueError):
+ num = 0
+ if not 1 <= num <= 9:
+ raise InvalidArguments('Man file must have a file extension of a number between 1 and 9')
+
+ m = build.Man(sources, kwargs['install_dir'], kwargs['install_mode'],
+ self.subproject, kwargs['locale'])
+ self.build.man.append(m)
+
+ return m
+
+ @FeatureNewKwargs('subdir', '0.44.0', ['if_found'])
+ @permittedKwargs({'if_found'})
+ def func_subdir(self, node, args, kwargs):
+ self.validate_arguments(args, 1, [str])
+ mesonlib.check_direntry_issues(args)
+ if '..' in args[0]:
+ raise InvalidArguments('Subdir contains ..')
+ if self.subdir == '' and args[0] == self.subproject_dir:
+ raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.')
+ if self.subdir == '' and args[0].startswith('meson-'):
+ raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().')
+ for i in mesonlib.extract_as_list(kwargs, 'if_found'):
+ if not hasattr(i, 'found'):
+ raise InterpreterException('Object used in if_found does not have a found method.')
+ if not i.found():
+ return
+ prev_subdir = self.subdir
+ subdir = os.path.join(prev_subdir, args[0])
+ if os.path.isabs(subdir):
+ raise InvalidArguments('Subdir argument must be a relative path.')
+ absdir = os.path.join(self.environment.get_source_dir(), subdir)
+ symlinkless_dir = os.path.realpath(absdir)
+ build_file = os.path.join(symlinkless_dir, 'meson.build')
+ if build_file in self.processed_buildfiles:
+ raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'
+ % subdir)
+ self.processed_buildfiles.add(build_file)
+ self.subdir = subdir
+ os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True)
+ buildfilename = os.path.join(self.subdir, environment.build_filename)
+ self.build_def_files.append(buildfilename)
+ absname = os.path.join(self.environment.get_source_dir(), buildfilename)
+ if not os.path.isfile(absname):
+ self.subdir = prev_subdir
+ raise InterpreterException(f"Non-existent build file '{buildfilename!s}'")
+ with open(absname, encoding='utf-8') as f:
+ code = f.read()
+ assert(isinstance(code, str))
+ try:
+ codeblock = mparser.Parser(code, absname).parse()
+ except mesonlib.MesonException as me:
+ me.file = absname
+ raise me
+ try:
+ self.evaluate_codeblock(codeblock)
+ except SubdirDoneRequest:
+ pass
+ self.subdir = prev_subdir
+
+ def _get_kwarg_install_mode(self, kwargs: T.Dict[str, T.Any]) -> T.Optional[FileMode]:
+ if kwargs.get('install_mode', None) is None:
+ return None
+ install_mode: T.List[str] = []
+ mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
+ for m in mode:
+ # We skip any arguments that are set to `false`
+ if m is False:
+ m = None
+ install_mode.append(m)
+ if len(install_mode) > 3:
+ raise InvalidArguments('Keyword argument install_mode takes at '
+ 'most 3 arguments.')
+ if len(install_mode) > 0 and install_mode[0] is not None and \
+ not isinstance(install_mode[0], str):
+ raise InvalidArguments('Keyword argument install_mode requires the '
+ 'permissions arg to be a string or false')
+ return FileMode(*install_mode)
+
+ @typed_pos_args('install_data', varargs=(str, mesonlib.File))
+ @typed_kwargs(
+ 'install_data',
+ KwargInfo('install_dir', str),
+ KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File)), listify=True, default=[]),
+ KwargInfo('rename', ContainerTypeInfo(list, str), default=[], listify=True, since='0.46.0'),
+ _INSTALL_MODE_KW.evolve(since='0.38.0'),
+ )
+ def func_install_data(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List['mesonlib.FileOrString']],
+ kwargs: 'kwargs.FuncInstallData') -> build.Data:
+ sources = self.source_strings_to_files(args[0] + kwargs['sources'])
+ rename = kwargs['rename'] or None
+ if rename:
+ if len(rename) != len(sources):
+ raise InvalidArguments(
+ '"rename" and "sources" argument lists must be the same length if "rename" is given. '
+ f'Rename has {len(rename)} elements and sources has {len(sources)}.')
+
+ data = build.Data(
+ sources, kwargs['install_dir'], kwargs['install_mode'],
+ self.subproject, rename)
+ self.build.data.append(data)
+ return data
+
+ @typed_pos_args('install_subdir', str)
+ @typed_kwargs(
+ 'install_subdir',
+ KwargInfo('install_dir', str, required=True),
+ KwargInfo('strip_directory', bool, default=False),
+ KwargInfo('exclude_files', ContainerTypeInfo(list, str),
+ default=[], listify=True, since='0.42.0',
+ validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
+ KwargInfo('exclude_directories', ContainerTypeInfo(list, str),
+ default=[], listify=True, since='0.42.0',
+ validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
+ _INSTALL_MODE_KW.evolve(since='0.38.0'),
+ )
+ def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str],
+ kwargs: 'kwargs.FuncInstallSubdir') -> build.InstallDir:
+ exclude = (set(kwargs['exclude_files']), set(kwargs['exclude_directories']))
+ idir = build.InstallDir(
+ self.subdir,
+ args[0],
+ kwargs['install_dir'],
+ kwargs['install_mode'],
+ exclude,
+ kwargs['strip_directory'],
+ self.subproject)
+ self.build.install_dirs.append(idir)
+ return idir
+
+ @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding'])
+ @FeatureNewKwargs('configure_file', '0.46.0', ['format'])
+ @FeatureNewKwargs('configure_file', '0.41.0', ['capture'])
+ @FeatureNewKwargs('configure_file', '0.50.0', ['install'])
+ @FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
+ @permittedKwargs({'input', 'output', 'configuration', 'command', 'copy', 'depfile',
+ 'install_dir', 'install_mode', 'capture', 'install', 'format',
+ 'output_format', 'encoding'})
+ @noPosargs
+ def func_configure_file(self, node, args, kwargs):
+ if 'output' not in kwargs:
+ raise InterpreterException('Required keyword argument "output" not defined.')
+ actions = {'configuration', 'command', 'copy'}.intersection(kwargs.keys())
+ if len(actions) == 0:
+ raise InterpreterException('Must specify an action with one of these '
+ 'keyword arguments: \'configuration\', '
+ '\'command\', or \'copy\'.')
+ elif len(actions) == 2:
+ raise InterpreterException('Must not specify both {!r} and {!r} '
+ 'keyword arguments since they are '
+ 'mutually exclusive.'.format(*actions))
+ elif len(actions) == 3:
+ raise InterpreterException('Must specify one of {!r}, {!r}, and '
+ '{!r} keyword arguments since they are '
+ 'mutually exclusive.'.format(*actions))
+ if 'capture' in kwargs:
+ if not isinstance(kwargs['capture'], bool):
+ raise InterpreterException('"capture" keyword must be a boolean.')
+ if 'command' not in kwargs:
+ raise InterpreterException('"capture" keyword requires "command" keyword.')
+
+ if 'format' in kwargs:
+ fmt = kwargs['format']
+ if not isinstance(fmt, str):
+ raise InterpreterException('"format" keyword must be a string.')
+ else:
+ fmt = 'meson'
+
+ if fmt not in ('meson', 'cmake', 'cmake@'):
+ raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".')
+
+ if 'output_format' in kwargs:
+ output_format = kwargs['output_format']
+ if not isinstance(output_format, str):
+ raise InterpreterException('"output_format" keyword must be a string.')
+ else:
+ output_format = 'c'
+
+ if output_format not in ('c', 'nasm'):
+ raise InterpreterException('"format" possible values are "c" or "nasm".')
+
+ if 'depfile' in kwargs:
+ depfile = kwargs['depfile']
+ if not isinstance(depfile, str):
+ raise InterpreterException('depfile file name must be a string')
+ else:
+ depfile = None
+
+ # Validate input
+ inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
+ inputs_abs = []
+ for f in inputs:
+ if isinstance(f, mesonlib.File):
+ inputs_abs.append(f.absolute_path(self.environment.source_dir,
+ self.environment.build_dir))
+ self.add_build_def_file(f)
+ else:
+ raise InterpreterException('Inputs can only be strings or file objects')
+ # Validate output
+ output = kwargs['output']
+ if not isinstance(output, str):
+ raise InterpreterException('Output file name must be a string')
+ if inputs_abs:
+ values = mesonlib.get_filenames_templates_dict(inputs_abs, None)
+ outputs = mesonlib.substitute_values([output], values)
+ output = outputs[0]
+ if depfile:
+ depfile = mesonlib.substitute_values([depfile], values)[0]
+ ofile_rpath = os.path.join(self.subdir, output)
+ if ofile_rpath in self.configure_file_outputs:
+ mesonbuildfile = os.path.join(self.subdir, 'meson.build')
+ current_call = f"{mesonbuildfile}:{self.current_lineno}"
+ first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath])
+ mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call)
+ else:
+ self.configure_file_outputs[ofile_rpath] = self.current_lineno
+ if os.path.dirname(output) != '':
+ raise InterpreterException('Output file name must not contain a subdirectory.')
+ (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
+ ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname)
+ # Perform the appropriate action
+ if 'configuration' in kwargs:
+ conf = kwargs['configuration']
+ if isinstance(conf, dict):
+ FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject)
+ conf = ConfigurationDataObject(self.subproject, conf)
+ elif not isinstance(conf, ConfigurationDataObject):
+ raise InterpreterException('Argument "configuration" is not of type configuration_data')
+ mlog.log('Configuring', mlog.bold(output), 'using configuration')
+ if len(inputs) > 1:
+ raise InterpreterException('At most one input file can given in configuration mode')
+ if inputs:
+ os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
+ file_encoding = kwargs.setdefault('encoding', 'utf-8')
+ missing_variables, confdata_useless = \
+ mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.conf_data,
+ fmt, file_encoding)
+ if missing_variables:
+ var_list = ", ".join(map(repr, sorted(missing_variables)))
+ mlog.warning(
+ "The variable(s) %s in the input file '%s' are not "
+ "present in the given configuration data." % (
+ var_list, inputs[0]), location=node)
+ if confdata_useless:
+ ifbase = os.path.basename(inputs_abs[0])
+ mlog.warning('Got an empty configuration_data() object and found no '
+ f'substitutions in the input file {ifbase!r}. If you want to '
+ 'copy a file to the build dir, use the \'copy:\' keyword '
+ 'argument added in 0.47.0', location=node)
+ else:
+ mesonlib.dump_conf_header(ofile_abs, conf.conf_data, output_format)
+ conf.mark_used()
+ elif 'command' in kwargs:
+ if len(inputs) > 1:
+ FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject)
+ # We use absolute paths for input and output here because the cwd
+ # that the command is run from is 'unspecified', so it could change.
+ # Currently it's builddir/subdir for in_builddir else srcdir/subdir.
+ values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs])
+ if depfile:
+ depfile = os.path.join(self.environment.get_scratch_dir(), depfile)
+ values['@DEPFILE@'] = depfile
+ # Substitute @INPUT@, @OUTPUT@, etc here.
+ cmd = mesonlib.substitute_values(kwargs['command'], values)
+ mlog.log('Configuring', mlog.bold(output), 'with command')
+ res = self.run_command_impl(node, cmd, {}, True)
+ if res.returncode != 0:
+ raise InterpreterException('Running configure command failed.\n%s\n%s' %
+ (res.stdout, res.stderr))
+ if 'capture' in kwargs and kwargs['capture']:
+ dst_tmp = ofile_abs + '~'
+ file_encoding = kwargs.setdefault('encoding', 'utf-8')
+ with open(dst_tmp, 'w', encoding=file_encoding) as f:
+ f.writelines(res.stdout)
+ if inputs_abs:
+ shutil.copymode(inputs_abs[0], dst_tmp)
+ mesonlib.replace_if_different(ofile_abs, dst_tmp)
+ if depfile:
+ mlog.log('Reading depfile:', mlog.bold(depfile))
+ with open(depfile, encoding='utf-8') as f:
+ df = DepFile(f.readlines())
+ deps = df.get_all_dependencies(ofile_fname)
+ for dep in deps:
+ self.add_build_def_file(dep)
+
+ elif 'copy' in kwargs:
+ if len(inputs_abs) != 1:
+ raise InterpreterException('Exactly one input file must be given in copy mode')
+ os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
+ shutil.copy2(inputs_abs[0], ofile_abs)
+ else:
+ # Not reachable
+ raise AssertionError
+ # Install file if requested, we check for the empty string
+ # for backwards compatibility. That was the behaviour before
+ # 0.45.0 so preserve it.
+ idir = kwargs.get('install_dir', '')
+ if idir is False:
+ idir = ''
+ mlog.deprecation('Please use the new `install:` kwarg instead of passing '
+ '`false` to `install_dir:`', location=node)
+ if not isinstance(idir, str):
+ if isinstance(idir, list) and len(idir) == 0:
+ mlog.deprecation('install_dir: kwarg must be a string and not an empty array. '
+ 'Please use the install: kwarg to enable or disable installation. '
+ 'This will be a hard error in the next release.')
+ else:
+ raise InterpreterException('"install_dir" must be a string')
+ install = kwargs.get('install', idir != '')
+ if not isinstance(install, bool):
+ raise InterpreterException('"install" must be a boolean')
+ if install:
+ if not idir:
+ raise InterpreterException('"install_dir" must be specified '
+ 'when "install" in a configure_file '
+ 'is true')
+ cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname)
+ install_mode = self._get_kwarg_install_mode(kwargs)
+ self.build.data.append(build.Data([cfile], idir, install_mode, self.subproject))
+ return mesonlib.File.from_built_file(self.subdir, output)
+
+ def extract_incdirs(self, kwargs):
+ prospectives = extract_as_list(kwargs, 'include_directories')
+ result = []
+ for p in prospectives:
+ if isinstance(p, build.IncludeDirs):
+ result.append(p)
+ elif isinstance(p, str):
+ result.append(self.build_incdir_object([p]))
+ else:
+ raise InterpreterException('Include directory objects can only be created from strings or include directories.')
+ return result
+
+ @permittedKwargs({'is_system'})
+ @stringArgs
+ def func_include_directories(self, node, args, kwargs):
+ return self.build_incdir_object(args, kwargs.get('is_system', False))
+
+ def build_incdir_object(self, incdir_strings: T.List[str], is_system: bool = False) -> build.IncludeDirs:
+ if not isinstance(is_system, bool):
+ raise InvalidArguments('Is_system must be boolean.')
+ src_root = self.environment.get_source_dir()
+ build_root = self.environment.get_build_dir()
+ absbase_src = os.path.join(src_root, self.subdir)
+ absbase_build = os.path.join(build_root, self.subdir)
+
+ for a in incdir_strings:
+ if a.startswith(src_root):
+ raise InvalidArguments(textwrap.dedent('''\
+ Tried to form an absolute path to a source dir.
+ You should not do that but use relative paths instead.
+
+ To get include path to any directory relative to the current dir do
+
+ incdir = include_directories(dirname)
+
+ After this incdir will contain both the current source dir as well as the
+ corresponding build dir. It can then be used in any subdirectory and
+ Meson will take care of all the busywork to make paths work.
+
+ Dirname can even be '.' to mark the current directory. Though you should
+ remember that the current source and build directories are always
+ put in the include directories by default so you only need to do
+ include_directories('.') if you intend to use the result in a
+ different subdirectory.
+ '''))
+ else:
+ try:
+ self.validate_within_subproject(self.subdir, a)
+ except InterpreterException:
+ mlog.warning('include_directories sandbox violation!')
+ print(textwrap.dedent(f'''\
+ The project is trying to access the directory {a} which belongs to a different
+ subproject. This is a problem as it hardcodes the relative paths of these two projeccts.
+ This makes it impossible to compile the project in any other directory layout and also
+ prevents the subproject from changing its own directory layout.
+
+ Instead of poking directly at the internals the subproject should be executed and
+ it should set a variable that the caller can then use. Something like:
+
+ # In subproject
+ some_dep = declare_depencency(include_directories: include_directories('include'))
+
+ # In parent project
+ some_dep = depencency('some')
+ executable(..., dependencies: [some_dep])
+
+ This warning will become a hard error in a future Meson release.
+ '''))
+ absdir_src = os.path.join(absbase_src, a)
+ absdir_build = os.path.join(absbase_build, a)
+ if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build):
+ raise InvalidArguments('Include dir %s does not exist.' % a)
+ i = build.IncludeDirs(self.subdir, incdir_strings, is_system)
+ return i
+
+ @permittedKwargs({'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default',
+ 'exclude_suites'})
+ @stringArgs
+ def func_add_test_setup(self, node, args, kwargs):
+ if len(args) != 1:
+ raise InterpreterException('Add_test_setup needs one argument for the setup name.')
+ setup_name = args[0]
+ if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None:
+ raise InterpreterException('Setup name may only contain alphanumeric characters.')
+ if ":" not in setup_name:
+ setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name
+ try:
+ inp = extract_as_list(kwargs, 'exe_wrapper')
+ exe_wrapper = []
+ for i in inp:
+ if isinstance(i, str):
+ exe_wrapper.append(i)
+ elif isinstance(i, ExternalProgram):
+ if not i.found():
+ raise InterpreterException('Tried to use non-found executable.')
+ exe_wrapper += i.get_command()
+ else:
+ raise InterpreterException('Exe wrapper can only contain strings or external binaries.')
+ except KeyError:
+ exe_wrapper = None
+ gdb = kwargs.get('gdb', False)
+ if not isinstance(gdb, bool):
+ raise InterpreterException('Gdb option must be a boolean')
+ timeout_multiplier = kwargs.get('timeout_multiplier', 1)
+ if not isinstance(timeout_multiplier, int):
+ raise InterpreterException('Timeout multiplier must be a number.')
+ if timeout_multiplier <= 0:
+ FeatureNew('add_test_setup() timeout_multiplier <= 0', '0.57.0').use(self.subproject)
+ is_default = kwargs.get('is_default', False)
+ if not isinstance(is_default, bool):
+ raise InterpreterException('is_default option must be a boolean')
+ if is_default:
+ if self.build.test_setup_default_name is not None:
+ raise InterpreterException('\'%s\' is already set as default. '
+ 'is_default can be set to true only once' % self.build.test_setup_default_name)
+ self.build.test_setup_default_name = setup_name
+ exclude_suites = mesonlib.stringlistify(kwargs.get('exclude_suites', []))
+ env = self.unpack_env_kwarg(kwargs)
+ self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env,
+ exclude_suites)
+
+ @typed_pos_args('add_global_arguments', varargs=str)
+ @typed_kwargs('add_global_arguments', _NATIVE_KW, _LANGUAGE_KW)
+ def func_add_global_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ self._add_global_arguments(node, self.build.global_args[kwargs['native']], args[0], kwargs)
+
+ @typed_pos_args('add_global_link_arguments', varargs=str)
+ @typed_kwargs('add_global_arguments', _NATIVE_KW, _LANGUAGE_KW)
+ def func_add_global_link_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ self._add_global_arguments(node, self.build.global_link_args[kwargs['native']], args[0], kwargs)
+
+ @typed_pos_args('add_project_arguments', varargs=str)
+ @typed_kwargs('add_project_arguments', _NATIVE_KW, _LANGUAGE_KW)
+ def func_add_project_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ self._add_project_arguments(node, self.build.projects_args[kwargs['native']], args[0], kwargs)
+
+ @typed_pos_args('add_project_link_arguments', varargs=str)
+ @typed_kwargs('add_global_arguments', _NATIVE_KW, _LANGUAGE_KW)
+ def func_add_project_link_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ self._add_project_arguments(node, self.build.projects_link_args[kwargs['native']], args[0], kwargs)
+
+ def _warn_about_builtin_args(self, args: T.List[str]) -> None:
+ # -Wpedantic is deliberately not included, since some people want to use it but not use -Wextra
+ # see e.g.
+ # https://github.com/mesonbuild/meson/issues/3275#issuecomment-641354956
+ # https://github.com/mesonbuild/meson/issues/3742
+ warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra')
+ optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os')
+ for arg in args:
+ if arg in warnargs:
+ mlog.warning(f'Consider using the built-in warning_level option instead of using "{arg}".',
+ location=self.current_node)
+ elif arg in optargs:
+ mlog.warning(f'Consider using the built-in optimization level instead of using "{arg}".',
+ location=self.current_node)
+ elif arg == '-Werror':
+ mlog.warning(f'Consider using the built-in werror option instead of using "{arg}".',
+ location=self.current_node)
+ elif arg == '-g':
+ mlog.warning(f'Consider using the built-in debug option instead of using "{arg}".',
+ location=self.current_node)
+ elif arg.startswith('-fsanitize'):
+ mlog.warning(f'Consider using the built-in option for sanitizers instead of using "{arg}".',
+ location=self.current_node)
+ elif arg.startswith('-std=') or arg.startswith('/std:'):
+ mlog.warning(f'Consider using the built-in option for language standard version instead of using "{arg}".',
+ location=self.current_node)
+
+ def _add_global_arguments(self, node: mparser.FunctionNode, argsdict: T.Dict[str, T.List[str]],
+ args: T.List[str], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ if self.is_subproject():
+ msg = f'Function \'{node.func_name}\' cannot be used in subprojects because ' \
+ 'there is no way to make that reliable.\nPlease only call ' \
+ 'this if is_subproject() returns false. Alternatively, ' \
+ 'define a variable that\ncontains your language-specific ' \
+ 'arguments and add it to the appropriate *_args kwarg ' \
+ 'in each target.'
+ raise InvalidCode(msg)
+ frozen = self.project_args_frozen or self.global_args_frozen
+ self._add_arguments(node, argsdict, frozen, args, kwargs)
+
+ def _add_project_arguments(self, node: mparser.FunctionNode, argsdict: T.Dict[str, T.Dict[str, T.List[str]]],
+ args: T.List[str], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ if self.subproject not in argsdict:
+ argsdict[self.subproject] = {}
+ self._add_arguments(node, argsdict[self.subproject],
+ self.project_args_frozen, args, kwargs)
+
+ def _add_arguments(self, node: mparser.FunctionNode, argsdict: T.Dict[str, T.List[str]],
+ args_frozen: bool, args: T.List[str], kwargs: 'kwargs.FuncAddProjectArgs') -> None:
+ if args_frozen:
+ msg = f'Tried to use \'{node.func_name}\' after a build target has been declared.\n' \
+ 'This is not permitted. Please declare all arguments before your targets.'
+ raise InvalidCode(msg)
+
+ self._warn_about_builtin_args(args)
+
+ for lang in kwargs['language']:
+ argsdict[lang] = argsdict.get(lang, []) + args
+
+ @noKwargs
+ @noArgsFlattening
+ def func_environment(self, node, args, kwargs):
+ if len(args) > 1:
+ raise InterpreterException('environment takes only one optional positional arguments')
+ elif len(args) == 1:
+ FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject)
+ initial_values = args[0]
+ if not isinstance(initial_values, dict) and not isinstance(initial_values, list):
+ raise InterpreterException('environment first argument must be a dictionary or a list')
+ else:
+ initial_values = {}
+ return EnvironmentVariablesObject(initial_values, self.subproject)
+
+ @stringArgs
+ @noKwargs
+ def func_join_paths(self, node, args, kwargs):
+ return self.join_path_strings(args)
+
+ def run(self) -> None:
+ super().run()
+ mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
+ FeatureNew.report(self.subproject)
+ FeatureDeprecated.report(self.subproject)
+ if not self.is_subproject():
+ self.print_extra_warnings()
+ if self.subproject == '':
+ self._print_summary()
+
+ def print_extra_warnings(self) -> None:
+ # TODO cross compilation
+ for c in self.coredata.compilers.host.values():
+ if c.get_id() == 'clang':
+ self.check_clang_asan_lundef()
+ break
+
+ def check_clang_asan_lundef(self) -> None:
+ if OptionKey('b_lundef') not in self.coredata.options:
+ return
+ if OptionKey('b_sanitize') not in self.coredata.options:
+ return
+ if (self.coredata.options[OptionKey('b_lundef')].value and
+ self.coredata.options[OptionKey('b_sanitize')].value != 'none'):
+ mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef.
+This will probably not work.
+Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey('b_sanitize')].value),
+ location=self.current_node)
+
+ # Check that the indicated file is within the same subproject
+ # as we currently are. This is to stop people doing
+ # nasty things like:
+ #
+ # f = files('../../master_src/file.c')
+ #
+ # Note that this is validated only when the file
+ # object is generated. The result can be used in a different
+ # subproject than it is defined in (due to e.g. a
+ # declare_dependency).
+ def validate_within_subproject(self, subdir, fname):
+ srcdir = Path(self.environment.source_dir)
+ norm = Path(srcdir, subdir, fname).resolve()
+ if os.path.isdir(norm):
+ inputtype = 'directory'
+ else:
+ inputtype = 'file'
+ if srcdir not in norm.parents:
+ # Grabbing files outside the source tree is ok.
+ # This is for vendor stuff like:
+ #
+ # /opt/vendorsdk/src/file_with_license_restrictions.c
+ return
+ project_root = Path(srcdir, self.root_subdir)
+ if norm == project_root:
+ return
+ if project_root not in norm.parents:
+ raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} outside current (sub)project.')
+ if project_root / self.subproject_dir in norm.parents:
+ raise InterpreterException(f'Sandbox violation: Tried to grab {inputtype} {norm.name} from a nested subproject.')
+
+
+ @T.overload
+ def source_strings_to_files(self, sources: T.List['mesonlib.FileOrString']) -> T.List['mesonlib.File']: ...
+
+ def source_strings_to_files(self, sources: T.List['SourceInputs']) -> T.List['SourceOutputs']:
+ """Lower inputs to a list of Targets and Files, replacing any strings.
+
+ :param sources: A raw (Meson DSL) list of inputs (targets, files, and
+ strings)
+ :raises InterpreterException: if any of the inputs are of an invalid type
+ :return: A list of Targets and Files
+ """
+ mesonlib.check_direntry_issues(sources)
+ if not isinstance(sources, list):
+ sources = [sources]
+ results: T.List['SourceOutputs'] = []
+ for s in sources:
+ if isinstance(s, str):
+ self.validate_within_subproject(self.subdir, s)
+ results.append(mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s))
+ elif isinstance(s, mesonlib.File):
+ results.append(s)
+ elif isinstance(s, (build.GeneratedList, build.BuildTarget,
+ build.CustomTargetIndex, build.CustomTarget,
+ build.GeneratedList)):
+ results.append(s)
+ else:
+ raise InterpreterException(f'Source item is {s!r} instead of '
+ 'string or File-type object')
+ return results
+
+ def add_target(self, name, tobj):
+ if name == '':
+ raise InterpreterException('Target name must not be empty.')
+ if name.strip() == '':
+ raise InterpreterException('Target name must not consist only of whitespace.')
+ if name.startswith('meson-'):
+ raise InvalidArguments("Target names starting with 'meson-' are reserved "
+ "for Meson's internal use. Please rename.")
+ if name in coredata.FORBIDDEN_TARGET_NAMES:
+ raise InvalidArguments("Target name '%s' is reserved for Meson's "
+ "internal use. Please rename." % name)
+ # To permit an executable and a shared library to have the
+ # same name, such as "foo.exe" and "libfoo.a".
+ idname = tobj.get_id()
+ if idname in self.build.targets:
+ raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name)
+ self.build.targets[idname] = tobj
+ if idname not in self.coredata.target_guids:
+ self.coredata.target_guids[idname] = str(uuid.uuid4()).upper()
+
+ @FeatureNew('both_libraries', '0.46.0')
+ def build_both_libraries(self, node, args, kwargs):
+ shared_lib = self.build_target(node, args, kwargs, build.SharedLibrary)
+
+ # Check if user forces non-PIC static library.
+ pic = True
+ key = OptionKey('b_staticpic')
+ if 'pic' in kwargs:
+ pic = kwargs['pic']
+ elif key in self.environment.coredata.options:
+ pic = self.environment.coredata.options[key].value
+
+ if self.backend.name == 'xcode':
+ # Xcode is a bit special in that you can't (at least for the moment)
+ # form a library only from object file inputs. The simple but inefficient
+ # solution is to use the sources directly. This will lead to them being
+ # built twice. This is unfortunate and slow, but at least it works.
+ # Feel free to submit patches to get this fixed if it is an
+ # issue for you.
+ reuse_object_files = False
+ else:
+ reuse_object_files = pic
+
+ if reuse_object_files:
+ # Exclude sources from args and kwargs to avoid building them twice
+ static_args = [args[0]]
+ static_kwargs = kwargs.copy()
+ static_kwargs['sources'] = []
+ static_kwargs['objects'] = shared_lib.extract_all_objects()
+ else:
+ static_args = args
+ static_kwargs = kwargs
+
+ static_lib = self.build_target(node, static_args, static_kwargs, build.StaticLibrary)
+
+ return build.BothLibraries(shared_lib, static_lib)
+
+ def build_library(self, node, args, kwargs):
+ default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject))
+ if default_library == 'shared':
+ return self.build_target(node, args, kwargs, build.SharedLibrary)
+ elif default_library == 'static':
+ return self.build_target(node, args, kwargs, build.StaticLibrary)
+ elif default_library == 'both':
+ return self.build_both_libraries(node, args, kwargs)
+ else:
+ raise InterpreterException('Unknown default_library value: %s.', default_library)
+
+ def build_target(self, node, args, kwargs, targetclass):
+ @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
+ @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
+ @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
+ @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility'])
+ def build_target_decorator_caller(self, node, args, kwargs):
+ return True
+
+ build_target_decorator_caller(self, node, args, kwargs)
+
+ if not args:
+ raise InterpreterException('Target does not have a name.')
+ name, *sources = args
+ for_machine = self.machine_from_native_kwarg(kwargs)
+ if 'sources' in kwargs:
+ sources += listify(kwargs['sources'])
+ sources = self.source_strings_to_files(sources)
+ objs = extract_as_list(kwargs, 'objects')
+ kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
+ kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
+ if 'extra_files' in kwargs:
+ ef = extract_as_list(kwargs, 'extra_files')
+ kwargs['extra_files'] = self.source_strings_to_files(ef)
+ self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources)
+ if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}:
+ mlog.debug('Unknown target type:', str(targetclass))
+ raise RuntimeError('Unreachable code')
+ self.kwarg_strings_to_includedirs(kwargs)
+
+ # Filter out kwargs from other target types. For example 'soversion'
+ # passed to library() when default_library == 'static'.
+ kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
+
+ kwargs['include_directories'] = self.extract_incdirs(kwargs)
+ target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
+ target.project_version = self.project_version
+
+ self.add_stdlib_info(target)
+ self.add_target(name, target)
+ self.project_args_frozen = True
+ return target
+
+ def kwarg_strings_to_includedirs(self, kwargs):
+ if 'd_import_dirs' in kwargs:
+ items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
+ cleaned_items = []
+ for i in items:
+ if isinstance(i, str):
+ # BW compatibility. This was permitted so we must support it
+ # for a few releases so people can transition to "correct"
+ # path declarations.
+ if os.path.normpath(i).startswith(self.environment.get_source_dir()):
+ mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead.
+This will become a hard error in the future.''', location=self.current_node)
+ i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir))
+ i = self.build_incdir_object([i])
+ cleaned_items.append(i)
+ kwargs['d_import_dirs'] = cleaned_items
+
+ def get_used_languages(self, target):
+ result = set()
+ for i in target.sources:
+ for lang, c in self.coredata.compilers[target.for_machine].items():
+ if c.can_compile(i):
+ result.add(lang)
+ break
+ return result
+
+ def add_stdlib_info(self, target):
+ for l in self.get_used_languages(target):
+ dep = self.build.stdlibs[target.for_machine].get(l, None)
+ if dep:
+ target.add_deps(dep)
+
+ def check_sources_exist(self, subdir, sources):
+ for s in sources:
+ if not isinstance(s, str):
+ continue # This means a generated source and they always exist.
+ fname = os.path.join(subdir, s)
+ if not os.path.isfile(fname):
+ raise InterpreterException('Tried to add non-existing source file %s.' % s)
+
+ # Only permit object extraction from the same subproject
+ def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None:
+ if self.subproject != buildtarget.subproject:
+ raise InterpreterException('Tried to extract objects from a different subproject.')
+
+ def is_subproject(self):
+ return self.subproject != ''
+
+ @noKwargs
+ @noArgsFlattening
+ @noSecondLevelHolderResolving
+ def func_set_variable(self, node, args, kwargs):
+ if len(args) != 2:
+ raise InvalidCode('Set_variable takes two arguments.')
+ varname, value = args
+ self.set_variable(varname, value, holderify=True)
+
+ @noKwargs
+ @noArgsFlattening
+ @permissive_unholder_return
+ def func_get_variable(self, node, args, kwargs):
+ if len(args) < 1 or len(args) > 2:
+ raise InvalidCode('Get_variable takes one or two arguments.')
+ varname = args[0]
+ if isinstance(varname, Disabler):
+ return varname
+ if not isinstance(varname, str):
+ raise InterpreterException('First argument must be a string.')
+ try:
+ return self.variables[varname]
+ except KeyError:
+ pass
+ if len(args) == 2:
+ return args[1]
+ raise InterpreterException('Tried to get unknown variable "%s".' % varname)
+
+ @stringArgs
+ @noKwargs
+ def func_is_variable(self, node, args, kwargs):
+ if len(args) != 1:
+ raise InvalidCode('Is_variable takes two arguments.')
+ varname = args[0]
+ return varname in self.variables
+
+ @staticmethod
+ def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice:
+ native = kwargs.get('native', False)
+ if not isinstance(native, bool):
+ raise InvalidArguments('Argument to "native" must be a boolean.')
+ return MachineChoice.BUILD if native else MachineChoice.HOST
+
+ @FeatureNew('is_disabler', '0.52.0')
+ @noKwargs
+ def func_is_disabler(self, node, args, kwargs):
+ if len(args) != 1:
+ raise InvalidCode('Is_disabler takes one argument.')
+ varname = args[0]
+ return isinstance(varname, Disabler)
+
+ @noKwargs
+ @FeatureNew('range', '0.58.0')
+ @typed_pos_args('range', int, optargs=[int, int])
+ def func_range(self, node, args: T.Tuple[int, T.Optional[int], T.Optional[int]], kwargs: T.Dict[str, T.Any]) -> RangeHolder:
+ start, stop, step = args
+ # Just like Python's range, we allow range(stop), range(start, stop), or
+ # range(start, stop, step)
+ if stop is None:
+ stop = start
+ start = 0
+ if step is None:
+ step = 1
+ # This is more strict than Python's range()
+ if start < 0:
+ raise InterpreterException('start cannot be negative')
+ if stop < start:
+ raise InterpreterException('stop cannot be less than start')
+ if step < 1:
+ raise InterpreterException('step must be >=1')
+ return RangeHolder(start, stop, step, subproject=self.subproject)
diff --git a/meson/mesonbuild/interpreter/interpreterobjects.py b/meson/mesonbuild/interpreter/interpreterobjects.py
new file mode 100644
index 000000000..5dc65d03f
--- /dev/null
+++ b/meson/mesonbuild/interpreter/interpreterobjects.py
@@ -0,0 +1,996 @@
+import os
+import shlex
+import subprocess
+import copy
+import textwrap
+
+from pathlib import Path, PurePath
+
+from .. import mesonlib
+from .. import coredata
+from .. import build
+from .. import mlog
+
+from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule
+from ..backend.backends import TestProtocol
+from ..interpreterbase import (
+ ContainerTypeInfo, KwargInfo,
+ InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject,
+ FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
+ typed_pos_args, typed_kwargs, stringArgs, permittedKwargs,
+ noArgsFlattening, noPosargs, noKwargs, permissive_unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs,
+ flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
+from ..dependencies import Dependency, ExternalLibrary, InternalDependency
+from ..programs import ExternalProgram
+from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe
+
+import typing as T
+
+if T.TYPE_CHECKING:
+ from . import kwargs
+ from .interpreter import Interpreter
+ from ..environment import Environment
+ from ..envconfig import MachineInfo
+
+
+def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired',
+ subproject: str,
+ feature_check: T.Optional[FeatureCheckBase] = None,
+ default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]:
+ val = kwargs.get('required', default)
+ disabled = False
+ required = False
+ feature: T.Optional[str] = None
+ if isinstance(val, coredata.UserFeatureOption):
+ if not feature_check:
+ feature_check = FeatureNew('User option "feature"', '0.47.0')
+ feature_check.use(subproject)
+ feature = val.name
+ if val.is_disabled():
+ disabled = True
+ elif val.is_enabled():
+ required = True
+ elif isinstance(val, bool):
+ required = val
+ else:
+ raise InterpreterException('required keyword argument must be boolean or a feature option')
+
+ # Keep boolean value in kwargs to simplify other places where this kwarg is
+ # checked.
+ # TODO: this should be removed, and those callers should learn about FeatureOptions
+ kwargs['required'] = required
+
+ return disabled, required, feature
+
+def extract_search_dirs(kwargs: T.Dict[str, T.Any]) -> T.List[str]:
+ search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', []))
+ search_dirs = [Path(d).expanduser() for d in search_dirs_str]
+ for d in search_dirs:
+ if mesonlib.is_windows() and d.root.startswith('\\'):
+ # a Unix-path starting with `/` that is not absolute on Windows.
+ # discard without failing for end-user ease of cross-platform directory arrays
+ continue
+ if not d.is_absolute():
+ raise InvalidCode(f'Search directory {d} is not an absolute path.')
+ return list(map(str, search_dirs))
+
+class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]):
+ def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'):
+ super().__init__(option, interpreter)
+ if option and option.is_auto():
+ # TODO: we need to case here because options is not a TypedDict
+ self.held_object = T.cast(coredata.UserFeatureOption, self.env.coredata.options[OptionKey('auto_features')])
+ self.held_object.name = option.name
+ self.methods.update({'enabled': self.enabled_method,
+ 'disabled': self.disabled_method,
+ 'allowed': self.allowed_method,
+ 'auto': self.auto_method,
+ 'require': self.require_method,
+ 'disable_auto_if': self.disable_auto_if_method,
+ })
+
+ @property
+ def value(self) -> str:
+ return 'disabled' if not self.held_object else self.held_object.value
+
+ def as_disabled(self) -> coredata.UserFeatureOption:
+ disabled = copy.deepcopy(self.held_object)
+ disabled.value = 'disabled'
+ return disabled
+
+ @noPosargs
+ @noKwargs
+ def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.value == 'enabled'
+
+ @noPosargs
+ @noKwargs
+ def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.value == 'disabled'
+
+ @noPosargs
+ @noKwargs
+ def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return not self.value == 'disabled'
+
+ @noPosargs
+ @noKwargs
+ def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.value == 'auto'
+
+ @permittedKwargs({'error_message'})
+ def require_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption:
+ if len(args) != 1:
+ raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), ))
+ if not isinstance(args[0], bool):
+ raise InvalidArguments('boolean argument expected.')
+ error_message = kwargs.pop('error_message', '')
+ if error_message and not isinstance(error_message, str):
+ raise InterpreterException("Error message must be a string.")
+ if args[0]:
+ return copy.deepcopy(self.held_object)
+
+ assert isinstance(error_message, str)
+ if self.value == 'enabled':
+ prefix = f'Feature {self.held_object.name} cannot be enabled'
+ prefix = prefix + ': ' if error_message else ''
+ raise InterpreterException(prefix + error_message)
+ return self.as_disabled()
+
+ @noKwargs
+ def disable_auto_if_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption:
+ if len(args) != 1:
+ raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), ))
+ if not isinstance(args[0], bool):
+ raise InvalidArguments('boolean argument expected.')
+ return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled()
+
+
+class RunProcess(MesonInterpreterObject):
+
+ def __init__(self,
+ cmd: ExternalProgram,
+ args: T.List[str],
+ env: build.EnvironmentVariables,
+ source_dir: str,
+ build_dir: str,
+ subdir: str,
+ mesonintrospect: T.List[str],
+ in_builddir: bool = False,
+ check: bool = False,
+ capture: bool = True) -> None:
+ super().__init__()
+ if not isinstance(cmd, ExternalProgram):
+ raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
+ self.capture = capture
+ self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
+ self.methods.update({'returncode': self.returncode_method,
+ 'stdout': self.stdout_method,
+ 'stderr': self.stderr_method,
+ })
+
+ def run_command(self,
+ cmd: ExternalProgram,
+ args: T.List[str],
+ env: build.EnvironmentVariables,
+ source_dir: str,
+ build_dir: str,
+ subdir: str,
+ mesonintrospect: T.List[str],
+ in_builddir: bool,
+ check: bool = False) -> T.Tuple[int, str, str]:
+ command_array = cmd.get_command() + args
+ menv = {'MESON_SOURCE_ROOT': source_dir,
+ 'MESON_BUILD_ROOT': build_dir,
+ 'MESON_SUBDIR': subdir,
+ 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]),
+ }
+ if in_builddir:
+ cwd = os.path.join(build_dir, subdir)
+ else:
+ cwd = os.path.join(source_dir, subdir)
+ child_env = os.environ.copy()
+ child_env.update(menv)
+ child_env = env.get_env(child_env)
+ stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
+ mlog.debug('Running command:', ' '.join(command_array))
+ try:
+ p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
+ if self.capture:
+ mlog.debug('--- stdout ---')
+ mlog.debug(o)
+ else:
+ o = ''
+ mlog.debug('--- stdout disabled ---')
+ mlog.debug('--- stderr ---')
+ mlog.debug(e)
+ mlog.debug('')
+
+ if check and p.returncode != 0:
+ raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
+
+ return p.returncode, o, e
+ except FileNotFoundError:
+ raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
+
+ @noPosargs
+ @noKwargs
+ def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
+ return self.returncode
+
+ @noPosargs
+ @noKwargs
+ def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.stdout
+
+ @noPosargs
+ @noKwargs
+ def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.stderr
+
+# TODO: Parsing the initial values should be either done directly in the
+# `Interpreter` or in `build.EnvironmentVariables`. This way, this class
+# can be converted into a pure object holder.
+class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObject):
+ # TODO: Move the type cheking for initial_values out of this class and replace T.Any
+ def __init__(self, initial_values: T.Optional[T.Any] = None, subproject: str = ''):
+ super().__init__(subproject=subproject)
+ self.vars = build.EnvironmentVariables()
+ self.methods.update({'set': self.set_method,
+ 'append': self.append_method,
+ 'prepend': self.prepend_method,
+ })
+ if isinstance(initial_values, dict):
+ for k, v in initial_values.items():
+ self.set_method([k, v], {})
+ elif initial_values is not None:
+ for e in mesonlib.listify(initial_values):
+ if not isinstance(e, str):
+ raise InterpreterException('Env var definition must be a list of strings.')
+ if '=' not in e:
+ raise InterpreterException('Env var definition must be of type key=val.')
+ (k, val) = e.split('=', 1)
+ k = k.strip()
+ val = val.strip()
+ if ' ' in k:
+ raise InterpreterException('Env var key must not have spaces in it.')
+ self.set_method([k, val], {})
+
+ def __repr__(self) -> str:
+ repr_str = "<{0}: {1}>"
+ return repr_str.format(self.__class__.__name__, self.vars.envvars)
+
+ def unpack_separator(self, kwargs: T.Dict[str, T.Any]) -> str:
+ separator = kwargs.get('separator', os.pathsep)
+ if not isinstance(separator, str):
+ raise InterpreterException("EnvironmentVariablesObject methods 'separator'"
+ " argument needs to be a string.")
+ return separator
+
+ def warn_if_has_name(self, name: str) -> None:
+ # Multiple append/prepend operations was not supported until 0.58.0.
+ if self.vars.has_name(name):
+ m = f'Overriding previous value of environment variable {name!r} with a new one'
+ FeatureNew('0.58.0', m).use(self.subproject)
+
+ @stringArgs
+ @permittedKwargs({'separator'})
+ @typed_pos_args('environment.set', str, varargs=str, min_varargs=1)
+ def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None:
+ name, values = args
+ separator = self.unpack_separator(kwargs)
+ self.vars.set(name, values, separator)
+
+ @stringArgs
+ @permittedKwargs({'separator'})
+ @typed_pos_args('environment.append', str, varargs=str, min_varargs=1)
+ def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None:
+ name, values = args
+ separator = self.unpack_separator(kwargs)
+ self.warn_if_has_name(name)
+ self.vars.append(name, values, separator)
+
+ @stringArgs
+ @permittedKwargs({'separator'})
+ @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1)
+ def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None:
+ name, values = args
+ separator = self.unpack_separator(kwargs)
+ self.warn_if_has_name(name)
+ self.vars.prepend(name, values, separator)
+
+
+class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject):
+ def __init__(self, subproject: str, initial_values: T.Optional[T.Dict[str, T.Any]] = None) -> None:
+ self.used = False # These objects become immutable after use in configure_file.
+ super().__init__(subproject=subproject)
+ self.conf_data = build.ConfigurationData()
+ self.methods.update({'set': self.set_method,
+ 'set10': self.set10_method,
+ 'set_quoted': self.set_quoted_method,
+ 'has': self.has_method,
+ 'get': self.get_method,
+ 'keys': self.keys_method,
+ 'get_unquoted': self.get_unquoted_method,
+ 'merge_from': self.merge_from_method,
+ })
+ if isinstance(initial_values, dict):
+ for k, v in initial_values.items():
+ self.set_method([k, v], {})
+ elif initial_values:
+ raise AssertionError('Unsupported ConfigurationDataObject initial_values')
+
+ def is_used(self) -> bool:
+ return self.used
+
+ def mark_used(self) -> None:
+ self.used = True
+
+ def validate_args(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[str, T.Union[str, int, bool], T.Optional[str]]:
+ if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
+ mlog.deprecation('Passing a list as the single argument to '
+ 'configuration_data.set is deprecated. This will '
+ 'become a hard error in the future.',
+ location=self.current_node)
+ args = args[0]
+
+ if len(args) != 2:
+ raise InterpreterException("Configuration set requires 2 arguments.")
+ if self.used:
+ raise InterpreterException("Can not set values on configuration object that has been used.")
+ name, val = args
+ if not isinstance(val, (int, str)):
+ msg = f'Setting a configuration data value to {val!r} is invalid, ' \
+ 'and will fail at configure_file(). If you are using it ' \
+ 'just to store some values, please use a dict instead.'
+ mlog.deprecation(msg, location=self.current_node)
+ desc = kwargs.get('description', None)
+ if not isinstance(name, str):
+ raise InterpreterException("First argument to set must be a string.")
+ if desc is not None and not isinstance(desc, str):
+ raise InterpreterException('Description must be a string.')
+
+ # TODO: Remove the cast once we get rid of the deprecation
+ return name, T.cast(T.Union[str, bool, int], val), desc
+
+ @noArgsFlattening
+ def set_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
+ (name, val, desc) = self.validate_args(args, kwargs)
+ self.conf_data.values[name] = (val, desc)
+
+ def set_quoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
+ (name, val, desc) = self.validate_args(args, kwargs)
+ if not isinstance(val, str):
+ raise InterpreterException("Second argument to set_quoted must be a string.")
+ escaped_val = '\\"'.join(val.split('"'))
+ self.conf_data.values[name] = ('"' + escaped_val + '"', desc)
+
+ def set10_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
+ (name, val, desc) = self.validate_args(args, kwargs)
+ if val:
+ self.conf_data.values[name] = (1, desc)
+ else:
+ self.conf_data.values[name] = (0, desc)
+
+ def has_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return args[0] in self.conf_data.values
+
+ @FeatureNew('configuration_data.get()', '0.38.0')
+ @noArgsFlattening
+ def get_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]:
+ if len(args) < 1 or len(args) > 2:
+ raise InterpreterException('Get method takes one or two arguments.')
+ if not isinstance(args[0], str):
+ raise InterpreterException('The variable name must be a string.')
+ name = args[0]
+ if name in self.conf_data:
+ return self.conf_data.get(name)[0]
+ if len(args) > 1:
+ # Assertion does not work because setting other values is still
+ # supported, but deprecated. Use T.cast in the meantime (even though
+ # this is a lie).
+ # TODO: Fix this once the deprecation is removed
+ # assert isinstance(args[1], (int, str, bool))
+ return T.cast(T.Union[str, int, bool], args[1])
+ raise InterpreterException('Entry %s not in configuration data.' % name)
+
+ @FeatureNew('configuration_data.get_unquoted()', '0.44.0')
+ def get_unquoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]:
+ if len(args) < 1 or len(args) > 2:
+ raise InterpreterException('Get method takes one or two arguments.')
+ if not isinstance(args[0], str):
+ raise InterpreterException('The variable name must be a string.')
+ name = args[0]
+ if name in self.conf_data:
+ val = self.conf_data.get(name)[0]
+ elif len(args) > 1:
+ assert isinstance(args[1], (str, int, bool))
+ val = args[1]
+ else:
+ raise InterpreterException('Entry %s not in configuration data.' % name)
+ if isinstance(val, str) and val[0] == '"' and val[-1] == '"':
+ return val[1:-1]
+ return val
+
+ def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]:
+ return self.conf_data.values[name]
+
+ @FeatureNew('configuration_data.keys()', '0.57.0')
+ @noPosargs
+ def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]:
+ return sorted(self.keys())
+
+ def keys(self) -> T.List[str]:
+ return list(self.conf_data.values.keys())
+
+ def merge_from_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
+ if len(args) != 1:
+ raise InterpreterException('Merge_from takes one positional argument.')
+ from_object_holder = args[0]
+ if not isinstance(from_object_holder, ConfigurationDataObject):
+ raise InterpreterException('Merge_from argument must be a configuration data object.')
+ from_object = from_object_holder.conf_data
+ for k, v in from_object.values.items():
+ self.conf_data.values[k] = v
+
+
+_PARTIAL_DEP_KWARGS = [
+ KwargInfo('compile_args', bool, default=False),
+ KwargInfo('link_args', bool, default=False),
+ KwargInfo('links', bool, default=False),
+ KwargInfo('includes', bool, default=False),
+ KwargInfo('sources', bool, default=False),
+]
+
+class DependencyHolder(ObjectHolder[Dependency]):
+ def __init__(self, dep: Dependency, interpreter: 'Interpreter'):
+ super().__init__(dep, interpreter)
+ self.methods.update({'found': self.found_method,
+ 'type_name': self.type_name_method,
+ 'version': self.version_method,
+ 'name': self.name_method,
+ 'get_pkgconfig_variable': self.pkgconfig_method,
+ 'get_configtool_variable': self.configtool_method,
+ 'get_variable': self.variable_method,
+ 'partial_dependency': self.partial_dependency_method,
+ 'include_type': self.include_type_method,
+ 'as_system': self.as_system_method,
+ 'as_link_whole': self.as_link_whole_method,
+ })
+
+ def found(self) -> bool:
+ return self.found_method([], {})
+
+ @noPosargs
+ @noKwargs
+ def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.type_name
+
+ @noPosargs
+ @noKwargs
+ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ if self.held_object.type_name == 'internal':
+ return True
+ return self.held_object.found()
+
+ @noPosargs
+ @noKwargs
+ def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.get_version()
+
+ @noPosargs
+ @noKwargs
+ def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.get_name()
+
+ @FeatureDeprecated('Dependency.get_pkgconfig_variable', '0.56.0',
+ 'use Dependency.get_variable(pkgconfig : ...) instead')
+ @permittedKwargs({'define_variable', 'default'})
+ def pkgconfig_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ args = listify(args)
+ if len(args) != 1:
+ raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
+ varname = args[0]
+ if not isinstance(varname, str):
+ raise InterpreterException('Variable name must be a string.')
+ return self.held_object.get_pkgconfig_variable(varname, kwargs)
+
+ @FeatureNew('dep.get_configtool_variable', '0.44.0')
+ @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0',
+ 'use Dependency.get_variable(configtool : ...) instead')
+ @noKwargs
+ def configtool_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ args = listify(args)
+ if len(args) != 1:
+ raise InterpreterException('get_configtool_variable takes exactly one argument.')
+ varname = args[0]
+ if not isinstance(varname, str):
+ raise InterpreterException('Variable name must be a string.')
+ return self.held_object.get_configtool_variable(varname)
+
+ @FeatureNew('dep.partial_dependency', '0.46.0')
+ @noPosargs
+ @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS)
+ def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency:
+ pdep = self.held_object.get_partial_dependency(**kwargs)
+ return pdep
+
+ @FeatureNew('dep.get_variable', '0.51.0')
+ @typed_pos_args('dep.get_variable', optargs=[str])
+ @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'})
+ @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal'])
+ def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Union[str, T.List[str]]:
+ default_varname = args[0]
+ if default_varname is not None:
+ FeatureNew('0.58.0', 'Positional argument to dep.get_variable()').use(self.subproject)
+ for k in ['cmake', 'pkgconfig', 'configtool', 'internal']:
+ kwargs.setdefault(k, default_varname)
+ return self.held_object.get_variable(**kwargs)
+
+ @FeatureNew('dep.include_type', '0.52.0')
+ @noPosargs
+ @noKwargs
+ def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.get_include_type()
+
+ @FeatureNew('dep.as_system', '0.52.0')
+ @noKwargs
+ def as_system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency:
+ args = listify(args)
+ new_is_system = 'system'
+ if len(args) > 1:
+ raise InterpreterException('as_system takes only one optional value')
+ if len(args) == 1:
+ if not isinstance(args[0], str):
+ raise InterpreterException('as_system takes exactly one string parameter')
+ new_is_system = args[0]
+ new_dep = self.held_object.generate_system_dependency(new_is_system)
+ return new_dep
+
+ @FeatureNew('dep.as_link_whole', '0.56.0')
+ @noKwargs
+ @noPosargs
+ def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency:
+ if not isinstance(self.held_object, InternalDependency):
+ raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects')
+ new_dep = self.held_object.generate_link_whole_dependency()
+ return new_dep
+
+class ExternalProgramHolder(ObjectHolder[ExternalProgram]):
+ def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None:
+ super().__init__(ep, interpreter)
+ self.methods.update({'found': self.found_method,
+ 'path': self.path_method,
+ 'full_path': self.full_path_method})
+
+ @noPosargs
+ @noKwargs
+ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.found()
+
+ @noPosargs
+ @noKwargs
+ @FeatureDeprecated('ExternalProgram.path', '0.55.0',
+ 'use ExternalProgram.full_path() instead')
+ def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self._full_path()
+
+ @noPosargs
+ @noKwargs
+ @FeatureNew('ExternalProgram.full_path', '0.55.0')
+ def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self._full_path()
+
+ def _full_path(self) -> str:
+ if not self.found():
+ raise InterpreterException('Unable to get the path of a not-found external program')
+ path = self.held_object.get_path()
+ assert path is not None
+ return path
+
+ def found(self) -> bool:
+ return self.held_object.found()
+
+class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]):
+ def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'):
+ super().__init__(el, interpreter)
+ self.methods.update({'found': self.found_method,
+ 'type_name': self.type_name_method,
+ 'partial_dependency': self.partial_dependency_method,
+ })
+
+ @noPosargs
+ @noKwargs
+ def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.type_name
+
+ @noPosargs
+ @noKwargs
+ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.held_object.found()
+
+ @FeatureNew('dep.partial_dependency', '0.46.0')
+ @noPosargs
+ @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS)
+ def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency:
+ pdep = self.held_object.get_partial_dependency(**kwargs)
+ return pdep
+
+# A machine that's statically known from the cross file
+class MachineHolder(ObjectHolder['MachineInfo']):
+ def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'):
+ super().__init__(machine_info, interpreter)
+ self.methods.update({'system': self.system_method,
+ 'cpu': self.cpu_method,
+ 'cpu_family': self.cpu_family_method,
+ 'endian': self.endian_method,
+ })
+
+ @noPosargs
+ @noKwargs
+ def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.cpu_family
+
+ @noPosargs
+ @noKwargs
+ def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.cpu
+
+ @noPosargs
+ @noKwargs
+ def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.system
+
+ @noPosargs
+ @noKwargs
+ def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.held_object.endian
+
+class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]):
+ pass
+
+class FileHolder(ObjectHolder[mesonlib.File]):
+ pass
+
+class HeadersHolder(ObjectHolder[build.Headers]):
+ pass
+
+class DataHolder(ObjectHolder[build.Data]):
+ pass
+
+class InstallDirHolder(ObjectHolder[build.InstallDir]):
+ pass
+
+class ManHolder(ObjectHolder[build.Man]):
+ pass
+
+class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]):
+ pass
+
+class Test(MesonInterpreterObject):
+ def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable,
+ depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
+ is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables,
+ should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
+ priority: int):
+ super().__init__()
+ self.name = name
+ self.suite = listify(suite)
+ self.project_name = project
+ self.exe = exe
+ self.depends = depends
+ self.is_parallel = is_parallel
+ self.cmd_args = cmd_args
+ self.env = env
+ self.should_fail = should_fail
+ self.timeout = timeout
+ self.workdir = workdir
+ self.protocol = TestProtocol.from_str(protocol)
+ self.priority = priority
+
+ def get_exe(self) -> build.Executable:
+ return self.exe
+
+ def get_name(self) -> str:
+ return self.name
+
+class NullSubprojectInterpreter(HoldableObject):
+ pass
+
+# TODO: This should really be an `ObjectHolder`, but the additional stuff in this
+# class prevents this. Thus, this class should be split into a pure
+# `ObjectHolder` and a class specifically for stroing in `Interpreter`.
+class SubprojectHolder(MesonInterpreterObject):
+
+ def __init__(self, subinterpreter: T.Union['Interpreter', NullSubprojectInterpreter],
+ subdir: str,
+ warnings: int = 0,
+ disabled_feature: T.Optional[str] = None,
+ exception: T.Optional[MesonException] = None) -> None:
+ super().__init__()
+ self.held_object = subinterpreter
+ self.warnings = warnings
+ self.disabled_feature = disabled_feature
+ self.exception = exception
+ self.subdir = PurePath(subdir).as_posix()
+ self.methods.update({'get_variable': self.get_variable_method,
+ 'found': self.found_method,
+ })
+
+ @noPosargs
+ @noKwargs
+ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ return self.found()
+
+ def found(self) -> bool:
+ return not isinstance(self.held_object, NullSubprojectInterpreter)
+
+ @noKwargs
+ @noArgsFlattening
+ @permissive_unholder_return
+ def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]:
+ if len(args) < 1 or len(args) > 2:
+ raise InterpreterException('Get_variable takes one or two arguments.')
+ if isinstance(self.held_object, NullSubprojectInterpreter): # == not self.found()
+ raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir))
+ varname = args[0]
+ if not isinstance(varname, str):
+ raise InterpreterException('Get_variable first argument must be a string.')
+ try:
+ return self.held_object.variables[varname]
+ except KeyError:
+ pass
+
+ if len(args) == 2:
+ return args[1]
+
+ raise InvalidArguments(f'Requested variable "{varname}" not found.')
+
+class ModuleObjectHolder(ObjectHolder[ModuleObject]):
+ def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var:
+ modobj = self.held_object
+ method = modobj.methods.get(method_name)
+ if not method:
+ raise InvalidCode(f'Unknown method {method_name!r} in object.')
+ if not getattr(method, 'no-args-flattening', False):
+ args = flatten(args)
+ if not getattr(method, 'no-second-level-holder-flattening', False):
+ args, kwargs = resolve_second_level_holders(args, kwargs)
+ state = ModuleState(self.interpreter)
+ # Many modules do for example self.interpreter.find_program_impl(),
+ # so we have to ensure they use the current interpreter and not the one
+ # that first imported that module, otherwise it will use outdated
+ # overrides.
+ if isinstance(modobj, ExtensionModule):
+ modobj.interpreter = self.interpreter
+ ret = method(state, args, kwargs)
+ if isinstance(ret, ModuleReturnValue):
+ self.interpreter.process_new_values(ret.new_objects)
+ ret = ret.return_value
+ return ret
+
+class MutableModuleObjectHolder(ModuleObjectHolder, MutableInterpreterObject):
+ def __deepcopy__(self, memo: T.Dict[int, T.Any]) -> 'MutableModuleObjectHolder':
+ # Deepcopy only held object, not interpreter
+ modobj = copy.deepcopy(self.held_object, memo)
+ return MutableModuleObjectHolder(modobj, self.interpreter)
+
+
+_BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build.BothLibraries])
+
+class BuildTargetHolder(ObjectHolder[_BuildTarget]):
+ def __init__(self, target: _BuildTarget, interp: 'Interpreter'):
+ super().__init__(target, interp)
+ self.methods.update({'extract_objects': self.extract_objects_method,
+ 'extract_all_objects': self.extract_all_objects_method,
+ 'name': self.name_method,
+ 'get_id': self.get_id_method,
+ 'outdir': self.outdir_method,
+ 'full_path': self.full_path_method,
+ 'path': self.path_method,
+ 'found': self.found_method,
+ 'private_dir_include': self.private_dir_include_method,
+ })
+
+ def __repr__(self) -> str:
+ r = '<{} {}: {}>'
+ h = self.held_object
+ return r.format(self.__class__.__name__, h.get_id(), h.filename)
+
+ @property
+ def _target_object(self) -> build.BuildTarget:
+ if isinstance(self.held_object, build.BothLibraries):
+ return self.held_object.get_default_object()
+ assert isinstance(self.held_object, build.BuildTarget)
+ return self.held_object
+
+ def is_cross(self) -> bool:
+ return not self._target_object.environment.machines.matches_build_machine(self._target_object.for_machine)
+
+ @noPosargs
+ @noKwargs
+ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
+ if not (isinstance(self.held_object, build.Executable) and self.held_object.was_returned_by_find_program):
+ FeatureNew.single_use('BuildTarget.found', '0.59.0', subproject=self.held_object.subproject)
+ return True
+
+ @noPosargs
+ @noKwargs
+ def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs:
+ return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)])
+
+ @noPosargs
+ @noKwargs
+ def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.interpreter.backend.get_target_filename_abs(self._target_object)
+
+ @noPosargs
+ @noKwargs
+ @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead')
+ def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.interpreter.backend.get_target_filename_abs(self._target_object)
+
+ @noPosargs
+ @noKwargs
+ def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.interpreter.backend.get_target_dir(self._target_object)
+
+ @noKwargs
+ @typed_pos_args('extract_objects', varargs=(mesonlib.File, str))
+ def extract_objects_method(self, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects:
+ return self._target_object.extract_objects(args[0])
+
+ @noPosargs
+ @typed_kwargs(
+ 'extract_all_objects',
+ KwargInfo(
+ 'recursive', bool, default=False, since='0.46.0',
+ not_set_warning=textwrap.dedent('''\
+ extract_all_objects called without setting recursive
+ keyword argument. Meson currently defaults to
+ non-recursive to maintain backward compatibility but
+ the default will be changed in the future.
+ ''')
+ )
+ )
+ def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects:
+ return self._target_object.extract_all_objects(kwargs['recursive'])
+
+ @noPosargs
+ @noKwargs
+ def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self._target_object.get_id()
+
+ @FeatureNew('name', '0.54.0')
+ @noPosargs
+ @noKwargs
+ def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self._target_object.name
+
+class ExecutableHolder(BuildTargetHolder[build.Executable]):
+ pass
+
+class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]):
+ pass
+
+class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]):
+ pass
+
+class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]):
+ def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'):
+ # FIXME: This build target always represents the shared library, but
+ # that should be configurable.
+ super().__init__(libs, interp)
+ self.methods.update({'get_shared_lib': self.get_shared_lib_method,
+ 'get_static_lib': self.get_static_lib_method,
+ })
+
+ def __repr__(self) -> str:
+ r = '<{} {}: {}, {}: {}>'
+ h1 = self.held_object.shared
+ h2 = self.held_object.static
+ return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
+
+ @noPosargs
+ @noKwargs
+ def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary:
+ return self.held_object.shared
+
+ @noPosargs
+ @noKwargs
+ def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary:
+ return self.held_object.static
+
+class SharedModuleHolder(BuildTargetHolder[build.SharedModule]):
+ pass
+
+class JarHolder(BuildTargetHolder[build.Jar]):
+ pass
+
+class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]):
+ def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'):
+ super().__init__(target, interp)
+ self.methods.update({'full_path': self.full_path_method,
+ })
+
+ @FeatureNew('custom_target[i].full_path', '0.54.0')
+ @noPosargs
+ @noKwargs
+ def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ assert self.interpreter.backend is not None
+ return self.interpreter.backend.get_target_filename_abs(self.held_object)
+
+class CustomTargetHolder(ObjectHolder[build.CustomTarget]):
+ def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'):
+ super().__init__(target, interp)
+ self.methods.update({'full_path': self.full_path_method,
+ 'to_list': self.to_list_method,
+ })
+
+ def __repr__(self) -> str:
+ r = '<{} {}: {}>'
+ h = self.held_object
+ return r.format(self.__class__.__name__, h.get_id(), h.command)
+
+ @noPosargs
+ @noKwargs
+ def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
+ return self.interpreter.backend.get_target_filename_abs(self.held_object)
+
+ @FeatureNew('custom_target.to_list', '0.54.0')
+ @noPosargs
+ @noKwargs
+ def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]:
+ result = []
+ for i in self.held_object:
+ result.append(i)
+ return result
+
+ def __getitem__(self, index: int) -> build.CustomTargetIndex:
+ return self.held_object[index]
+
+ def __setitem__(self, index: int, value: T.Any) -> None: # lgtm[py/unexpected-raise-in-special-method]
+ raise InterpreterException('Cannot set a member of a CustomTarget')
+
+ def __delitem__(self, index: int) -> None: # lgtm[py/unexpected-raise-in-special-method]
+ raise InterpreterException('Cannot delete a member of a CustomTarget')
+
+class RunTargetHolder(ObjectHolder[build.RunTarget]):
+ pass
+
+class AliasTargetHolder(ObjectHolder[build.AliasTarget]):
+ pass
+
+class GeneratedListHolder(ObjectHolder[build.GeneratedList]):
+ pass
+
+class GeneratorHolder(ObjectHolder[build.Generator]):
+ def __init__(self, gen: build.Generator, interpreter: 'Interpreter'):
+ super().__init__(gen, interpreter)
+ self.methods.update({'process': self.process_method})
+
+ @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList))
+ @typed_kwargs(
+ 'generator.process',
+ KwargInfo('preserve_path_from', str, since='0.45.0'),
+ KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]),
+ )
+ def process_method(self,
+ args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]],
+ kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList:
+ preserve_path_from = kwargs['preserve_path_from']
+ if preserve_path_from is not None:
+ preserve_path_from = os.path.normpath(preserve_path_from)
+ if not os.path.isabs(preserve_path_from):
+ # This is a bit of a hack. Fix properly before merging.
+ raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
+
+ if any(isinstance(a, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for a in args[0]):
+ FeatureNew.single_use(
+ f'Calling generator.process with CustomTaget or Index of CustomTarget.',
+ '0.57.0', self.interpreter.subproject)
+
+ gl = self.held_object.process_files(args[0], self.interpreter,
+ preserve_path_from, extra_args=kwargs['extra_args'])
+
+ return gl
diff --git a/meson/mesonbuild/interpreter/kwargs.py b/meson/mesonbuild/interpreter/kwargs.py
new file mode 100644
index 000000000..b92b66fd7
--- /dev/null
+++ b/meson/mesonbuild/interpreter/kwargs.py
@@ -0,0 +1,139 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2021 The Meson Developers
+# Copyright © 2021 Intel Corporation
+
+"""Keyword Argument type annotations."""
+
+import typing as T
+
+from typing_extensions import TypedDict, Literal
+
+from .. import build
+from .. import coredata
+from ..mesonlib import MachineChoice, File, FileMode, FileOrString
+from .interpreterobjects import EnvironmentVariablesObject
+
+
+class FuncAddProjectArgs(TypedDict):
+
+ """Keyword Arguments for the add_*_arguments family of arguments.
+
+ including `add_global_arguments`, `add_project_arguments`, and their
+ link variants
+
+ Because of the use of a convertor function, we get the native keyword as
+ a MachineChoice instance already.
+ """
+
+ native: MachineChoice
+ language: T.List[str]
+
+
+class BaseTest(TypedDict):
+
+ """Shared base for the Rust module."""
+
+ args: T.List[T.Union[str, File, build.Target]]
+ should_fail: bool
+ timeout: int
+ workdir: T.Optional[str]
+ depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]]
+ priority: int
+ env: T.Union[EnvironmentVariablesObject, T.List[str], T.Dict[str, str], str]
+ suite: T.List[str]
+
+
+class FuncBenchmark(BaseTest):
+
+ """Keyword Arguments shared between `test` and `benchmark`."""
+
+ protocol: Literal['exitcode', 'tap', 'gtest', 'rust']
+
+
+class FuncTest(FuncBenchmark):
+
+ """Keyword Arguments for `test`
+
+ `test` only adds the `is_prallel` argument over benchmark, so inherintance
+ is helpful here.
+ """
+
+ is_parallel: bool
+
+
+class ExtractRequired(TypedDict):
+
+ """Keyword Arguments consumed by the `extract_required_kwargs` function.
+
+ Any function that uses the `required` keyword argument which accepts either
+ a boolean or a feature option should inherit it's arguments from this class.
+ """
+
+ required: T.Union[bool, coredata.UserFeatureOption]
+
+
+class FuncGenerator(TypedDict):
+
+ """Keyword rguments for the generator function."""
+
+ arguments: T.List[str]
+ output: T.List[str]
+ depfile: bool
+ capture: bool
+ depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]]
+
+
+class GeneratorProcess(TypedDict):
+
+ """Keyword Arguments for generator.process."""
+
+ preserve_path_from: T.Optional[str]
+ extra_args: T.List[str]
+
+class DependencyMethodPartialDependency(TypedDict):
+
+ """ Keyword Arguments for the dep.partial_dependency methods """
+
+ compile_args: bool
+ link_args: bool
+ links: bool
+ includes: bool
+ sources: bool
+
+class BuildTargeMethodExtractAllObjects(TypedDict):
+ recursive: bool
+
+class FuncInstallSubdir(TypedDict):
+
+ install_dir: str
+ strip_directory: bool
+ exclude_files: T.List[str]
+ exclude_directories: T.List[str]
+ install_mode: FileMode
+
+
+class FuncInstallData(TypedDict):
+
+ install_dir: str
+ sources: T.List[FileOrString]
+ rename: T.List[str]
+ install_mode: FileMode
+
+
+class FuncInstallHeaders(TypedDict):
+
+ install_dir: T.Optional[str]
+ install_mode: FileMode
+ subdir: T.Optional[str]
+
+
+class FuncInstallMan(TypedDict):
+
+ install_dir: T.Optional[str]
+ install_mode: FileMode
+ locale: T.Optional[str]
+
+
+class FuncImportModule(ExtractRequired):
+
+ disabler: bool
diff --git a/meson/mesonbuild/interpreter/mesonmain.py b/meson/mesonbuild/interpreter/mesonmain.py
new file mode 100644
index 000000000..97a695b9d
--- /dev/null
+++ b/meson/mesonbuild/interpreter/mesonmain.py
@@ -0,0 +1,382 @@
+import os
+
+from .. import mesonlib
+from .. import dependencies
+from .. import build
+from .. import mlog
+
+from ..mesonlib import MachineChoice, OptionKey
+from ..programs import OverrideProgram, ExternalProgram
+from ..interpreterbase import (MesonInterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
+ typed_pos_args, permittedKwargs, noArgsFlattening, noPosargs, noKwargs,
+ MesonVersionString, InterpreterException)
+
+from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder,
+ CustomTargetHolder, CustomTargetIndexHolder,
+ EnvironmentVariablesObject)
+
+import typing as T
+
+if T.TYPE_CHECKING:
+ from .interpreter import Interpreter
+
+class MesonMain(MesonInterpreterObject):
+ def __init__(self, build: 'build.Build', interpreter: 'Interpreter'):
+ super().__init__(subproject=interpreter.subproject)
+ 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,
+ 'global_source_root': self.global_source_root_method,
+ 'global_build_root': self.global_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, build.Executable, ExternalProgram], args):
+
+ if isinstance(prog, (build.Executable, ExternalProgram)):
+ return self.interpreter.backend.get_executable_serialisation([prog] + args)
+ found = self.interpreter.func_find_program({}, prog, {})
+ 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:
+ 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(
+ f'Arguments to {name} must be strings, Files, or CustomTargets, '
+ 'Indexes of CustomTargets')
+ if new:
+ FeatureNew.single_use(
+ f'Calling "{name}" with File, CustomTaget, Index of CustomTarget, '
+ 'Executable, or ExternalProgram',
+ '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.project_source_root() or meson.global_source_root() 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.project_build_root() or meson.global_build_root() 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
+ @noKwargs
+ @FeatureNew('meson.global_source_root', '0.58.0')
+ def global_source_root_method(self, args, kwargs):
+ return self.interpreter.environment.source_dir
+
+ @noPosargs
+ @noKwargs
+ @FeatureNew('meson.global_build_root', '0.58.0')
+ def global_build_root_method(self, args, kwargs):
+ return self.interpreter.environment.build_dir
+
+ @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 clist[cname]
+ 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')
+ 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')
+ 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, EnvironmentVariablesObject))
+ def add_devenv_method(self, args: T.Union[str, list, dict, EnvironmentVariablesObject], kwargs: T.Dict[str, T.Any]) -> None:
+ env = args[0]
+ if isinstance(env, (str, list, dict)):
+ env = EnvironmentVariablesObject(env)
+ self.build.devenv.append(env.vars)