1# Copyright 2013-2019 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 15# This file contains the detection logic for miscellaneous external dependencies. 16 17from pathlib import Path 18import functools 19import re 20import sysconfig 21import typing as T 22 23from .. import mlog 24from .. import mesonlib 25from ..environment import detect_cpu_family 26 27from .base import ( 28 DependencyException, DependencyMethods, ExternalDependency, 29 PkgConfigDependency, CMakeDependency, ConfigToolDependency, 30 factory_methods, DependencyFactory, 31) 32 33if T.TYPE_CHECKING: 34 from ..environment import Environment, MachineChoice 35 from .base import DependencyType # noqa: F401 36 37 38@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE}) 39def netcdf_factory(env: 'Environment', for_machine: 'MachineChoice', 40 kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyType']: 41 language = kwargs.get('language', 'c') 42 if language not in ('c', 'cpp', 'fortran'): 43 raise DependencyException('Language {} is not supported with NetCDF.'.format(language)) 44 45 candidates = [] # type: T.List['DependencyType'] 46 47 if DependencyMethods.PKGCONFIG in methods: 48 if language == 'fortran': 49 pkg = 'netcdf-fortran' 50 else: 51 pkg = 'netcdf' 52 53 candidates.append(functools.partial(PkgConfigDependency, pkg, env, kwargs, language=language)) 54 55 if DependencyMethods.CMAKE in methods: 56 candidates.append(functools.partial(CMakeDependency, 'NetCDF', env, kwargs, language=language)) 57 58 return candidates 59 60 61class OpenMPDependency(ExternalDependency): 62 # Map date of specification release (which is the macro value) to a version. 63 VERSIONS = { 64 '201811': '5.0', 65 '201611': '5.0-revision1', # This is supported by ICC 19.x 66 '201511': '4.5', 67 '201307': '4.0', 68 '201107': '3.1', 69 '200805': '3.0', 70 '200505': '2.5', 71 '200203': '2.0', 72 '199810': '1.0', 73 } 74 75 def __init__(self, environment, kwargs): 76 language = kwargs.get('language') 77 super().__init__('openmp', environment, kwargs, language=language) 78 self.is_found = False 79 if self.clib_compiler.get_id() == 'pgi': 80 # through at least PGI 19.4, there is no macro defined for OpenMP, but OpenMP 3.1 is supported. 81 self.version = '3.1' 82 self.is_found = True 83 self.compile_args = self.link_args = self.clib_compiler.openmp_flags() 84 return 85 try: 86 openmp_date = self.clib_compiler.get_define( 87 '_OPENMP', '', self.env, self.clib_compiler.openmp_flags(), [self], disable_cache=True)[0] 88 except mesonlib.EnvironmentException as e: 89 mlog.debug('OpenMP support not available in the compiler') 90 mlog.debug(e) 91 openmp_date = None 92 93 if openmp_date: 94 self.version = self.VERSIONS[openmp_date] 95 # Flang has omp_lib.h 96 header_names = ('omp.h', 'omp_lib.h') 97 for name in header_names: 98 if self.clib_compiler.has_header(name, '', self.env, dependencies=[self], disable_cache=True)[0]: 99 self.is_found = True 100 self.compile_args = self.clib_compiler.openmp_flags() 101 self.link_args = self.clib_compiler.openmp_link_flags() 102 break 103 if not self.is_found: 104 mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.') 105 106 107class ThreadDependency(ExternalDependency): 108 def __init__(self, name: str, environment, kwargs): 109 super().__init__(name, environment, kwargs) 110 self.is_found = True 111 # Happens if you are using a language with threads 112 # concept without C, such as plain Cuda. 113 if self.clib_compiler is None: 114 self.compile_args = [] 115 self.link_args = [] 116 else: 117 self.compile_args = self.clib_compiler.thread_flags(environment) 118 self.link_args = self.clib_compiler.thread_link_flags(environment) 119 120 @staticmethod 121 def get_methods(): 122 return [DependencyMethods.AUTO, DependencyMethods.CMAKE] 123 124 125class BlocksDependency(ExternalDependency): 126 def __init__(self, environment, kwargs): 127 super().__init__('blocks', environment, kwargs) 128 self.name = 'blocks' 129 self.is_found = False 130 131 if self.env.machines[self.for_machine].is_darwin(): 132 self.compile_args = [] 133 self.link_args = [] 134 else: 135 self.compile_args = ['-fblocks'] 136 self.link_args = ['-lBlocksRuntime'] 137 138 if not self.clib_compiler.has_header('Block.h', '', environment, disable_cache=True) or \ 139 not self.clib_compiler.find_library('BlocksRuntime', environment, []): 140 mlog.log(mlog.red('ERROR:'), 'BlocksRuntime not found.') 141 return 142 143 source = ''' 144 int main(int argc, char **argv) 145 { 146 int (^callback)(void) = ^ int (void) { return 0; }; 147 return callback(); 148 }''' 149 150 with self.clib_compiler.compile(source, extra_args=self.compile_args + self.link_args) as p: 151 if p.returncode != 0: 152 mlog.log(mlog.red('ERROR:'), 'Compiler does not support blocks extension.') 153 return 154 155 self.is_found = True 156 157 158class Python3DependencySystem(ExternalDependency): 159 def __init__(self, name, environment, kwargs): 160 super().__init__(name, environment, kwargs) 161 162 if not environment.machines.matches_build_machine(self.for_machine): 163 return 164 if not environment.machines[self.for_machine].is_windows(): 165 return 166 167 self.name = 'python3' 168 self.static = kwargs.get('static', False) 169 # We can only be sure that it is Python 3 at this point 170 self.version = '3' 171 self._find_libpy3_windows(environment) 172 173 @staticmethod 174 def get_windows_python_arch(): 175 pyplat = sysconfig.get_platform() 176 if pyplat == 'mingw': 177 pycc = sysconfig.get_config_var('CC') 178 if pycc.startswith('x86_64'): 179 return '64' 180 elif pycc.startswith(('i686', 'i386')): 181 return '32' 182 else: 183 mlog.log('MinGW Python built with unknown CC {!r}, please file' 184 'a bug'.format(pycc)) 185 return None 186 elif pyplat == 'win32': 187 return '32' 188 elif pyplat in ('win64', 'win-amd64'): 189 return '64' 190 mlog.log('Unknown Windows Python platform {!r}'.format(pyplat)) 191 return None 192 193 def get_windows_link_args(self): 194 pyplat = sysconfig.get_platform() 195 if pyplat.startswith('win'): 196 vernum = sysconfig.get_config_var('py_version_nodot') 197 if self.static: 198 libpath = Path('libs') / 'libpython{}.a'.format(vernum) 199 else: 200 comp = self.get_compiler() 201 if comp.id == "gcc": 202 libpath = 'python{}.dll'.format(vernum) 203 else: 204 libpath = Path('libs') / 'python{}.lib'.format(vernum) 205 lib = Path(sysconfig.get_config_var('base')) / libpath 206 elif pyplat == 'mingw': 207 if self.static: 208 libname = sysconfig.get_config_var('LIBRARY') 209 else: 210 libname = sysconfig.get_config_var('LDLIBRARY') 211 lib = Path(sysconfig.get_config_var('LIBDIR')) / libname 212 if not lib.exists(): 213 mlog.log('Could not find Python3 library {!r}'.format(str(lib))) 214 return None 215 return [str(lib)] 216 217 def _find_libpy3_windows(self, env): 218 ''' 219 Find python3 libraries on Windows and also verify that the arch matches 220 what we are building for. 221 ''' 222 pyarch = self.get_windows_python_arch() 223 if pyarch is None: 224 self.is_found = False 225 return 226 arch = detect_cpu_family(env.coredata.compilers.host) 227 if arch == 'x86': 228 arch = '32' 229 elif arch == 'x86_64': 230 arch = '64' 231 else: 232 # We can't cross-compile Python 3 dependencies on Windows yet 233 mlog.log('Unknown architecture {!r} for'.format(arch), 234 mlog.bold(self.name)) 235 self.is_found = False 236 return 237 # Pyarch ends in '32' or '64' 238 if arch != pyarch: 239 mlog.log('Need', mlog.bold(self.name), 'for {}-bit, but ' 240 'found {}-bit'.format(arch, pyarch)) 241 self.is_found = False 242 return 243 # This can fail if the library is not found 244 largs = self.get_windows_link_args() 245 if largs is None: 246 self.is_found = False 247 return 248 self.link_args = largs 249 # Compile args 250 inc = sysconfig.get_path('include') 251 platinc = sysconfig.get_path('platinclude') 252 self.compile_args = ['-I' + inc] 253 if inc != platinc: 254 self.compile_args.append('-I' + platinc) 255 self.version = sysconfig.get_config_var('py_version') 256 self.is_found = True 257 258 @staticmethod 259 def get_methods(): 260 if mesonlib.is_windows(): 261 return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG] 262 elif mesonlib.is_osx(): 263 return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK] 264 else: 265 return [DependencyMethods.PKGCONFIG] 266 267 def log_tried(self): 268 return 'sysconfig' 269 270class PcapDependencyConfigTool(ConfigToolDependency): 271 272 tools = ['pcap-config'] 273 tool_name = 'pcap-config' 274 275 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 276 super().__init__(name, environment, kwargs) 277 if not self.is_found: 278 return 279 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 280 self.link_args = self.get_config_value(['--libs'], 'link_args') 281 self.version = self.get_pcap_lib_version() 282 283 @staticmethod 284 def get_methods(): 285 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL] 286 287 def get_pcap_lib_version(self): 288 # Since we seem to need to run a program to discover the pcap version, 289 # we can't do that when cross-compiling 290 # FIXME: this should be handled if we have an exe_wrapper 291 if not self.env.machines.matches_build_machine(self.for_machine): 292 return None 293 294 v = self.clib_compiler.get_return_value('pcap_lib_version', 'string', 295 '#include <pcap.h>', self.env, [], [self]) 296 v = re.sub(r'libpcap version ', '', v) 297 v = re.sub(r' -- Apple version.*$', '', v) 298 return v 299 300 301class CupsDependencyConfigTool(ConfigToolDependency): 302 303 tools = ['cups-config'] 304 tool_name = 'cups-config' 305 306 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 307 super().__init__(name, environment, kwargs) 308 if not self.is_found: 309 return 310 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 311 self.link_args = self.get_config_value(['--ldflags', '--libs'], 'link_args') 312 313 @staticmethod 314 def get_methods(): 315 if mesonlib.is_osx(): 316 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE] 317 else: 318 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.CMAKE] 319 320 321class LibWmfDependencyConfigTool(ConfigToolDependency): 322 323 tools = ['libwmf-config'] 324 tool_name = 'libwmf-config' 325 326 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 327 super().__init__(name, environment, kwargs) 328 if not self.is_found: 329 return 330 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 331 self.link_args = self.get_config_value(['--libs'], 'link_args') 332 333 @staticmethod 334 def get_methods(): 335 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL] 336 337 338class LibGCryptDependencyConfigTool(ConfigToolDependency): 339 340 tools = ['libgcrypt-config'] 341 tool_name = 'libgcrypt-config' 342 343 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 344 super().__init__(name, environment, kwargs) 345 if not self.is_found: 346 return 347 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 348 self.link_args = self.get_config_value(['--libs'], 'link_args') 349 self.version = self.get_config_value(['--version'], 'version')[0] 350 351 @staticmethod 352 def get_methods(): 353 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL] 354 355 356class GpgmeDependencyConfigTool(ConfigToolDependency): 357 358 tools = ['gpgme-config'] 359 tool_name = 'gpg-config' 360 361 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): 362 super().__init__(name, environment, kwargs) 363 if not self.is_found: 364 return 365 self.compile_args = self.get_config_value(['--cflags'], 'compile_args') 366 self.link_args = self.get_config_value(['--libs'], 'link_args') 367 self.version = self.get_config_value(['--version'], 'version')[0] 368 369 @staticmethod 370 def get_methods(): 371 return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL] 372 373 374class ShadercDependency(ExternalDependency): 375 376 def __init__(self, environment, kwargs): 377 super().__init__('shaderc', environment, kwargs) 378 379 static_lib = 'shaderc_combined' 380 shared_lib = 'shaderc_shared' 381 382 libs = [shared_lib, static_lib] 383 if self.static: 384 libs.reverse() 385 386 cc = self.get_compiler() 387 388 for lib in libs: 389 self.link_args = cc.find_library(lib, environment, []) 390 if self.link_args is not None: 391 self.is_found = True 392 393 if self.static and lib != static_lib: 394 mlog.warning('Static library {!r} not found for dependency {!r}, may ' 395 'not be statically linked'.format(static_lib, self.name)) 396 397 break 398 399 def log_tried(self): 400 return 'system' 401 402 @staticmethod 403 def get_methods(): 404 return [DependencyMethods.SYSTEM, DependencyMethods.PKGCONFIG] 405 406 407@factory_methods({DependencyMethods.PKGCONFIG}) 408def curses_factory(env: 'Environment', for_machine: 'MachineChoice', 409 kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyType']: 410 candidates = [] # type: T.List['DependencyType'] 411 412 if DependencyMethods.PKGCONFIG in methods: 413 pkgconfig_files = ['ncurses', 'ncursesw'] 414 for pkg in pkgconfig_files: 415 candidates.append(functools.partial(PkgConfigDependency, pkg, env, kwargs)) 416 417 return candidates 418 419 420@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}) 421def shaderc_factory(env: 'Environment', for_machine: 'MachineChoice', 422 kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyType']: 423 """Custom DependencyFactory for ShaderC. 424 425 ShaderC's odd you get three different libraries from the same build 426 thing are just easier to represent as a separate function than 427 twisting DependencyFactory even more. 428 """ 429 candidates = [] # type: T.List['DependencyType'] 430 431 if DependencyMethods.PKGCONFIG in methods: 432 # ShaderC packages their shared and static libs together 433 # and provides different pkg-config files for each one. We 434 # smooth over this difference by handling the static 435 # keyword before handing off to the pkg-config handler. 436 shared_libs = ['shaderc'] 437 static_libs = ['shaderc_combined', 'shaderc_static'] 438 439 if kwargs.get('static', False): 440 c = [functools.partial(PkgConfigDependency, name, env, kwargs) 441 for name in static_libs + shared_libs] 442 else: 443 c = [functools.partial(PkgConfigDependency, name, env, kwargs) 444 for name in shared_libs + static_libs] 445 candidates.extend(c) 446 447 if DependencyMethods.SYSTEM in methods: 448 candidates.append(functools.partial(ShadercDependency, env, kwargs)) 449 450 return candidates 451 452 453cups_factory = DependencyFactory( 454 'cups', 455 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE], 456 configtool_class=CupsDependencyConfigTool, 457 cmake_name='Cups', 458) 459 460gpgme_factory = DependencyFactory( 461 'gpgme', 462 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], 463 configtool_class=GpgmeDependencyConfigTool, 464) 465 466libgcrypt_factory = DependencyFactory( 467 'libgcrypt', 468 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], 469 configtool_class=LibGCryptDependencyConfigTool, 470) 471 472libwmf_factory = DependencyFactory( 473 'libwmf', 474 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], 475 configtool_class=LibWmfDependencyConfigTool, 476) 477 478pcap_factory = DependencyFactory( 479 'pcap', 480 [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], 481 configtool_class=PcapDependencyConfigTool, 482 pkgconfig_name='libpcap', 483) 484 485python3_factory = DependencyFactory( 486 'python3', 487 [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.EXTRAFRAMEWORK], 488 system_class=Python3DependencySystem, 489 # There is no version number in the macOS version number 490 framework_name='Python', 491 # There is a python in /System/Library/Frameworks, but thats python 2.x, 492 # Python 3 will always be in /Library 493 extra_kwargs={'paths': ['/Library/Frameworks']}, 494) 495 496threads_factory = DependencyFactory( 497 'threads', 498 [DependencyMethods.SYSTEM, DependencyMethods.CMAKE], 499 cmake_name='Threads', 500 system_class=ThreadDependency, 501) 502