diff options
Diffstat (limited to '')
-rw-r--r-- | mesonbuild/dependencies/detect.py | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/mesonbuild/dependencies/detect.py b/mesonbuild/dependencies/detect.py new file mode 100644 index 0000000..4c7a477 --- /dev/null +++ b/mesonbuild/dependencies/detect.py @@ -0,0 +1,227 @@ +# Copyright 2013-2021 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 __future__ import annotations + +from .base import ExternalDependency, DependencyException, DependencyMethods, NotFoundDependency +from .cmake import CMakeDependency +from .dub import DubDependency +from .framework import ExtraFrameworkDependency +from .pkgconfig import PkgConfigDependency + +from ..mesonlib import listify, MachineChoice, PerMachine +from .. import mlog +import functools +import typing as T + +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], ...] + +# These must be defined in this file to avoid cyclical references. +packages: T.Dict[ + str, + T.Union[T.Type[ExternalDependency], 'DependencyFactory', 'WrappedFactoryFunc'] +] = {} +_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]) -> 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 + 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 it's explicitly requested, use the dub detection method (only) + if 'dub' == kwargs.get('method', ''): + candidates.append(functools.partial(DubDependency, name, env, kwargs)) + return candidates + + # If it's explicitly requested, use the pkgconfig detection method (only) + if 'pkg-config' == kwargs.get('method', ''): + candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) + return candidates + + # If it's explicitly requested, use the CMake detection method (only) + if 'cmake' == kwargs.get('method', ''): + candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) + return candidates + + # If it's explicitly requested, use the Extraframework detection method (only) + if 'extraframework' == kwargs.get('method', ''): + # On OSX, also try framework dependency detector + if env.machines[for_machine].is_darwin(): + candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) + return candidates + + # Otherwise, just use the pkgconfig and cmake dependency detector + if 'auto' == kwargs.get('method', 'auto'): + candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) + + # On OSX, also try framework dependency detector + if env.machines[for_machine].is_darwin(): + candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) + + # Only use CMake as a last resort, since it might not work 100% (see #6113) + candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) + + return candidates |