1# Copyright 2013-2021 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from .base import Dependency, ExternalDependency, DependencyException, DependencyMethods, NotFoundDependency 16from .cmake import CMakeDependency 17from .dub import DubDependency 18from .framework import ExtraFrameworkDependency 19from .pkgconfig import PkgConfigDependency 20 21from ..mesonlib import listify, MachineChoice, PerMachine 22from .. import mlog 23import functools 24import typing as T 25 26if T.TYPE_CHECKING: 27 from ..environment import Environment 28 from .factory import DependencyFactory, WrappedFactoryFunc, DependencyGenerator 29 30# These must be defined in this file to avoid cyclical references. 31packages: T.Dict[ 32 str, 33 T.Union[T.Type[ExternalDependency], 'DependencyFactory', 'WrappedFactoryFunc'] 34] = {} 35_packages_accept_language: T.Set[str] = set() 36 37if T.TYPE_CHECKING: 38 TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]] 39 TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...] 40 41 42def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': 43 identifier: 'TV_DepID' = (('name', name), ) 44 from ..interpreter import permitted_dependency_kwargs 45 assert len(permitted_dependency_kwargs) == 19, \ 46 'Extra kwargs have been added to dependency(), please review if it makes sense to handle it here' 47 for key, value in kwargs.items(): 48 # 'version' is irrelevant for caching; the caller must check version matches 49 # 'native' is handled above with `for_machine` 50 # 'required' is irrelevant for caching; the caller handles it separately 51 # 'fallback' and 'allow_fallback' is not part of the cache because, 52 # once a dependency has been found through a fallback, it should 53 # be used for the rest of the Meson run. 54 # 'default_options' is only used in fallback case 55 # 'not_found_message' has no impact on the dependency lookup 56 # 'include_type' is handled after the dependency lookup 57 if key in ('version', 'native', 'required', 'fallback', 'allow_fallback', 'default_options', 58 'not_found_message', 'include_type'): 59 continue 60 # All keyword arguments are strings, ints, or lists (or lists of lists) 61 if isinstance(value, list): 62 value = frozenset(listify(value)) 63 for i in value: 64 assert isinstance(i, str) 65 else: 66 assert isinstance(value, (str, bool, int)) 67 identifier += (key, value) 68 return identifier 69 70display_name_map = { 71 'boost': 'Boost', 72 'cuda': 'CUDA', 73 'dub': 'DUB', 74 'gmock': 'GMock', 75 'gtest': 'GTest', 76 'hdf5': 'HDF5', 77 'llvm': 'LLVM', 78 'mpi': 'MPI', 79 'netcdf': 'NetCDF', 80 'openmp': 'OpenMP', 81 'wxwidgets': 'WxWidgets', 82} 83 84def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, object]) -> T.Union['ExternalDependency', NotFoundDependency]: 85 assert(name) 86 required = kwargs.get('required', True) 87 if not isinstance(required, bool): 88 raise DependencyException('Keyword "required" must be a boolean.') 89 if not isinstance(kwargs.get('method', ''), str): 90 raise DependencyException('Keyword "method" must be a string.') 91 lname = name.lower() 92 if lname not in _packages_accept_language and 'language' in kwargs: 93 raise DependencyException(f'{name} dependency does not accept "language" keyword argument') 94 if not isinstance(kwargs.get('version', ''), (str, list)): 95 raise DependencyException('Keyword "Version" must be string or list.') 96 97 # display the dependency name with correct casing 98 display_name = display_name_map.get(lname, lname) 99 100 for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST 101 102 type_text = PerMachine('Build-time', 'Run-time')[for_machine] + ' dependency' 103 104 # build a list of dependency methods to try 105 candidates = _build_external_dependency_list(name, env, for_machine, kwargs) 106 107 pkg_exc: T.List[DependencyException] = [] 108 pkgdep: T.List[ExternalDependency] = [] 109 details = '' 110 111 for c in candidates: 112 # try this dependency method 113 try: 114 d = c() 115 d._check_version() 116 pkgdep.append(d) 117 except DependencyException as e: 118 pkg_exc.append(e) 119 mlog.debug(str(e)) 120 else: 121 pkg_exc.append(None) 122 details = d.log_details() 123 if details: 124 details = '(' + details + ') ' 125 if 'language' in kwargs: 126 details += 'for ' + d.language + ' ' 127 128 # if the dependency was found 129 if d.found(): 130 131 info: mlog.TV_LoggableList = [] 132 if d.version: 133 info.append(mlog.normal_cyan(d.version)) 134 135 log_info = d.log_info() 136 if log_info: 137 info.append('(' + log_info + ')') 138 139 mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.green('YES'), *info) 140 141 return d 142 143 # otherwise, the dependency could not be found 144 tried_methods = [d.log_tried() for d in pkgdep if d.log_tried()] 145 if tried_methods: 146 tried = '{}'.format(mlog.format_list(tried_methods)) 147 else: 148 tried = '' 149 150 mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.red('NO'), 151 f'(tried {tried})' if tried else '') 152 153 if required: 154 # if an exception occurred with the first detection method, re-raise it 155 # (on the grounds that it came from the preferred dependency detection 156 # method) 157 if pkg_exc and pkg_exc[0]: 158 raise pkg_exc[0] 159 160 # we have a list of failed ExternalDependency objects, so we can report 161 # the methods we tried to find the dependency 162 raise DependencyException('Dependency "%s" not found' % (name) + 163 (', tried %s' % (tried) if tried else '')) 164 165 return NotFoundDependency(env) 166 167 168def _build_external_dependency_list(name: str, env: 'Environment', for_machine: MachineChoice, 169 kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: 170 # First check if the method is valid 171 if 'method' in kwargs and kwargs['method'] not in [e.value for e in DependencyMethods]: 172 raise DependencyException('method {!r} is invalid'.format(kwargs['method'])) 173 174 # Is there a specific dependency detector for this dependency? 175 lname = name.lower() 176 if lname in packages: 177 # Create the list of dependency object constructors using a factory 178 # class method, if one exists, otherwise the list just consists of the 179 # constructor 180 if isinstance(packages[lname], type): 181 entry1 = T.cast(T.Type[ExternalDependency], packages[lname]) # mypy doesn't understand isinstance(..., type) 182 if issubclass(entry1, ExternalDependency): 183 # TODO: somehow make mypy understand that entry1(env, kwargs) is OK... 184 func: T.Callable[[], 'ExternalDependency'] = lambda: entry1(env, kwargs) # type: ignore 185 dep = [func] 186 else: 187 entry2 = T.cast(T.Union['DependencyFactory', 'WrappedFactoryFunc'], packages[lname]) 188 dep = entry2(env, for_machine, kwargs) 189 return dep 190 191 candidates: T.List['DependencyGenerator'] = [] 192 193 # If it's explicitly requested, use the dub detection method (only) 194 if 'dub' == kwargs.get('method', ''): 195 candidates.append(functools.partial(DubDependency, name, env, kwargs)) 196 return candidates 197 198 # If it's explicitly requested, use the pkgconfig detection method (only) 199 if 'pkg-config' == kwargs.get('method', ''): 200 candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) 201 return candidates 202 203 # If it's explicitly requested, use the CMake detection method (only) 204 if 'cmake' == kwargs.get('method', ''): 205 candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) 206 return candidates 207 208 # If it's explicitly requested, use the Extraframework detection method (only) 209 if 'extraframework' == kwargs.get('method', ''): 210 # On OSX, also try framework dependency detector 211 if env.machines[for_machine].is_darwin(): 212 candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) 213 return candidates 214 215 # Otherwise, just use the pkgconfig and cmake dependency detector 216 if 'auto' == kwargs.get('method', 'auto'): 217 candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) 218 219 # On OSX, also try framework dependency detector 220 if env.machines[for_machine].is_darwin(): 221 candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) 222 223 # Only use CMake as a last resort, since it might not work 100% (see #6113) 224 candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) 225 226 return candidates 227