|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Copyright 2017 The Meson development team
|
|
|
|
|
|
|
|
|
|
|
|
from .base import Dependency, InternalDependency, ExternalDependency, NotFoundDependency, MissingCompiler
|
|
|
|
from .base import (
|
|
|
|
ExternalLibrary, DependencyException, DependencyMethods,
|
|
|
|
BuiltinDependency, SystemDependency, get_leaf_external_dependencies)
|
|
|
|
from .detect import find_external_dependency, get_dep_identifier, packages, _packages_accept_language
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'Dependency',
|
|
|
|
'InternalDependency',
|
|
|
|
'ExternalDependency',
|
|
|
|
'SystemDependency',
|
|
|
|
'BuiltinDependency',
|
|
|
|
'NotFoundDependency',
|
|
|
|
'ExternalLibrary',
|
|
|
|
'DependencyException',
|
|
|
|
'DependencyMethods',
|
|
|
|
'MissingCompiler',
|
|
|
|
|
|
|
|
'find_external_dependency',
|
|
|
|
'get_dep_identifier',
|
|
|
|
'get_leaf_external_dependencies',
|
|
|
|
]
|
|
|
|
|
|
|
|
"""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 and the
|
|
|
|
`prefer_static` built-in option. 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
|
|
|
|
|
|
|
|
get_option = environment.coredata.get_option
|
|
|
|
static_opt = kwargs.get('static', get_option(Mesonlib.OptionKey('prefer_static'))
|
|
|
|
static = Mesonlib.LibType.STATIC if static_opt 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]]
|
|
|
|
#
|
|
|
|
# The internal "defaults" attribute contains a separate dictionary mapping
|
|
|
|
# for lazy imports. The values must be:
|
|
|
|
# - a string naming the submodule that should be imported from `mesonbuild.dependencies` to populate the dependency
|
|
|
|
packages.defaults.update({
|
|
|
|
# From dev:
|
|
|
|
'gtest': 'dev',
|
|
|
|
'gmock': 'dev',
|
|
|
|
'llvm': 'dev',
|
|
|
|
'valgrind': 'dev',
|
|
|
|
'zlib': 'dev',
|
|
|
|
'jni': 'dev',
|
|
|
|
'jdk': 'dev',
|
|
|
|
|
|
|
|
'boost': 'boost',
|
|
|
|
'cuda': 'cuda',
|
|
|
|
|
|
|
|
# per-file
|
|
|
|
'coarray': 'coarrays',
|
|
|
|
'hdf5': 'hdf5',
|
|
|
|
'mpi': 'mpi',
|
|
|
|
'scalapack': 'scalapack',
|
|
|
|
|
|
|
|
# From misc:
|
|
|
|
'blocks': 'misc',
|
|
|
|
'curses': 'misc',
|
|
|
|
'netcdf': 'misc',
|
|
|
|
'openmp': 'misc',
|
|
|
|
'threads': 'misc',
|
|
|
|
'pcap': 'misc',
|
|
|
|
'cups': 'misc',
|
|
|
|
'libwmf': 'misc',
|
|
|
|
'libgcrypt': 'misc',
|
|
|
|
'gpgme': 'misc',
|
|
|
|
'shaderc': 'misc',
|
|
|
|
'iconv': 'misc',
|
|
|
|
'intl': 'misc',
|
|
|
|
'dl': 'misc',
|
|
|
|
'openssl': 'misc',
|
|
|
|
'libcrypto': 'misc',
|
|
|
|
'libssl': 'misc',
|
|
|
|
|
|
|
|
# From platform:
|
|
|
|
'appleframeworks': 'platform',
|
|
|
|
|
|
|
|
# from python:
|
|
|
|
'numpy': 'python',
|
|
|
|
'python3': 'python',
|
|
|
|
'pybind11': 'python',
|
|
|
|
|
|
|
|
# From ui:
|
|
|
|
'gl': 'ui',
|
|
|
|
'gnustep': 'ui',
|
|
|
|
'sdl2': 'ui',
|
|
|
|
'wxwidgets': 'ui',
|
|
|
|
'vulkan': 'ui',
|
|
|
|
|
|
|
|
# from qt
|
|
|
|
'qt4': 'qt',
|
|
|
|
'qt5': 'qt',
|
|
|
|
'qt6': 'qt',
|
|
|
|
})
|
|
|
|
_packages_accept_language.update({
|
|
|
|
'hdf5',
|
|
|
|
'mpi',
|
|
|
|
'netcdf',
|
|
|
|
'openmp',
|
|
|
|
})
|