summaryrefslogtreecommitdiff
path: root/mesonbuild/modules/dlang.py
blob: 35ce86be81f1605f6308629beb2aec9226e7350d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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)