1# Copyright 2019 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# This file contains the detection logic for external dependencies that
16# are UI-related.
17
18import os
19import typing as T
20
21from .. import build, mesonlib
22from ..mesonlib import relpath, HoldableObject
23from ..interpreterbase.decorators import noKwargs, noPosargs
24
25if T.TYPE_CHECKING:
26    from ..interpreter import Interpreter
27    from ..interpreterbase import TYPE_var, TYPE_kwargs
28    from ..programs import ExternalProgram
29
30class ModuleState:
31    """Object passed to all module methods.
32
33    This is a WIP API provided to modules, it should be extended to have everything
34    needed so modules does not touch any other part of Meson internal APIs.
35    """
36
37    def __init__(self, interpreter: 'Interpreter') -> None:
38        # Keep it private, it should be accessed only through methods.
39        self._interpreter = interpreter
40
41        self.source_root = interpreter.environment.get_source_dir()
42        self.build_to_src = relpath(interpreter.environment.get_source_dir(),
43                                    interpreter.environment.get_build_dir())
44        self.subproject = interpreter.subproject
45        self.subdir = interpreter.subdir
46        self.current_lineno = interpreter.current_lineno
47        self.environment = interpreter.environment
48        self.project_name = interpreter.build.project_name
49        self.project_version = interpreter.build.dep_manifest[interpreter.active_projectname]
50        # The backend object is under-used right now, but we will need it:
51        # https://github.com/mesonbuild/meson/issues/1419
52        self.backend = interpreter.backend
53        self.targets = interpreter.build.targets
54        self.data = interpreter.build.data
55        self.headers = interpreter.build.get_headers()
56        self.man = interpreter.build.get_man()
57        self.global_args = interpreter.build.global_args.host
58        self.project_args = interpreter.build.projects_args.host.get(interpreter.subproject, {})
59        self.build_machine = interpreter.builtin['build_machine'].held_object
60        self.host_machine = interpreter.builtin['host_machine'].held_object
61        self.target_machine = interpreter.builtin['target_machine'].held_object
62        self.current_node = interpreter.current_node
63
64    def get_include_args(self, include_dirs: T.Iterable[T.Union[str, build.IncludeDirs]], prefix: str = '-I') -> T.List[str]:
65        if not include_dirs:
66            return []
67
68        srcdir = self.environment.get_source_dir()
69        builddir = self.environment.get_build_dir()
70
71        dirs_str: T.List[str] = []
72        for dirs in include_dirs:
73            if isinstance(dirs, str):
74                dirs_str += [f'{prefix}{dirs}']
75                continue
76
77            # Should be build.IncludeDirs object.
78            basedir = dirs.get_curdir()
79            for d in dirs.get_incdirs():
80                expdir = os.path.join(basedir, d)
81                srctreedir = os.path.join(srcdir, expdir)
82                buildtreedir = os.path.join(builddir, expdir)
83                dirs_str += [f'{prefix}{buildtreedir}',
84                             f'{prefix}{srctreedir}']
85            for d in dirs.get_extra_build_dirs():
86                dirs_str += [f'{prefix}{d}']
87
88        return dirs_str
89
90    def find_program(self, prog: T.Union[str, T.List[str]], required: bool = True,
91                     version_func: T.Optional[T.Callable[['ExternalProgram'], str]] = None,
92                     wanted: T.Optional[str] = None) -> 'ExternalProgram':
93        return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted)
94
95    def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, 'ExternalProgram', mesonlib.File]],
96             workdir: T.Optional[str] = None,
97             env: T.Union[T.List[str], T.Dict[str, str], str] = None,
98             depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]] = None) -> None:
99        kwargs = {'workdir': workdir,
100                  'env': env,
101                  'depends': depends,
102                  }
103        # TODO: Use interpreter internal API, but we need to go through @typed_kwargs
104        self._interpreter.func_test(self.current_node, args, kwargs)
105
106
107class ModuleObject(HoldableObject):
108    """Base class for all objects returned by modules
109    """
110    def __init__(self) -> None:
111        self.methods: T.Dict[
112            str,
113            T.Callable[[ModuleState, T.List['TYPE_var'], 'TYPE_kwargs'], T.Union[ModuleReturnValue, 'TYPE_var']]
114        ] = {}
115
116
117class MutableModuleObject(ModuleObject):
118    pass
119
120
121# FIXME: Port all modules to stop using self.interpreter and use API on
122# ModuleState instead. Modules should stop using this class and instead use
123# ModuleObject base class.
124class ExtensionModule(ModuleObject):
125    def __init__(self, interpreter: 'Interpreter') -> None:
126        super().__init__()
127        self.interpreter = interpreter
128        self.methods.update({
129            'found': self.found_method,
130        })
131
132    @noPosargs
133    @noKwargs
134    def found_method(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool:
135        return self.found()
136
137    @staticmethod
138    def found() -> bool:
139        return True
140
141
142class NewExtensionModule(ModuleObject):
143
144    """Class for modern modules
145
146    provides the found method.
147    """
148
149    def __init__(self) -> None:
150        super().__init__()
151        self.methods.update({
152            'found': self.found_method,
153        })
154
155    @noPosargs
156    @noKwargs
157    def found_method(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool:
158        return self.found()
159
160    @staticmethod
161    def found() -> bool:
162        return True
163
164
165class NotFoundExtensionModule(NewExtensionModule):
166
167    """Class for modern modules
168
169    provides the found method.
170    """
171
172    @staticmethod
173    def found() -> bool:
174        return False
175
176
177def is_module_library(fname):
178    '''
179    Check if the file is a library-like file generated by a module-specific
180    target, such as GirTarget or TypelibTarget
181    '''
182    if hasattr(fname, 'fname'):
183        fname = fname.fname
184    suffix = fname.split('.')[-1]
185    return suffix in ('gir', 'typelib')
186
187
188class ModuleReturnValue:
189    def __init__(self, return_value: T.Optional['TYPE_var'], new_objects: T.List['TYPE_var']) -> None:
190        self.return_value = return_value
191        assert(isinstance(new_objects, list))
192        self.new_objects = new_objects
193
194class GResourceTarget(build.CustomTarget):
195    def __init__(self, name, subdir, subproject, kwargs):
196        super().__init__(name, subdir, subproject, kwargs)
197
198class GResourceHeaderTarget(build.CustomTarget):
199    def __init__(self, name, subdir, subproject, kwargs):
200        super().__init__(name, subdir, subproject, kwargs)
201
202class GirTarget(build.CustomTarget):
203    def __init__(self, name, subdir, subproject, kwargs):
204        super().__init__(name, subdir, subproject, kwargs)
205
206class TypelibTarget(build.CustomTarget):
207    def __init__(self, name, subdir, subproject, kwargs):
208        super().__init__(name, subdir, subproject, kwargs)
209
210class VapiTarget(build.CustomTarget):
211    def __init__(self, name, subdir, subproject, kwargs):
212        super().__init__(name, subdir, subproject, kwargs)
213