diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2021-03-02 22:17:38 -0500 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2021-03-04 11:33:22 -0500 |
commit | ba9bfd2bd84985d8915f79d2415bffe68e9deada (patch) | |
tree | afd108657534f73fec6610c327bbaa721fe14409 /mesonbuild/interpreter.py | |
parent | 1e69908be5fb738cd787f0e4825da395e9b356b2 (diff) | |
download | meson-ba9bfd2bd84985d8915f79d2415bffe68e9deada.zip meson-ba9bfd2bd84985d8915f79d2415bffe68e9deada.tar.gz meson-ba9bfd2bd84985d8915f79d2415bffe68e9deada.tar.bz2 |
Simplify module API
- ModuleState is now a real class that will have methods in the future
for actions modules needs, instead of using interpreter internal API.
- New ModuleObject base class, similar to InterpreterObject, that should
be used by all objects returned by modules. Its methods gets the
ModuleState passed as first argument. It has a `methods` dictionary to
define what is public API that can be called from build definition.
- Method return value is not required to be a ModuleReturnValue any
more, it can be any type that interpreter can holderify, including
ModuleObject.
- Legacy module API is maintained until we port all modules.
In the future modules should be updated:
- Use methods dict.
- Remove snippets.
- Custom objects returned by modules should all be subclass of
ModuleObject to get the state iface in their methods.
- Modules should never call into interpreter directly and instead state
object should have wrapper API.
- Stop using ModuleReturnValue in methods that just return simple
objects like strings. Possibly remove ModuleReturnValue completely
since all objects that needs to be processed by interpreter (e.g.
CustomTarget) should be created through ModuleState API.
Diffstat (limited to 'mesonbuild/interpreter.py')
-rw-r--r-- | mesonbuild/interpreter.py | 118 |
1 files changed, 31 insertions, 87 deletions
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 570b34f..2cc4f44 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 |