| # 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, intl_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, | |
| 'intl': intl_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', | |
| }) |