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