| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2013-2021 The Meson development team |
| |
| from __future__ import annotations |
| |
| import collections, functools, importlib |
| import typing as T |
| |
| from .base import ExternalDependency, DependencyException, DependencyMethods, NotFoundDependency |
| |
| from ..mesonlib import listify, MachineChoice, PerMachine |
| from .. import mlog |
| |
| if T.TYPE_CHECKING: |
| from ..environment import Environment |
| from .factory import DependencyFactory, WrappedFactoryFunc, DependencyGenerator |
| |
| TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]] |
| TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...] |
| PackageTypes = T.Union[T.Type[ExternalDependency], DependencyFactory, WrappedFactoryFunc] |
| |
| class DependencyPackages(collections.UserDict): |
| data: T.Dict[str, PackageTypes] |
| defaults: T.Dict[str, str] = {} |
| |
| def __missing__(self, key: str) -> PackageTypes: |
| if key in self.defaults: |
| modn = self.defaults[key] |
| importlib.import_module(f'mesonbuild.dependencies.{modn}') |
| |
| return self.data[key] |
| raise KeyError(key) |
| |
| def __contains__(self, key: object) -> bool: |
| return key in self.defaults or key in self.data |
| |
| # These must be defined in this file to avoid cyclical references. |
| packages = DependencyPackages() |
| _packages_accept_language: T.Set[str] = set() |
| |
| def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': |
| identifier: 'TV_DepID' = (('name', name), ) |
| from ..interpreter import permitted_dependency_kwargs |
| assert len(permitted_dependency_kwargs) == 19, \ |
| 'Extra kwargs have been added to dependency(), please review if it makes sense to handle it here' |
| for key, value in kwargs.items(): |
| # 'version' is irrelevant for caching; the caller must check version matches |
| # 'native' is handled above with `for_machine` |
| # 'required' is irrelevant for caching; the caller handles it separately |
| # 'fallback' and 'allow_fallback' is not part of the cache because, |
| # once a dependency has been found through a fallback, it should |
| # be used for the rest of the Meson run. |
| # 'default_options' is only used in fallback case |
| # 'not_found_message' has no impact on the dependency lookup |
| # 'include_type' is handled after the dependency lookup |
| if key in {'version', 'native', 'required', 'fallback', 'allow_fallback', 'default_options', |
| 'not_found_message', 'include_type'}: |
| continue |
| # All keyword arguments are strings, ints, or lists (or lists of lists) |
| if isinstance(value, list): |
| for i in value: |
| assert isinstance(i, str) |
| value = tuple(frozenset(listify(value))) |
| else: |
| assert isinstance(value, (str, bool, int)) |
| identifier = (*identifier, (key, value),) |
| return identifier |
| |
| display_name_map = { |
| 'boost': 'Boost', |
| 'cuda': 'CUDA', |
| 'dub': 'DUB', |
| 'gmock': 'GMock', |
| 'gtest': 'GTest', |
| 'hdf5': 'HDF5', |
| 'llvm': 'LLVM', |
| 'mpi': 'MPI', |
| 'netcdf': 'NetCDF', |
| 'openmp': 'OpenMP', |
| 'wxwidgets': 'WxWidgets', |
| } |
| |
| def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, object], candidates: T.Optional[T.List['DependencyGenerator']] = None) -> T.Union['ExternalDependency', NotFoundDependency]: |
| assert name |
| required = kwargs.get('required', True) |
| if not isinstance(required, bool): |
| raise DependencyException('Keyword "required" must be a boolean.') |
| if not isinstance(kwargs.get('method', ''), str): |
| raise DependencyException('Keyword "method" must be a string.') |
| lname = name.lower() |
| if lname not in _packages_accept_language and 'language' in kwargs: |
| raise DependencyException(f'{name} dependency does not accept "language" keyword argument') |
| if not isinstance(kwargs.get('version', ''), (str, list)): |
| raise DependencyException('Keyword "Version" must be string or list.') |
| |
| # display the dependency name with correct casing |
| display_name = display_name_map.get(lname, lname) |
| |
| for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST |
| |
| type_text = PerMachine('Build-time', 'Run-time')[for_machine] + ' dependency' |
| |
| # build a list of dependency methods to try |
| if candidates is None: |
| candidates = _build_external_dependency_list(name, env, for_machine, kwargs) |
| |
| pkg_exc: T.List[DependencyException] = [] |
| pkgdep: T.List[ExternalDependency] = [] |
| details = '' |
| |
| for c in candidates: |
| # try this dependency method |
| try: |
| d = c() |
| d._check_version() |
| pkgdep.append(d) |
| except DependencyException as e: |
| assert isinstance(c, functools.partial), 'for mypy' |
| bettermsg = f'Dependency lookup for {name} with method {c.func.log_tried()!r} failed: {e}' |
| mlog.debug(bettermsg) |
| e.args = (bettermsg,) |
| pkg_exc.append(e) |
| else: |
| pkg_exc.append(None) |
| details = d.log_details() |
| if details: |
| details = '(' + details + ') ' |
| if 'language' in kwargs: |
| details += 'for ' + d.language + ' ' |
| |
| # if the dependency was found |
| if d.found(): |
| |
| info: mlog.TV_LoggableList = [] |
| if d.version: |
| info.append(mlog.normal_cyan(d.version)) |
| |
| log_info = d.log_info() |
| if log_info: |
| info.append('(' + log_info + ')') |
| |
| mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.green('YES'), *info) |
| |
| return d |
| |
| # otherwise, the dependency could not be found |
| tried_methods = [d.log_tried() for d in pkgdep if d.log_tried()] |
| if tried_methods: |
| tried = mlog.format_list(tried_methods) |
| else: |
| tried = '' |
| |
| mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.red('NO'), |
| f'(tried {tried})' if tried else '') |
| |
| if required: |
| # if an exception occurred with the first detection method, re-raise it |
| # (on the grounds that it came from the preferred dependency detection |
| # method) |
| if pkg_exc and pkg_exc[0]: |
| raise pkg_exc[0] |
| |
| # we have a list of failed ExternalDependency objects, so we can report |
| # the methods we tried to find the dependency |
| raise DependencyException(f'Dependency "{name}" not found' + |
| (f', tried {tried}' if tried else '')) |
| |
| return NotFoundDependency(name, env) |
| |
| |
| def _build_external_dependency_list(name: str, env: 'Environment', for_machine: MachineChoice, |
| kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: |
| # First check if the method is valid |
| if 'method' in kwargs and kwargs['method'] not in [e.value for e in DependencyMethods]: |
| raise DependencyException('method {!r} is invalid'.format(kwargs['method'])) |
| |
| # Is there a specific dependency detector for this dependency? |
| lname = name.lower() |
| if lname in packages: |
| # Create the list of dependency object constructors using a factory |
| # class method, if one exists, otherwise the list just consists of the |
| # constructor |
| if isinstance(packages[lname], type): |
| entry1 = T.cast('T.Type[ExternalDependency]', packages[lname]) # mypy doesn't understand isinstance(..., type) |
| if issubclass(entry1, ExternalDependency): |
| func: T.Callable[[], 'ExternalDependency'] = functools.partial(entry1, env, kwargs) |
| dep = [func] |
| else: |
| entry2 = T.cast('T.Union[DependencyFactory, WrappedFactoryFunc]', packages[lname]) |
| dep = entry2(env, for_machine, kwargs) |
| return dep |
| |
| candidates: T.List['DependencyGenerator'] = [] |
| |
| if kwargs.get('method', 'auto') == 'auto': |
| # Just use the standard detection methods. |
| methods = ['pkg-config', 'extraframework', 'cmake'] |
| else: |
| # If it's explicitly requested, use that detection method (only). |
| methods = [kwargs['method']] |
| |
| # Exclusive to when it is explicitly requested |
| if 'dub' in methods: |
| from .dub import DubDependency |
| candidates.append(functools.partial(DubDependency, name, env, kwargs)) |
| |
| # Preferred first candidate for auto. |
| if 'pkg-config' in methods: |
| from .pkgconfig import PkgConfigDependency |
| candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) |
| |
| # On OSX only, try framework dependency detector. |
| if 'extraframework' in methods: |
| if env.machines[for_machine].is_darwin(): |
| from .framework import ExtraFrameworkDependency |
| candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) |
| |
| # Only use CMake: |
| # - if it's explicitly requested |
| # - as a last resort, since it might not work 100% (see #6113) |
| if 'cmake' in methods: |
| from .cmake import CMakeDependency |
| candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) |
| |
| return candidates |