Merge pull request #8950 from dcbaker/submit/import-required-disabled

Add required and disabled to import, modules.found method
pull/8959/head
Jussi Pakkanen 4 years ago committed by GitHub
commit ec5baa62c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      docs/markdown/Reference-manual.md
  2. 5
      docs/markdown/snippets/required_and_disabled_import.md
  3. 51
      mesonbuild/interpreter/interpreter.py
  4. 5
      mesonbuild/interpreter/kwargs.py
  5. 56
      mesonbuild/modules/__init__.py
  6. 4
      mesonbuild/modules/unstable_cuda.py
  7. 4
      mesonbuild/modules/unstable_external_project.py
  8. 10
      test cases/common/67 modules/meson.build
  9. 6
      test cases/common/67 modules/meson_options.txt
  10. 5
      test cases/failing/57 kwarg in module/meson.build
  11. 7
      test cases/failing/57 kwarg in module/test.json

@ -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*

@ -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.

@ -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

@ -132,3 +132,8 @@ class FuncInstallMan(TypedDict):
install_dir: T.Optional[str]
install_mode: FileMode
locale: T.Optional[str]
class FuncImportModule(ExtractRequired):
disabler: bool

@ -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):
'''

@ -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):

@ -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,

@ -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))

@ -0,0 +1,6 @@
option(
'disabled',
type : 'feature',
value : 'disabled',
description : 'test disabled'
)

@ -1,5 +0,0 @@
project('module test', 'c')
modtest = import('modtest', i_cause: 'a_build_failure')
modtest.print_hello()

@ -1,7 +0,0 @@
{
"stdout": [
{
"line": "test cases/failing/57 kwarg in module/meson.build:3:0: ERROR: Function does not take keyword arguments."
}
]
}
Loading…
Cancel
Save