# SPDX-License-Identifier: Apache-2.0 # Copyright 2018 The Meson development team # This file contains the detection logic for external dependencies that # are UI-related. from __future__ import annotations import json import os import typing as T from . import ExtensionModule, ModuleInfo from .. import mlog from ..build import InvalidArguments from ..dependencies import Dependency from ..dependencies.dub import DubDependency from ..interpreterbase import typed_pos_args from ..mesonlib import Popen_safe, MesonException, listify if T.TYPE_CHECKING: from typing_extensions import Literal, TypeAlias from . import ModuleState from ..build import OverrideExecutable from ..interpreter.interpreter import Interpreter from ..interpreterbase.baseobjects import TYPE_kwargs from ..programs import ExternalProgram, OverrideProgram _AnyProgram: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram] _JSONTypes: TypeAlias = T.Union[str, int, bool, None, T.List['_JSONTypes'], T.Dict[str, '_JSONTypes']] class DlangModule(ExtensionModule): class_dubbin: T.Union[_AnyProgram, Literal[False], None] = None init_dub = False dubbin: T.Union[_AnyProgram, Literal[False], None] INFO = ModuleInfo('dlang', '0.48.0') def __init__(self, interpreter: Interpreter): super().__init__(interpreter) self.methods.update({ 'generate_dub_file': self.generate_dub_file, }) def _init_dub(self, state: ModuleState) -> None: if DlangModule.class_dubbin is None and DubDependency.class_dubbin is not None: self.dubbin = DubDependency.class_dubbin[0] DlangModule.class_dubbin = self.dubbin else: self.dubbin = DlangModule.class_dubbin if DlangModule.class_dubbin is None: self.dubbin = self.check_dub(state) DlangModule.class_dubbin = self.dubbin else: self.dubbin = DlangModule.class_dubbin if not self.dubbin: if not self.dubbin: raise MesonException('DUB not found.') @typed_pos_args('dlang.generate_dub_file', str, str) def generate_dub_file(self, state: ModuleState, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> None: if not DlangModule.init_dub: self._init_dub(state) config: T.Dict[str, _JSONTypes] = { 'name': args[0] } config_path = os.path.join(args[1], 'dub.json') if os.path.exists(config_path): with open(config_path, encoding='utf-8') as ofile: try: config = json.load(ofile) except ValueError: mlog.warning('Failed to load the data in dub.json') warn_publishing = ['description', 'license'] for arg in warn_publishing: if arg not in kwargs and \ arg not in config: mlog.warning('Without', mlog.bold(arg), 'the DUB package can\'t be published') for key, value in kwargs.items(): if key == 'dependencies': values = listify(value, flatten=False) data: T.Dict[str, _JSONTypes] = {} for dep in values: if isinstance(dep, Dependency): name = dep.get_name() ret, res = self._call_dubbin(['describe', name]) if ret == 0: version = dep.get_version() if version is None: data[name] = '' else: data[name] = version config[key] = data else: def _do_validate(v: object) -> _JSONTypes: if not isinstance(v, (str, int, bool, list, dict)): raise InvalidArguments('keyword arguments must be strings, numbers, booleans, arrays, or dictionaries of such') if isinstance(v, list): for e in v: _do_validate(e) if isinstance(v, dict): for e in v.values(): _do_validate(e) return T.cast('_JSONTypes', v) config[key] = _do_validate(value) with open(config_path, 'w', encoding='utf-8') as ofile: ofile.write(json.dumps(config, indent=4, ensure_ascii=False)) def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Mapping[str, str]] = None) -> T.Tuple[int, str]: assert self.dubbin is not None and self.dubbin is not False, 'for mypy' p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2] return p.returncode, out.strip() def check_dub(self, state: ModuleState) -> T.Union[_AnyProgram, Literal[False]]: dubbin = state.find_program('dub', silent=True) if dubbin.found(): try: p, out = Popen_safe(dubbin.get_command() + ['--version'])[0:2] if p.returncode != 0: mlog.warning('Found dub {!r} but couldn\'t run it' ''.format(' '.join(dubbin.get_command()))) # Set to False instead of None to signify that we've already # searched for it and not found it else: mlog.log('Found DUB:', mlog.green('YES'), ':', mlog.bold(dubbin.get_path() or ''), '({})'.format(out.strip())) return dubbin except (FileNotFoundError, PermissionError): pass mlog.log('Found DUB:', mlog.red('NO')) return False def initialize(interp: Interpreter) -> DlangModule: return DlangModule(interp)