diff options
-rw-r--r-- | docs/markdown/Reference-manual.md | 20 | ||||
-rw-r--r-- | docs/markdown/snippets/required_and_disabled_import.md | 5 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 51 | ||||
-rw-r--r-- | mesonbuild/interpreter/kwargs.py | 5 | ||||
-rw-r--r-- | mesonbuild/modules/__init__.py | 56 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_cuda.py | 4 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_external_project.py | 4 | ||||
-rw-r--r-- | test cases/common/67 modules/meson.build | 10 | ||||
-rw-r--r-- | test cases/common/67 modules/meson_options.txt | 6 | ||||
-rw-r--r-- | test cases/failing/57 kwarg in module/meson.build | 5 | ||||
-rw-r--r-- | test cases/failing/57 kwarg in module/test.json | 7 |
11 files changed, 134 insertions, 39 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 16fcbbf..e96a6fc 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -973,19 +973,21 @@ a non-existing variable will cause a fatal error. ### import() -``` meson - module_object import(module_name) +``` + module_object import(string, required : bool | feature, disabler : bool) ``` -Imports the given extension module. Returns an opaque object that can -be used to call the methods of the module. Here's an example for a -hypothetical `testmod` module. +Imports the given extension module. Returns an object that can be used to call +the methods of the module. Here's an example for a hypothetical `testmod` +module. ```meson tmod = import('testmod') tmod.do_something() ``` +*Since 0.59.0* the required and disabler keyword arguments + ### include_directories() ``` meson @@ -2911,3 +2913,11 @@ sample piece of code with [`compiler.run()`](#compiler-object) or - `returncode()`: the return code of executing the compiled binary - `stderr()`: the standard error produced when the command was run - `stdout()`: the standard out produced when the command was run + +### `module` object + +Modules provide their own specific implementation methods, but all modules +proivide the following methods: + +- `bool found()`: returns True if the module was successfully imported, + otherwise false. *Since 0.59.0* diff --git a/docs/markdown/snippets/required_and_disabled_import.md b/docs/markdown/snippets/required_and_disabled_import.md new file mode 100644 index 0000000..39ca307 --- /dev/null +++ b/docs/markdown/snippets/required_and_disabled_import.md @@ -0,0 +1,5 @@ +## The `import()` function gains `required` and `disabler` arguments + +In addition, modules now have a `found()` method, like programs and +dependencies. This allows them to be conditionally required, and used in most +places that an object with a `found()` method can be. diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d37d5f7..860fd98 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -32,7 +32,7 @@ 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 ModuleObject, MutableModuleObject +from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule from ..cmake import CMakeInterpreter from ..backend.backends import Backend, ExecutableSerialisation @@ -161,6 +161,13 @@ _INSTALL_MODE_KW = KwargInfo( 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): @@ -304,7 +311,7 @@ class Interpreter(InterpreterBase, HoldableObject): subproject: str = '', subdir: str = '', subproject_dir: str = 'subprojects', - modules: T.Optional[T.Dict[str, ModuleObject]] = None, + modules: T.Optional[T.Dict[str, T.Union[ExtensionModule, NewExtensionModule, NotFoundExtensionModule]]] = None, default_project_options: T.Optional[T.Dict[str, str]] = None, mock: bool = False, ast: T.Optional[mparser.CodeBlockNode] = None, @@ -601,35 +608,47 @@ class Interpreter(InterpreterBase, HoldableObject): dep = df.lookup(kwargs, force_fallback=True) self.build.stdlibs[for_machine][l] = dep - def import_module(self, modname): + def _import_module(self, modname: str, required: bool) -> T.Union[ExtensionModule, NewExtensionModule, NotFoundExtensionModule]: if modname in self.modules: - return + return self.modules[modname] try: module = importlib.import_module('mesonbuild.modules.' + modname) except ImportError: - raise InvalidArguments(f'Module "{modname}" does not exist') - ext_module = module.initialize(self) - assert isinstance(ext_module, ModuleObject) + 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 - @stringArgs - @noKwargs - def func_import(self, node, args, kwargs): - if len(args) != 1: - raise InvalidCode('Import takes one argument.') + @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 - self.import_module(plainname) + 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.') - modname = plainname + 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 - self.import_module(modname) - return self.modules[modname] + return self._import_module(modname, required) @stringArgs @noKwargs diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 3c3ecf6..b92b66f 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -132,3 +132,8 @@ class FuncInstallMan(TypedDict): install_dir: T.Optional[str] install_mode: FileMode locale: T.Optional[str] + + +class FuncImportModule(ExtractRequired): + + disabler: bool diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 2b53de5..19de1bd 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -16,10 +16,11 @@ # are UI-related. import os +import typing as T from .. import build from ..mesonlib import relpath, HoldableObject -import typing as T +from ..interpreterbase.decorators import noKwargs, noPosargs if T.TYPE_CHECKING: from ..interpreter import Interpreter @@ -91,18 +92,21 @@ class ModuleState: wanted: T.Optional[str] = None) -> 'ExternalProgram': return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted) + class ModuleObject(HoldableObject): """Base class for all objects returned by modules """ def __init__(self) -> None: self.methods: T.Dict[ str, - T.Callable[[ModuleState, T.List[TYPE_var], TYPE_kwargs], T.Union[ModuleReturnValue, TYPE_var]] + T.Callable[[ModuleState, T.List['TYPE_var'], 'TYPE_kwargs'], T.Union[ModuleReturnValue, 'TYPE_var']] ] = {} + class MutableModuleObject(ModuleObject): pass + # FIXME: Port all modules to stop using self.interpreter and use API on # ModuleState instead. Modules should stop using this class and instead use # ModuleObject base class. @@ -110,6 +114,54 @@ class ExtensionModule(ModuleObject): def __init__(self, interpreter: 'Interpreter') -> None: super().__init__() self.interpreter = interpreter + self.methods.update({ + 'found': self.found_method, + }) + + @noPosargs + @noKwargs + def found_method(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: + return self.found() + + @staticmethod + def found() -> bool: + return True + + +class NewExtensionModule(ModuleObject): + + """Class for modern modules + + provides the found method. + """ + + def __init__(self) -> None: + super().__init__() + self.methods.update({ + 'found': self.found_method, + }) + + @noPosargs + @noKwargs + def found_method(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: + return self.found() + + @staticmethod + def found() -> bool: + return True + + +class NotFoundExtensionModule(NewExtensionModule): + + """Class for modern modules + + provides the found method. + """ + + @staticmethod + def found() -> bool: + return False + def is_module_library(fname): ''' diff --git a/mesonbuild/modules/unstable_cuda.py b/mesonbuild/modules/unstable_cuda.py index 328b90b..9e8aa63 100644 --- a/mesonbuild/modules/unstable_cuda.py +++ b/mesonbuild/modules/unstable_cuda.py @@ -18,14 +18,14 @@ import re from ..mesonlib import version_compare from ..compilers import CudaCompiler, Compiler -from . import ModuleObject +from . import NewExtensionModule from ..interpreterbase import ( flatten, permittedKwargs, noKwargs, InvalidArguments, FeatureNew ) -class CudaModule(ModuleObject): +class CudaModule(NewExtensionModule): @FeatureNew('CUDA module', '0.50.0') def __init__(self, *args, **kwargs): diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index 4f8ac56..f866e75 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -16,7 +16,7 @@ import os, subprocess, shlex from pathlib import Path import typing as T -from . import ExtensionModule, ModuleReturnValue, ModuleState, ModuleObject +from . import ExtensionModule, ModuleReturnValue, ModuleState, NewExtensionModule from .. import mlog, build from ..mesonlib import (MesonException, Popen_safe, MachineChoice, get_variable_regex, do_replacement, extract_as_list) @@ -26,7 +26,7 @@ from ..compilers.compilers import CFLAGS_MAPPING, CEXE_MAPPING from ..dependencies import InternalDependency, PkgConfigDependency from ..mesonlib import OptionKey -class ExternalProject(ModuleObject): +class ExternalProject(NewExtensionModule): def __init__(self, state: ModuleState, configure_command: str, diff --git a/test cases/common/67 modules/meson.build b/test cases/common/67 modules/meson.build index e9750cd..ad33ed6 100644 --- a/test cases/common/67 modules/meson.build +++ b/test cases/common/67 modules/meson.build @@ -2,3 +2,13 @@ project('module test', 'c') modtest = import('modtest') modtest.print_hello() +assert(modtest.found()) + +modtest = import('modtest', required : get_option('disabled')) +assert(not modtest.found()) + +notfound = import('not-found', required : false) +assert(not notfound.found()) + +disabled = import('not-found', required : false, disabler : true) +assert(is_disabler(disabled)) diff --git a/test cases/common/67 modules/meson_options.txt b/test cases/common/67 modules/meson_options.txt new file mode 100644 index 0000000..0671144 --- /dev/null +++ b/test cases/common/67 modules/meson_options.txt @@ -0,0 +1,6 @@ +option( + 'disabled', + type : 'feature', + value : 'disabled', + description : 'test disabled' +) diff --git a/test cases/failing/57 kwarg in module/meson.build b/test cases/failing/57 kwarg in module/meson.build deleted file mode 100644 index b105db1..0000000 --- a/test cases/failing/57 kwarg in module/meson.build +++ /dev/null @@ -1,5 +0,0 @@ -project('module test', 'c') - -modtest = import('modtest', i_cause: 'a_build_failure') -modtest.print_hello() - diff --git a/test cases/failing/57 kwarg in module/test.json b/test cases/failing/57 kwarg in module/test.json deleted file mode 100644 index cafb3ab..0000000 --- a/test cases/failing/57 kwarg in module/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/57 kwarg in module/meson.build:3:0: ERROR: Function does not take keyword arguments." - } - ] -} |