| # 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 .base import Dependency, 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 | |
| # 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() | |
| if T.TYPE_CHECKING: | |
| TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]] | |
| TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...] | |
| 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): | |
| value = frozenset(listify(value)) | |
| for i in value: | |
| assert isinstance(i, str) | |
| else: | |
| assert isinstance(value, (str, bool, int)) | |
| 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: | |
| pkg_exc.append(e) | |
| mlog.debug(str(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 = '{}'.format(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('Dependency "%s" not found' % (name) + | |
| (', tried %s' % (tried) if tried else '')) | |
| return NotFoundDependency(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): | |
| # TODO: somehow make mypy understand that entry1(env, kwargs) is OK... | |
| func: T.Callable[[], 'ExternalDependency'] = lambda: entry1(env, kwargs) # type: ignore | |
| 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 |