# SPDX-License-Identifier: Apache-2.0 # Copyright 2019 The Meson development team # This file contains the base representation for import('modname') from __future__ import annotations import dataclasses import typing as T from .. import build, dependencies, mesonlib from ..options import OptionKey from ..build import IncludeDirs from ..interpreterbase.decorators import noKwargs, noPosargs from ..mesonlib import relpath, HoldableObject, MachineChoice from ..programs import ExternalProgram if T.TYPE_CHECKING: from ..interpreter import Interpreter from ..interpreter.interpreter import ProgramVersionFunc from ..interpreterbase import TYPE_var, TYPE_kwargs from ..programs import OverrideProgram from ..dependencies import Dependency from ..options import ElementaryOptionValues 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. """ def __init__(self, interpreter: 'Interpreter') -> None: # Keep it private, it should be accessed only through methods. self._interpreter = interpreter 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.root_subdir = interpreter.root_subdir self.current_lineno = interpreter.current_node.lineno self.environment = interpreter.environment self.project_name = interpreter.active_projectname self.project_version = interpreter.project_version # 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.dependency_overrides = interpreter.build.dependency_overrides 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.current_node = interpreter.current_node def get_include_args(self, include_dirs: T.Iterable[T.Union[str, build.IncludeDirs]], prefix: str = '-I') -> T.List[str]: if not include_dirs: return [] srcdir = self.environment.get_source_dir() builddir = self.environment.get_build_dir() dirs_str: T.List[str] = [] for dirs in include_dirs: if isinstance(dirs, str): dirs_str += [f'{prefix}{dirs}'] else: dirs_str.extend([f'{prefix}{i}' for i in dirs.to_string_list(srcdir, builddir)]) dirs_str.extend([f'{prefix}{i}' for i in dirs.get_extra_build_dirs()]) return dirs_str def find_program(self, prog: T.Union[mesonlib.FileOrString, T.List[mesonlib.FileOrString]], required: bool = True, version_func: T.Optional[ProgramVersionFunc] = None, wanted: T.Union[str, T.List[str]] = '', silent: bool = False, for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.OverrideExecutable, OverrideProgram]: if not isinstance(prog, list): prog = [prog] return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted, silent=silent, for_machine=for_machine) def find_tool(self, name: str, depname: str, varname: str, required: bool = True, wanted: T.Optional[str] = None) -> T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: # Look in overrides in case it's built as subproject progobj = self._interpreter.program_from_overrides([name], []) if progobj is not None: return progobj # Look in machine file prog_list = self.environment.lookup_binary_entry(MachineChoice.HOST, name) if prog_list is not None: return ExternalProgram.from_entry(name, prog_list) # Check if pkgconfig has a variable dep = self.dependency(depname, native=True, required=False, wanted=wanted) if dep.found() and dep.type_name == 'pkgconfig': value = dep.get_variable(pkgconfig=varname) if value: progobj = ExternalProgram(value) if not progobj.found(): msg = (f'Dependency {depname!r} tool variable {varname!r} contains erroneous value: {value!r}\n\n' f'This is a distributor issue -- please report it to your {depname} provider.') raise mesonlib.MesonException(msg) return progobj # Normal program lookup return self.find_program(name, required=required, wanted=wanted) def overridden_dependency(self, depname: str, for_machine: MachineChoice = MachineChoice.HOST) -> Dependency: identifier = dependencies.get_dep_identifier(depname, {}) try: return self.dependency_overrides[for_machine][identifier].dep except KeyError: raise mesonlib.MesonException(f'dependency "{depname}" was not overridden for the {for_machine}') def dependency(self, depname: str, native: bool = False, required: bool = True, wanted: T.Optional[T.Union[str, T.List[str]]] = None) -> 'Dependency': kwargs: T.Dict[str, object] = {'native': native, 'required': required} if wanted: kwargs['version'] = wanted # FIXME: Even if we fix the function, mypy still can't figure out what's # going on here. And we really don't want to call interpreter # implementations of meson functions anyway. return self._interpreter.func_dependency(self.current_node, [depname], kwargs) # type: ignore def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, 'ExternalProgram', mesonlib.File]], workdir: T.Optional[str] = None, env: T.Union[T.List[str], T.Dict[str, str], str] = None, depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]] = None) -> None: kwargs = {'workdir': workdir, 'env': env, 'depends': depends, } # typed_* takes a list, and gives a tuple to func_test. Violating that constraint # makes the universe (or at least use of this function) implode real_args = list(args) # TODO: Use interpreter internal API, but we need to go through @typed_kwargs self._interpreter.func_test(self.current_node, real_args, kwargs) def get_option(self, name: str, subproject: str = '', machine: MachineChoice = MachineChoice.HOST) -> ElementaryOptionValues: return self.environment.coredata.optstore.get_value_for(OptionKey(name, subproject, machine)) def is_user_defined_option(self, name: str, subproject: str = '', machine: MachineChoice = MachineChoice.HOST, lang: T.Optional[str] = None) -> bool: key = OptionKey(name, subproject, machine) return key in self._interpreter.user_defined_options.cmd_line_options def process_include_dirs(self, dirs: T.Iterable[T.Union[str, IncludeDirs]]) -> T.Iterable[IncludeDirs]: """Convert raw include directory arguments to only IncludeDirs :param dirs: An iterable of strings and IncludeDirs :return: None :yield: IncludeDirs objects """ for d in dirs: if isinstance(d, IncludeDirs): yield d else: yield self._interpreter.build_incdir_object([d]) def add_language(self, lang: str, for_machine: MachineChoice) -> None: self._interpreter.add_languages([lang], True, for_machine) 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']] ] = {} class MutableModuleObject(ModuleObject): pass @dataclasses.dataclass class ModuleInfo: """Metadata about a Module.""" name: str added: T.Optional[str] = None deprecated: T.Optional[str] = None unstable: bool = False stabilized: T.Optional[str] = None class NewExtensionModule(ModuleObject): """Class for modern modules provides the found method. """ INFO: ModuleInfo 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 def postconf_hook(self, b: build.Build) -> None: 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. class ExtensionModule(NewExtensionModule): def __init__(self, interpreter: 'Interpreter') -> None: super().__init__() self.interpreter = interpreter class NotFoundExtensionModule(NewExtensionModule): """Class for modern modules provides the found method. """ def __init__(self, name: str) -> None: super().__init__() self.INFO = ModuleInfo(name) @staticmethod def found() -> bool: return False def is_module_library(fname: mesonlib.FileOrString) -> bool: ''' Check if the file is a library-like file generated by a module-specific target, such as GirTarget or TypelibTarget ''' suffix = fname.split('.')[-1] return suffix in {'gir', 'typelib'} class ModuleReturnValue: def __init__(self, return_value: T.Optional['TYPE_var'], new_objects: T.Sequence[T.Union['TYPE_var', 'mesonlib.ExecutableSerialisation']]) -> None: self.return_value = return_value assert isinstance(new_objects, list) self.new_objects: T.List[T.Union['TYPE_var', 'mesonlib.ExecutableSerialisation']] = new_objects class GResourceTarget(build.CustomTarget): source_dirs: T.List[str] = [] class GResourceHeaderTarget(build.CustomTarget): pass class GirTarget(build.CustomTarget): pass class TypelibTarget(build.CustomTarget): pass class VapiTarget(build.CustomTarget): pass