aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/interpreter/interpreter.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/interpreter/interpreter.py')
-rw-r--r--mesonbuild/interpreter/interpreter.py152
1 files changed, 91 insertions, 61 deletions
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 0bace86..9f12719 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -33,7 +33,7 @@ from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCod
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
from ..interpreterbase import ObjectHolder
-from ..interpreterbase.baseobjects import TYPE_var, TYPE_kwargs
+from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
from ..cmake import CMakeInterpreter
from ..backend.backends import Backend, ExecutableSerialisation
@@ -93,6 +93,8 @@ import importlib
if T.TYPE_CHECKING:
import argparse
+ from typing_extensions import Literal
+
from . import kwargs
from ..programs import OverrideProgram
@@ -115,7 +117,7 @@ def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None])
return None
-def stringifyUserArguments(args, quote=False):
+def stringifyUserArguments(args: T.List[T.Any], quote: bool = False) -> str:
if isinstance(args, list):
return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args])
elif isinstance(args, dict):
@@ -258,7 +260,7 @@ class Interpreter(InterpreterBase, HoldableObject):
subproject: str = '',
subdir: str = '',
subproject_dir: str = 'subprojects',
- default_project_options: T.Optional[T.Dict[str, str]] = None,
+ default_project_options: T.Optional[T.Dict[OptionKey, str]] = None,
mock: bool = False,
ast: T.Optional[mparser.CodeBlockNode] = None,
is_translated: bool = False,
@@ -271,7 +273,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.coredata = self.environment.get_coredata()
self.backend = backend
self.summary: T.Dict[str, 'Summary'] = {}
- self.modules = {}
+ self.modules: T.Dict[str, NewExtensionModule] = {}
# 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]
@@ -289,14 +291,14 @@ class Interpreter(InterpreterBase, HoldableObject):
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 = {}
+ self.subproject_stack: T.List[str] = []
+ self.configure_file_outputs: T.Dict[str, int] = {}
# 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.project_default_options: T.Dict[OptionKey, str] = {}
self.build_func_dict()
self.build_holder_map()
self.user_defined_options = user_defined_options
@@ -306,10 +308,10 @@ class Interpreter(InterpreterBase, HoldableObject):
# 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 = []
+ self.build_def_files: mesonlib.OrderedSet[str] = mesonlib.OrderedSet()
build_filename = os.path.join(self.subdir, environment.build_filename)
if not is_translated:
- self.build_def_files.append(build_filename)
+ self.build_def_files.add(build_filename)
if not mock:
self.parse_project()
self._redetect_machines()
@@ -317,7 +319,7 @@ class Interpreter(InterpreterBase, HoldableObject):
def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]:
raise MesonBugException('This class is unpicklable')
- def _redetect_machines(self):
+ def _redetect_machines(self) -> None:
# 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.
@@ -335,7 +337,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.builtin['target_machine'] = \
OBJ.MachineHolder(self.build.environment.machines.target, self)
- def build_func_dict(self):
+ def build_func_dict(self) -> None:
self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
'add_global_link_arguments': self.func_add_global_link_arguments,
'add_languages': self.func_add_languages,
@@ -501,7 +503,7 @@ class Interpreter(InterpreterBase, HoldableObject):
else:
raise InterpreterException(f'Module returned a value of unknown type {v!r}.')
- def get_build_def_files(self) -> T.List[str]:
+ def get_build_def_files(self) -> mesonlib.OrderedSet[str]:
return self.build_def_files
def add_build_def_file(self, f: mesonlib.FileOrString) -> None:
@@ -517,32 +519,32 @@ class Interpreter(InterpreterBase, HoldableObject):
srcdir = Path(self.environment.get_source_dir())
builddir = Path(self.environment.get_build_dir())
try:
- f = Path(f).resolve()
+ f_ = Path(f).resolve()
except OSError:
- f = Path(f)
- s = f.stat()
+ 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
+ f_ = f_.parent.resolve() / f_.name
else:
raise
- if builddir in f.parents:
+ if builddir in f_.parents:
return
- if srcdir in f.parents:
- f = f.relative_to(srcdir)
- f = str(f)
+ 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)
+ self.build_def_files.add(f)
- def get_variables(self):
+ def get_variables(self) -> T.Dict[str, InterpreterObject]:
return self.variables
- def check_stdlibs(self):
+ def check_stdlibs(self) -> None:
machine_choices = [MachineChoice.HOST]
if self.coredata.is_cross_build():
machine_choices.append(MachineChoice.BUILD)
@@ -563,7 +565,7 @@ class Interpreter(InterpreterBase, HoldableObject):
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]:
+ def _import_module(self, modname: str, required: bool) -> NewExtensionModule:
if modname in self.modules:
return self.modules[modname]
try:
@@ -797,24 +799,37 @@ external dependencies (including libraries) must go to "dependencies".''')
@FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
@permittedKwargs({'version', 'default_options', 'required'})
@typed_pos_args('subproject', str)
- def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> SubprojectHolder:
- return self.do_subproject(args[0], 'meson', kwargs)
-
- def disabled_subproject(self, subp_name, disabled_feature=None, exception=None):
+ @typed_kwargs(
+ 'subproject',
+ REQUIRED_KW,
+ DEFAULT_OPTIONS.evolve(since='0.38.0'),
+ KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True),
+ )
+ def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs_: kwargs.Subproject) -> SubprojectHolder:
+ kw: kwargs.DoSubproject = {
+ 'required': kwargs_['required'],
+ 'default_options': kwargs_['default_options'],
+ 'version': kwargs_['version'],
+ 'options': None,
+ 'cmake_options': [],
+ }
+ return self.do_subproject(args[0], 'meson', kw)
+
+ def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None,
+ exception: T.Optional[Exception] = None) -> SubprojectHolder:
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):
+ def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs: kwargs.DoSubproject) -> SubprojectHolder:
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)
+ default_options = coredata.create_options_dict(kwargs['default_options'], subp_name)
if subp_name == '':
raise InterpreterException('Subproject name must not be empty.')
@@ -835,7 +850,7 @@ external dependencies (including libraries) must go to "dependencies".''')
subproject = self.subprojects[subp_name]
if required and not subproject.found():
raise InterpreterException(f'Subproject "{subproject.subdir}" required but not found.')
- if 'version' in kwargs:
+ if kwargs['version']:
pv = self.build.subprojects[subp_name]
wanted = kwargs['version']
if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
@@ -882,7 +897,9 @@ external dependencies (including libraries) must go to "dependencies".''')
return self.disabled_subproject(subp_name, exception=e)
raise e
- def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwargs,
+ def _do_subproject_meson(self, subp_name: str, subdir: str,
+ default_options: T.Dict[OptionKey, str],
+ kwargs: kwargs.DoSubproject,
ast: T.Optional[mparser.CodeBlockNode] = None,
build_def_files: T.Optional[T.List[str]] = None,
is_translated: bool = False) -> SubprojectHolder:
@@ -911,7 +928,7 @@ external dependencies (including libraries) must go to "dependencies".''')
mlog.log()
- if 'version' in kwargs:
+ if kwargs['version']:
pv = subi.project_version
wanted = kwargs['version']
if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
@@ -921,27 +938,24 @@ external dependencies (including libraries) must go to "dependencies".''')
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))
+ self.build_def_files.update(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_def_files.update(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):
+ def _do_subproject_cmake(self, subp_name: str, subdir: str, subdir_abs: str,
+ default_options: T.Dict[OptionKey, str],
+ kwargs: kwargs.DoSubproject) -> SubprojectHolder:
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
+ options = kwargs['options'] or CMakeSubprojectOptions()
+ cmake_options = kwargs['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()
@@ -967,13 +981,13 @@ external dependencies (including libraries) must go to "dependencies".''')
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 = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, [str(f) for f in cm_int.bs_files], is_translated=True)
result.cm_interpreter = cm_int
mlog.log()
return result
- def get_option_internal(self, optname: str):
+ def get_option_internal(self, optname: str) -> coredata.UserOption:
key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
if not key.is_project():
@@ -982,6 +996,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if v is None or v.yielding:
v = opts.get(key.as_root())
if v is not None:
+ assert isinstance(v, coredata.UserOption), 'for mypy'
return v
try:
@@ -1037,7 +1052,7 @@ external dependencies (including libraries) must go to "dependencies".''')
f'"configuration_data": initial value dictionary key "{k!r}"" must be "str | int | bool", not "{v!r}"')
return build.ConfigurationData(initial_values)
- def set_backend(self):
+ def set_backend(self) -> None:
# The backend is already set when parsing subprojects
if self.backend is not None:
return
@@ -1326,7 +1341,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return False
return should
- def add_languages_for(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> None:
+ def add_languages_for(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> bool:
args = [a.lower() for a in args]
langs = set(self.coredata.compilers[for_machine].keys())
langs.update(args)
@@ -1378,7 +1393,8 @@ external dependencies (including libraries) must go to "dependencies".''')
return success
- def program_from_file_for(self, for_machine, prognames):
+ def program_from_file_for(self, for_machine: MachineChoice, prognames: T.List[mesonlib.FileOrString]
+ ) -> T.Optional[ExternalProgram]:
for p in prognames:
if isinstance(p, mesonlib.File):
continue # Always points to a local (i.e. self generated) file.
@@ -1389,7 +1405,8 @@ external dependencies (including libraries) must go to "dependencies".''')
return prog
return None
- def program_from_system(self, args, search_dirs, extra_info):
+ def program_from_system(self, args: T.List[mesonlib.FileOrString], search_dirs: T.List[str],
+ extra_info: T.List[mlog.TV_Loggable]) -> T.Optional[ExternalProgram]:
# 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.
@@ -1415,9 +1432,11 @@ external dependencies (including libraries) must go to "dependencies".''')
if extprog.found():
extra_info.append(f"({' '.join(extprog.get_command())})")
return extprog
+ return None
- def program_from_overrides(self, command_names: T.List[str], extra_info: T.List['mlog.TV_Loggable']) -> \
- T.Optional[T.Union[ExternalProgram, 'OverrideProgram', 'build.Executable']]:
+ def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString],
+ extra_info: T.List['mlog.TV_Loggable']
+ ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.Executable]]:
for name in command_names:
if not isinstance(name, str):
continue
@@ -1427,7 +1446,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return exe
return None
- def store_name_lookups(self, command_names):
+ def store_name_lookups(self, command_names: T.List[mesonlib.FileOrString]) -> None:
for name in command_names:
if isinstance(name, str):
self.build.searched_programs.add(name)
@@ -1455,7 +1474,7 @@ external dependencies (including libraries) must go to "dependencies".''')
) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']:
args = mesonlib.listify(args)
- extra_info = []
+ extra_info: T.List[mlog.TV_Loggable] = []
progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info)
if progobj is None:
progobj = self.notfound_program(args)
@@ -1472,14 +1491,15 @@ external dependencies (including libraries) must go to "dependencies".''')
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)
+ else:
+ interp = self
+ assert isinstance(interp, Interpreter)
version = interp.project_version
else:
version = progobj.get_version(self)
- is_found, not_found, found = mesonlib.version_compare_many(version, wanted)
+ is_found, not_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:',
@@ -1498,7 +1518,9 @@ external dependencies (including libraries) must go to "dependencies".''')
progobj.was_returned_by_find_program = True
return progobj
- def program_lookup(self, args, for_machine, required, search_dirs, extra_info):
+ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice,
+ required: bool, search_dirs: T.List[str], extra_info: T.List[mlog.TV_Loggable]
+ ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]:
progobj = self.program_from_overrides(args, extra_info)
if progobj:
return progobj
@@ -1521,10 +1543,18 @@ external dependencies (including libraries) must go to "dependencies".''')
return progobj
- def find_program_fallback(self, fallback, args, required, extra_info):
+ def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString],
+ required: bool, extra_info: T.List[mlog.TV_Loggable]
+ ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]:
mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program',
mlog.bold(' '.join(args)))
- sp_kwargs = {'required': required}
+ sp_kwargs: kwargs.DoSubproject = {
+ 'required': required,
+ 'default_options': [],
+ 'version': [],
+ 'cmake_options': [],
+ 'options': None,
+ }
self.do_subproject(fallback, 'meson', sp_kwargs)
return self.program_from_overrides(args, extra_info)
@@ -2115,7 +2145,7 @@ external dependencies (including libraries) must go to "dependencies".''')
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)
+ self.build_def_files.add(buildfilename)
absname = os.path.join(self.environment.get_source_dir(), buildfilename)
if not os.path.isfile(absname):
self.subdir = prev_subdir