diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 570b34f49..2cc4f44a0 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -33,7 +33,7 @@ from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, Fe from .interpreterbase import ObjectHolder, MesonVersionString from .interpreterbase import TYPE_var, TYPE_nkwargs from .interpreterbase import typed_pos_args -from .modules import ModuleReturnValue, ExtensionModule +from .modules import ModuleReturnValue, ModuleObject, ModuleState from .cmake import CMakeInterpreter from .backend.backends import TestProtocol, Backend, ExecutableSerialisation @@ -55,7 +55,6 @@ if T.TYPE_CHECKING: from .compilers import Compiler from .envconfig import MachineInfo from .environment import Environment - from .modules import ExtensionModule permitted_method_kwargs = { 'partial_dependency': {'compile_args', 'link_args', 'links', 'includes', @@ -1765,93 +1764,43 @@ class CompilerHolder(InterpreterObject): return self.compiler.get_argument_syntax() -class ModuleState(T.NamedTuple): - - """Object passed to a module when it a method is called. - - holds the current state of the meson process at a given method call in - the interpreter. - """ - - source_root: str - build_to_src: str - subproject: str - subdir: str - current_lineno: str - environment: 'Environment' - project_name: str - project_version: str - backend: str - targets: T.Dict[str, build.Target] - data: T.List[build.Data] - headers: T.List[build.Headers] - man: T.List[build.Man] - global_args: T.Dict[str, T.List[str]] - project_args: T.Dict[str, T.List[str]] - build_machine: 'MachineInfo' - host_machine: 'MachineInfo' - target_machine: 'MachineInfo' - current_node: mparser.BaseNode - - -class ModuleHolder(InterpreterObject, ObjectHolder['ExtensionModule']): - def __init__(self, modname: str, module: 'ExtensionModule', interpreter: 'Interpreter'): +class ModuleObjectHolder(InterpreterObject, ObjectHolder['ModuleObject']): + def __init__(self, modobj: 'ModuleObject', interpreter: 'Interpreter'): InterpreterObject.__init__(self) - ObjectHolder.__init__(self, module) - self.modname = modname + ObjectHolder.__init__(self, modobj) self.interpreter = interpreter def method_call(self, method_name, args, kwargs): - try: - fn = getattr(self.held_object, method_name) - except AttributeError: - raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name)) - if method_name.startswith('_'): - raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname)) - if not getattr(fn, 'no-args-flattening', False): + modobj = self.held_object + method = modobj.methods.get(method_name) + if not method and not modobj.methods: + # FIXME: Port all modules to use the methods dict. + method = getattr(modobj, method_name, None) + if method_name.startswith('_'): + raise InvalidArguments('Method {!r} is private.'.format(method_name)) + if not method: + raise InvalidCode('Unknown method "%s" in object.' % method_name) + if not getattr(method, 'no-args-flattening', False): args = flatten(args) - # This is not 100% reliable but we can't use hash() - # because the Build object contains dicts and lists. - num_targets = len(self.interpreter.build.targets) - state = ModuleState( - source_root = self.interpreter.environment.get_source_dir(), - build_to_src=mesonlib.relpath(self.interpreter.environment.get_source_dir(), - self.interpreter.environment.get_build_dir()), - subproject=self.interpreter.subproject, - subdir=self.interpreter.subdir, - current_lineno=self.interpreter.current_lineno, - environment=self.interpreter.environment, - project_name=self.interpreter.build.project_name, - project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname], - # The backend object is under-used right now, but we will need it: - # https://github.com/mesonbuild/meson/issues/1419 - backend=self.interpreter.backend, - targets=self.interpreter.build.targets, - data=self.interpreter.build.data, - headers=self.interpreter.build.get_headers(), - man=self.interpreter.build.get_man(), - #global_args_for_build = self.interpreter.build.global_args.build, - global_args = self.interpreter.build.global_args.host, - #project_args_for_build = self.interpreter.build.projects_args.build.get(self.interpreter.subproject, {}), - project_args = self.interpreter.build.projects_args.host.get(self.interpreter.subproject, {}), - build_machine=self.interpreter.builtin['build_machine'].held_object, - host_machine=self.interpreter.builtin['host_machine'].held_object, - target_machine=self.interpreter.builtin['target_machine'].held_object, - current_node=self.current_node - ) + 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. - self.held_object.interpreter = self.interpreter - if self.held_object.is_snippet(method_name): - value = fn(self.interpreter, state, args, kwargs) - return self.interpreter.holderify(value) + modobj.interpreter = self.interpreter + if method_name in modobj.snippets: + ret = method(self.interpreter, state, args, kwargs) else: - value = fn(state, args, kwargs) + # This is not 100% reliable but we can't use hash() + # because the Build object contains dicts and lists. + num_targets = len(self.interpreter.build.targets) + ret = method(state, args, kwargs) if num_targets != len(self.interpreter.build.targets): raise InterpreterException('Extension module altered internal state illegally.') - return self.interpreter.module_method_callback(value) + if isinstance(ret, ModuleReturnValue): + self.interpreter.process_new_values(ret.new_objects) + ret = ret.return_value + return self.interpreter.holderify(ret) class Summary: @@ -2401,7 +2350,7 @@ class Interpreter(InterpreterBase): subproject: str = '', subdir: str = '', subproject_dir: str = 'subprojects', - modules: T.Optional[T.Dict[str, ExtensionModule]] = None, + modules: T.Optional[T.Dict[str, ModuleObject]] = None, default_project_options: T.Optional[T.Dict[str, str]] = None, mock: bool = False, ast: T.Optional[mparser.CodeBlockNode] = None, @@ -2566,6 +2515,8 @@ class Interpreter(InterpreterBase): return DependencyHolder(item, self.subproject) elif isinstance(item, dependencies.ExternalProgram): return ExternalProgramHolder(item, self.subproject) + elif isinstance(item, ModuleObject): + return ModuleObjectHolder(item, self) elif isinstance(item, (InterpreterObject, ObjectHolder)): return item else: @@ -2600,13 +2551,6 @@ class Interpreter(InterpreterBase): else: raise InterpreterException('Module returned a value of unknown type.') - def module_method_callback(self, return_object): - if not isinstance(return_object, ModuleReturnValue): - raise InterpreterException('Bug in module, it returned an invalid object') - invalues = return_object.new_objects - self.process_new_values(invalues) - return self.holderify(return_object.return_value) - def get_build_def_files(self) -> T.List[str]: return self.build_def_files @@ -2676,7 +2620,7 @@ class Interpreter(InterpreterBase): except ImportError: raise InvalidArguments('Module "%s" does not exist' % (modname, )) ext_module = module.initialize(self) - assert isinstance(ext_module, ExtensionModule) + assert isinstance(ext_module, ModuleObject) self.modules[modname] = ext_module @stringArgs @@ -2696,7 +2640,7 @@ class Interpreter(InterpreterBase): mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) modname = 'unstable_' + plainname self.import_module(modname) - return ModuleHolder(modname, self.modules[modname], self) + return ModuleObjectHolder(self.modules[modname], self) @stringArgs @noKwargs diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index ff27a112b..1cf7c1cf5 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,21 +18,58 @@ import os from .. import build -from ..mesonlib import unholder +from ..mesonlib import unholder, relpath import typing as T if T.TYPE_CHECKING: from ..interpreter import Interpreter - from ..interpreterbase import TYPE_var + from ..interpreterbase import TYPE_var, TYPE_nvar, TYPE_nkwargs + +class ModuleState: + """Object passed to all module methods. + + This is a WIP API provided to modules, it should be extended to have everything + needed so modules does not touch any other part of Meson internal APIs. + """ -class ExtensionModule: def __init__(self, interpreter: 'Interpreter') -> None: + self.source_root = interpreter.environment.get_source_dir() + self.build_to_src = relpath(interpreter.environment.get_source_dir(), + interpreter.environment.get_build_dir()) + self.subproject = interpreter.subproject + self.subdir = interpreter.subdir + self.current_lineno = interpreter.current_lineno + self.environment = interpreter.environment + self.project_name = interpreter.build.project_name + self.project_version = interpreter.build.dep_manifest[interpreter.active_projectname] + # The backend object is under-used right now, but we will need it: + # https://github.com/mesonbuild/meson/issues/1419 + self.backend = interpreter.backend + self.targets = interpreter.build.targets + self.data = interpreter.build.data + self.headers = interpreter.build.get_headers() + self.man = interpreter.build.get_man() + self.global_args = interpreter.build.global_args.host + self.project_args = interpreter.build.projects_args.host.get(interpreter.subproject, {}) + self.build_machine = interpreter.builtin['build_machine'].held_object + self.host_machine = interpreter.builtin['host_machine'].held_object + self.target_machine = interpreter.builtin['target_machine'].held_object + self.current_node = interpreter.current_node + +class ModuleObject: + """Base class for all objects returned by modules + """ + def __init__(self, interpreter: T.Optional['Interpreter'] = None) -> None: + self.methods = {} # type: T.Dict[str, T.Callable[[T.List[TYPE_nvar], TYPE_nkwargs], TYPE_var]] + # FIXME: Port all modules to stop using self.interpreter and use API on + # ModuleState instead. self.interpreter = interpreter - self.snippets = set() # type: T.Set[str] # List of methods that operate only on the interpreter. - - def is_snippet(self, funcname: str) -> bool: - return funcname in self.snippets + # FIXME: Port all modules to remove snippets methods. + self.snippets: T.Set[str] = set() +# FIXME: Port all modules to use ModuleObject directly. +class ExtensionModule(ModuleObject): + pass def get_include_args(include_dirs, prefix='-I'): ''' diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index d0b5e978d..7de8cf7d0 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -29,7 +29,8 @@ from ..mesonlib import ( from ..interpreterbase import FeatureNew, typed_pos_args, noKwargs, permittedKwargs if T.TYPE_CHECKING: - from ..interpreter import Interpreter, ModuleState + from . import ModuleState + from ..interpreter import Interpreter class FSModule(ExtensionModule): diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 564d18143..cfe2244f1 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -21,7 +21,6 @@ from pathlib import Path from .. import mesonlib from ..mesonlib import MachineChoice, MesonException from . import ExtensionModule -from mesonbuild.modules import ModuleReturnValue from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, InvalidArguments, @@ -399,12 +398,12 @@ class PythonInstallation(ExternalProgramHolder): else: res = os.path.join(self.platlib_install_path, subdir) - return self.interpreter.module_method_callback(ModuleReturnValue(res, [])) + return res @noPosargs @noKwargs def language_version_method(self, args, kwargs): - return self.interpreter.module_method_callback(ModuleReturnValue(self.version, [])) + return self.version @noKwargs def has_path_method(self, args, kwargs): @@ -414,7 +413,7 @@ class PythonInstallation(ExternalProgramHolder): if not isinstance(path_name, str): raise InvalidArguments('has_path argument must be a string.') - return self.interpreter.module_method_callback(ModuleReturnValue(path_name in self.paths, [])) + return path_name in self.paths @noKwargs def get_path_method(self, args, kwargs): @@ -432,7 +431,7 @@ class PythonInstallation(ExternalProgramHolder): else: raise InvalidArguments('{} is not a valid path name'.format(path_name)) - return self.interpreter.module_method_callback(ModuleReturnValue(path, [])) + return path @noKwargs def has_variable_method(self, args, kwargs): @@ -442,7 +441,7 @@ class PythonInstallation(ExternalProgramHolder): if not isinstance(var_name, str): raise InvalidArguments('has_variable argument must be a string.') - return self.interpreter.module_method_callback(ModuleReturnValue(var_name in self.variables, [])) + return var_name in self.variables @noKwargs def get_variable_method(self, args, kwargs): @@ -460,7 +459,7 @@ class PythonInstallation(ExternalProgramHolder): else: raise InvalidArguments('{} is not a valid variable name'.format(var_name)) - return self.interpreter.module_method_callback(ModuleReturnValue(var, [])) + return var @noPosargs @noKwargs diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py index c4d7d41b6..11b436576 100644 --- a/mesonbuild/modules/unstable_rust.py +++ b/mesonbuild/modules/unstable_rust.py @@ -24,7 +24,8 @@ from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, from ..mesonlib import stringlistify, unholder, listify, typeslistify, File if T.TYPE_CHECKING: - from ..interpreter import ModuleState, Interpreter + from . import ModuleState + from ..interpreter import Interpreter from ..dependencies import ExternalProgram