The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
8.1 KiB
254 lines
8.1 KiB
# 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', |
|
'diasdk': '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', |
|
'objfw': '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', |
|
})
|
|
|