aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/dependencies/__init__.py
blob: b49514b2950e2c9be42a6d2020e9b36eab364427 (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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# Copyright 2017 The Meson development team

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .boost import BoostDependency
from .cuda import CudaDependency
from .hdf5 import hdf5_factory
from .base import Dependency, InternalDependency, ExternalDependency, NotFoundDependency
from .base import (
        ExternalLibrary, DependencyException, DependencyMethods,
        BuiltinDependency, SystemDependency)
from .cmake import CMakeDependency
from .configtool import ConfigToolDependency
from .dub import DubDependency
from .framework import ExtraFrameworkDependency
from .pkgconfig import PkgConfigDependency
from .factory import DependencyFactory
from .detect import find_external_dependency, get_dep_identifier, packages, _packages_accept_language
from .dev import (
    ValgrindDependency, JDKSystemDependency, gmock_factory, gtest_factory,
    llvm_factory, zlib_factory)
from .coarrays import coarray_factory
from .mpi import mpi_factory
from .scalapack import scalapack_factory
from .misc import (
    BlocksDependency, OpenMPDependency, cups_factory, curses_factory, gpgme_factory,
    libgcrypt_factory, libwmf_factory, netcdf_factory, pcap_factory, python3_factory,
    shaderc_factory, threads_factory, ThreadDependency, iconv_factory, intl_factory,
    dl_factory, openssl_factory, libcrypto_factory, libssl_factory,
)
from .platform import AppleFrameworks
from .qt import qt4_factory, qt5_factory, qt6_factory
from .ui import GnuStepDependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory

__all__ = [
    'Dependency',
    'InternalDependency',
    'ExternalDependency',
    'SystemDependency',
    'BuiltinDependency',
    'NotFoundDependency',
    'ExternalLibrary',
    'DependencyException',
    'DependencyMethods',

    'CMakeDependency',
    'ConfigToolDependency',
    'DubDependency',
    'ExtraFrameworkDependency',
    'PkgConfigDependency',

    'DependencyFactory',

    'ThreadDependency',

    'find_external_dependency',
    'get_dep_identifier',
]

"""Dependency representations and discovery logic.

Meson attempts to largely abstract away dependency discovery information, and
to encapsulate that logic itself so that the DSL doesn't have too much direct
information. There are some cases where this is impossible/undesirable, such
as the `get_variable()` method.

Meson has four primary dependency types:
  1. pkg-config
  2. apple frameworks
  3. CMake
  4. system

Plus a few more niche ones.

When a user calls `dependency('foo')` Meson creates a list of candidates, and
tries those candidates in order to find one that matches the criteria
provided by the user (such as version requirements, or optional components
that are required.)

Except to work around bugs or handle odd corner cases, pkg-config and CMake
generally just workâ„¢, though there are exceptions. Most of this package is
concerned with dependencies that don't (always) provide CMake and/or
pkg-config files.

For these cases one needs to write a `system` dependency. These dependencies
descend directly from `ExternalDependency`, in their constructor they
manually set up the necessary link and compile args (and additional
dependencies as necessary).

For example, imagine a dependency called Foo, it uses an environment variable
called `$FOO_ROOT` to point to its install root, which looks like this:
```txt
$FOOROOT
→ include/
→ lib/
```
To use Foo, you need its include directory, and you need to link to
`lib/libfoo.ext`.

You could write code that looks like:

```python
class FooSystemDependency(ExternalDependency):

    def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
        super().__init__(name, environment, kwargs)
        root = os.environ.get('FOO_ROOT')
        if root is None:
            mlog.debug('$FOO_ROOT is unset.')
            self.is_found = False
            return

        lib = self.clib_compiler.find_library('foo', environment, [os.path.join(root, 'lib')])
        if lib is None:
            mlog.debug('Could not find lib.')
            self.is_found = False
            return

        self.compile_args.append(f'-I{os.path.join(root, "include")}')
        self.link_args.append(lib)
        self.is_found = True
```

This code will look for `FOO_ROOT` in the environment, handle `FOO_ROOT` being
undefined gracefully, then set its `compile_args` and `link_args` gracefully.
It will also gracefully handle not finding the required lib (hopefully that
doesn't happen, but it could if, for example, the lib is only static and
shared linking is requested).

There are a couple of things about this that still aren't ideal. For one, we
don't want to be reading random environment variables at this point. Those
should actually be added to `envconfig.Properties` and read in
`environment.Environment._set_default_properties_from_env` (see how
`BOOST_ROOT` is handled). We can also handle the `static` keyword. So
now that becomes:

```python
class FooSystemDependency(ExternalDependency):

    def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
        super().__init__(name, environment, kwargs)
        root = environment.properties[self.for_machine].foo_root
        if root is None:
            mlog.debug('foo_root is unset.')
            self.is_found = False
            return

        static = Mesonlib.LibType.STATIC if kwargs.get('static', False) else Mesonlib.LibType.SHARED
        lib = self.clib_compiler.find_library(
            'foo', environment, [os.path.join(root, 'lib')], libtype=static)
        if lib is None:
            mlog.debug('Could not find lib.')
            self.is_found = False
            return

        self.compile_args.append(f'-I{os.path.join(root, "include")}')
        self.link_args.append(lib)
        self.is_found = True
```

This is nicer in a couple of ways. First we can properly cross compile as we
are allowed to set `FOO_ROOT` for both the build and host machines, it also
means that users can override this in their machine files, and if that
environment variables changes during a Meson reconfigure Meson won't re-read
it, this is important for reproducibility. Finally, Meson will figure out
whether it should be finding `libfoo.so` or `libfoo.a` (or the platform
specific names). Things are looking pretty good now, so it can be added to
the `packages` dict below:

```python
packages.update({
    'foo': FooSystemDependency,
})
```

Now, what if foo also provides pkg-config, but it's only shipped on Unices,
or only included in very recent versions of the dependency? We can use the
`DependencyFactory` class:

```python
foo_factory = DependencyFactory(
    'foo',
    [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
    system_class=FooSystemDependency,
)
```

This is a helper function that will generate a default pkg-config based
dependency, and use the `FooSystemDependency` as well. It can also handle
custom finders for pkg-config and cmake based dependencies that need some
extra help. You would then add the `foo_factory` to packages instead of
`FooSystemDependency`:

```python
packages.update({
    'foo': foo_factory,
})
```

If you have a dependency that is very complicated, (such as having multiple
implementations) you may need to write your own factory function. There are a
number of examples in this package.

_Note_ before we moved to factory functions it was common to use an
`ExternalDependency` class that would instantiate different types of
dependencies and hold the one it found. There are a number of drawbacks to
this approach, and no new dependencies should do this.
"""

# This is a dict where the keys should be strings, and the values must be one
# of:
# - An ExternalDependency subclass
# - A DependencyFactory object
# - A callable with a signature of (Environment, MachineChoice, Dict[str, Any]) -> List[Callable[[], ExternalDependency]]
packages.update({
    # From dev:
    'gtest': gtest_factory,
    'gmock': gmock_factory,
    'llvm': llvm_factory,
    'valgrind': ValgrindDependency,
    'zlib': zlib_factory,
    'jdk': JDKSystemDependency,

    'boost': BoostDependency,
    'cuda': CudaDependency,

    # per-file
    'coarray': coarray_factory,
    'hdf5': hdf5_factory,
    'mpi': mpi_factory,
    'scalapack': scalapack_factory,

    # From misc:
    'blocks': BlocksDependency,
    'curses': curses_factory,
    'netcdf': netcdf_factory,
    'openmp': OpenMPDependency,
    'python3': python3_factory,
    'threads': threads_factory,
    'pcap': pcap_factory,
    'cups': cups_factory,
    'libwmf': libwmf_factory,
    'libgcrypt': libgcrypt_factory,
    'gpgme': gpgme_factory,
    'shaderc': shaderc_factory,
    'iconv': iconv_factory,
    'intl': intl_factory,
    'dl': dl_factory,
    'openssl': openssl_factory,
    'libcrypto': libcrypto_factory,
    'libssl': libssl_factory,

    # From platform:
    'appleframeworks': AppleFrameworks,

    # From ui:
    'gl': gl_factory,
    'gnustep': GnuStepDependency,
    'qt4': qt4_factory,
    'qt5': qt5_factory,
    'qt6': qt6_factory,
    'sdl2': sdl2_factory,
    'wxwidgets': WxDependency,
    'vulkan': vulkan_factory,
})
_packages_accept_language.update({
    'hdf5',
    'mpi',
    'netcdf',
    'openmp',
})