# Copyright 2013-2019 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. # This file contains the detection logic for miscellaneous external dependencies. from __future__ import annotations import functools import re import typing as T from .. import mesonlib from .. import mlog from .base import DependencyException, DependencyMethods from .base import BuiltinDependency, SystemDependency from .cmake import CMakeDependency from .configtool import ConfigToolDependency from .factory import DependencyFactory, factory_methods from .pkgconfig import PkgConfigDependency if T.TYPE_CHECKING: from ..environment import Environment, MachineChoice from .factory import DependencyGenerator @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE}) def netcdf_factory(env: 'Environment', for_machine: 'MachineChoice', kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: language = kwargs.get('language', 'c') if language not in ('c', 'cpp', 'fortran'): raise DependencyException(f'Language {language} is not supported with NetCDF.') candidates: T.List['DependencyGenerator'] = [] if DependencyMethods.PKGCONFIG in methods: if language == 'fortran': pkg = 'netcdf-fortran' else: pkg = 'netcdf' candidates.append(functools.partial(PkgConfigDependency, pkg, env, kwargs, language=language)) if DependencyMethods.CMAKE in methods: candidates.append(functools.partial(CMakeDependency, 'NetCDF', env, kwargs, language=language)) return candidates class DlBuiltinDependency(BuiltinDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.62.0', "consider checking for `dlopen` with and without `find_library('dl')`") if self.clib_compiler.has_function('dlopen', '#include ', env)[0]: self.is_found = True class DlSystemDependency(SystemDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.62.0', "consider checking for `dlopen` with and without `find_library('dl')`") h = self.clib_compiler.has_header('dlfcn.h', '', env) self.link_args = self.clib_compiler.find_library('dl', env, [], self.libtype) if h[0] and self.link_args: self.is_found = True class OpenMPDependency(SystemDependency): # Map date of specification release (which is the macro value) to a version. VERSIONS = { '201811': '5.0', '201611': '5.0-revision1', # This is supported by ICC 19.x '201511': '4.5', '201307': '4.0', '201107': '3.1', '200805': '3.0', '200505': '2.5', '200203': '2.0', '199810': '1.0', } def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: language = kwargs.get('language') super().__init__('openmp', environment, kwargs, language=language) self.is_found = False if self.clib_compiler.get_id() == 'nagfor': # No macro defined for OpenMP, but OpenMP 3.1 is supported. self.version = '3.1' self.is_found = True self.compile_args = self.link_args = self.clib_compiler.openmp_flags() return if self.clib_compiler.get_id() == 'pgi': # through at least PGI 19.4, there is no macro defined for OpenMP, but OpenMP 3.1 is supported. self.version = '3.1' self.is_found = True self.compile_args = self.link_args = self.clib_compiler.openmp_flags() return try: openmp_date = self.clib_compiler.get_define( '_OPENMP', '', self.env, self.clib_compiler.openmp_flags(), [self], disable_cache=True)[0] except mesonlib.EnvironmentException as e: mlog.debug('OpenMP support not available in the compiler') mlog.debug(e) openmp_date = None if openmp_date: try: self.version = self.VERSIONS[openmp_date] except KeyError: mlog.debug(f'Could not find an OpenMP version matching {openmp_date}') if openmp_date == '_OPENMP': mlog.debug('This can be caused by flags such as gcc\'s `-fdirectives-only`, which affect preprocessor behavior.') return # Flang has omp_lib.h header_names = ('omp.h', 'omp_lib.h') for name in header_names: if self.clib_compiler.has_header(name, '', self.env, dependencies=[self], disable_cache=True)[0]: self.is_found = True self.compile_args = self.clib_compiler.openmp_flags() self.link_args = self.clib_compiler.openmp_link_flags() break if not self.is_found: mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.') class ThreadDependency(SystemDependency): def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: super().__init__(name, environment, kwargs) self.is_found = True # Happens if you are using a language with threads # concept without C, such as plain Cuda. if not self.clib_compiler: self.compile_args = [] self.link_args = [] else: self.compile_args = self.clib_compiler.thread_flags(environment) self.link_args = self.clib_compiler.thread_link_flags(environment) class BlocksDependency(SystemDependency): def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: super().__init__('blocks', environment, kwargs) self.name = 'blocks' self.is_found = False if self.env.machines[self.for_machine].is_darwin(): self.compile_args = [] self.link_args = [] else: self.compile_args = ['-fblocks'] self.link_args = ['-lBlocksRuntime'] if not self.clib_compiler.has_header('Block.h', '', environment, disable_cache=True) or \ not self.clib_compiler.find_library('BlocksRuntime', environment, []): mlog.log(mlog.red('ERROR:'), 'BlocksRuntime not found.') return source = ''' int main(int argc, char **argv) { int (^callback)(void) = ^ int (void) { return 0; }; return callback(); }''' with self.clib_compiler.compile(source, extra_args=self.compile_args + self.link_args) as p: if p.returncode != 0: mlog.log(mlog.red('ERROR:'), 'Compiler does not support blocks extension.') return self.is_found = True class PcapDependencyConfigTool(ConfigToolDependency): tools = ['pcap-config'] tool_name = 'pcap-config' # version 1.10.2 added error checking for invalid arguments # version 1.10.3 will hopefully add actual support for --version skip_version = '--help' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') if self.version is None: # older pcap-config versions don't support this self.version = self.get_pcap_lib_version() def get_pcap_lib_version(self) -> T.Optional[str]: # Since we seem to need to run a program to discover the pcap version, # we can't do that when cross-compiling # FIXME: this should be handled if we have an exe_wrapper if not self.env.machines.matches_build_machine(self.for_machine): return None v = self.clib_compiler.get_return_value('pcap_lib_version', 'string', '#include ', self.env, [], [self]) v = re.sub(r'libpcap version ', '', str(v)) v = re.sub(r' -- Apple version.*$', '', v) return v class CupsDependencyConfigTool(ConfigToolDependency): tools = ['cups-config'] tool_name = 'cups-config' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--ldflags', '--libs'], 'link_args') class LibWmfDependencyConfigTool(ConfigToolDependency): tools = ['libwmf-config'] tool_name = 'libwmf-config' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') class LibGCryptDependencyConfigTool(ConfigToolDependency): tools = ['libgcrypt-config'] tool_name = 'libgcrypt-config' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') self.version = self.get_config_value(['--version'], 'version')[0] class GpgmeDependencyConfigTool(ConfigToolDependency): tools = ['gpgme-config'] tool_name = 'gpg-config' def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') self.version = self.get_config_value(['--version'], 'version')[0] class ShadercDependency(SystemDependency): def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__('shaderc', environment, kwargs) static_lib = 'shaderc_combined' shared_lib = 'shaderc_shared' libs = [shared_lib, static_lib] if self.static: libs.reverse() cc = self.get_compiler() for lib in libs: self.link_args = cc.find_library(lib, environment, []) if self.link_args is not None: self.is_found = True if self.static and lib != static_lib: mlog.warning(f'Static library {static_lib!r} not found for dependency ' f'{self.name!r}, may not be statically linked') break class CursesConfigToolDependency(ConfigToolDependency): """Use the curses config tools.""" tool = 'curses-config' # ncurses5.4-config is for macOS Catalina tools = ['ncursesw6-config', 'ncursesw5-config', 'ncurses6-config', 'ncurses5-config', 'ncurses5.4-config'] def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): super().__init__(name, env, kwargs, language) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') self.link_args = self.get_config_value(['--libs'], 'link_args') class CursesSystemDependency(SystemDependency): """Curses dependency the hard way. This replaces hand rolled find_library() and has_header() calls. We provide this for portability reasons, there are a large number of curses implementations, and the differences between them can be very annoying. """ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) candidates = [ ('pdcurses', ['pdcurses/curses.h']), ('ncursesw', ['ncursesw/ncurses.h', 'ncurses.h']), ('ncurses', ['ncurses/ncurses.h', 'ncurses/curses.h', 'ncurses.h']), ('curses', ['curses.h']), ] # Not sure how else to elegently break out of both loops for lib, headers in candidates: l = self.clib_compiler.find_library(lib, env, []) if l: for header in headers: h = self.clib_compiler.has_header(header, '', env) if h[0]: self.is_found = True self.link_args = l # Not sure how to find version for non-ncurses curses # implementations. The one in illumos/OpenIndiana # doesn't seem to have a version defined in the header. if lib.startswith('ncurses'): v, _ = self.clib_compiler.get_define('NCURSES_VERSION', f'#include <{header}>', env, [], [self]) self.version = v.strip('"') if lib.startswith('pdcurses'): v_major, _ = self.clib_compiler.get_define('PDC_VER_MAJOR', f'#include <{header}>', env, [], [self]) v_minor, _ = self.clib_compiler.get_define('PDC_VER_MINOR', f'#include <{header}>', env, [], [self]) self.version = f'{v_major}.{v_minor}' # Check the version if possible, emit a warning if we can't req = kwargs.get('version') if req: if self.version: self.is_found = mesonlib.version_compare(self.version, req) else: mlog.warning('Cannot determine version of curses to compare against.') if self.is_found: mlog.debug('Curses library:', l) mlog.debug('Curses header:', header) break if self.is_found: break class IconvBuiltinDependency(BuiltinDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.60.0', "consider checking for `iconv_open` with and without `find_library('iconv')`") code = '''#include \n\nint main() {\n iconv_open("","");\n}''' # [ignore encoding] this is C, not python, Mr. Lint if self.clib_compiler.links(code, env)[0]: self.is_found = True class IconvSystemDependency(SystemDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.60.0', "consider checking for `iconv_open` with and without find_library('iconv')") h = self.clib_compiler.has_header('iconv.h', '', env) self.link_args = self.clib_compiler.find_library('iconv', env, [], self.libtype) if h[0] and self.link_args: self.is_found = True class IntlBuiltinDependency(BuiltinDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.59.0', "consider checking for `ngettext` with and without `find_library('intl')`") code = '''#include \n\nint main() {\n gettext("Hello world");\n}''' if self.clib_compiler.links(code, env)[0]: self.is_found = True class IntlSystemDependency(SystemDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) self.feature_since = ('0.59.0', "consider checking for `ngettext` with and without `find_library('intl')`") h = self.clib_compiler.has_header('libintl.h', '', env) self.link_args = self.clib_compiler.find_library('intl', env, [], self.libtype) if h[0] and self.link_args: self.is_found = True if self.static: if not self._add_sub_dependency(iconv_factory(env, self.for_machine, {'static': True})): self.is_found = False return class OpensslSystemDependency(SystemDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) dependency_kwargs = { 'method': 'system', 'static': self.static, } if not self.clib_compiler.has_header('openssl/ssl.h', '', env)[0]: return # openssl >= 3 only self.version = self.clib_compiler.get_define('OPENSSL_VERSION_STR', '#include ', env, [], [self])[0] # openssl < 3 only if not self.version: version_hex = self.clib_compiler.get_define('OPENSSL_VERSION_NUMBER', '#include ', env, [], [self])[0] if not version_hex: return version_hex = version_hex.rstrip('L') version_ints = [((int(version_hex.rstrip('L'), 16) >> 4 + i) & 0xFF) for i in (24, 16, 8, 0)] # since this is openssl, the format is 1.2.3a in four parts self.version = '.'.join(str(i) for i in version_ints[:3]) + chr(ord('a') + version_ints[3] - 1) if name == 'openssl': if self._add_sub_dependency(libssl_factory(env, self.for_machine, dependency_kwargs)) and \ self._add_sub_dependency(libcrypto_factory(env, self.for_machine, dependency_kwargs)): self.is_found = True return else: self.link_args = self.clib_compiler.find_library(name.lstrip('lib'), env, [], self.libtype) if not self.link_args: return if not self.static: self.is_found = True else: if name == 'libssl': if self._add_sub_dependency(libcrypto_factory(env, self.for_machine, dependency_kwargs)): self.is_found = True elif name == 'libcrypto': use_threads = self.clib_compiler.has_header_symbol('openssl/opensslconf.h', 'OPENSSL_THREADS', '', env, dependencies=[self])[0] if not use_threads or self._add_sub_dependency(threads_factory(env, self.for_machine, {})): self.is_found = True # only relevant on platforms where it is distributed with the libc, in which case it always succeeds sublib = self.clib_compiler.find_library('dl', env, [], self.libtype) if sublib: self.link_args.extend(sublib) @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM}) def curses_factory(env: 'Environment', for_machine: 'MachineChoice', kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: candidates: T.List['DependencyGenerator'] = [] if DependencyMethods.PKGCONFIG in methods: pkgconfig_files = ['pdcurses', 'ncursesw', 'ncurses', 'curses'] for pkg in pkgconfig_files: candidates.append(functools.partial(PkgConfigDependency, pkg, env, kwargs)) # There are path handling problems with these methods on msys, and they # don't apply to windows otherwise (cygwin is handled separately from # windows) if not env.machines[for_machine].is_windows(): if DependencyMethods.CONFIG_TOOL in methods: candidates.append(functools.partial(CursesConfigToolDependency, 'curses', env, kwargs)) if DependencyMethods.SYSTEM in methods: candidates.append(functools.partial(CursesSystemDependency, 'curses', env, kwargs)) return candidates @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}) def shaderc_factory(env: 'Environment', for_machine: 'MachineChoice', kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: """Custom DependencyFactory for ShaderC. ShaderC's odd you get three different libraries from the same build thing are just easier to represent as a separate function than twisting DependencyFactory even more. """ candidates: T.List['DependencyGenerator'] = [] if DependencyMethods.PKGCONFIG in methods: # ShaderC packages their shared and static libs together # and provides different pkg-config files for each one. We # smooth over this difference by handling the static # keyword before handing off to the pkg-config handler. shared_libs = ['shaderc'] static_libs = ['shaderc_combined', 'shaderc_static'] if kwargs.get('static', env.coredata.get_option(mesonlib.OptionKey('prefer_static'))): c = [functools.partial(PkgConfigDependency, name, env, kwargs) for name in static_libs + shared_libs] else: c = [functools.partial(PkgConfigDependency, name, env, kwargs) for name in shared_libs + static_libs] candidates.extend(c) if DependencyMethods.SYSTEM in methods: candidates.append(functools.partial(ShadercDependency, env, kwargs)) return candidates cups_factory = DependencyFactory( 'cups', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE], configtool_class=CupsDependencyConfigTool, cmake_name='Cups', ) dl_factory = DependencyFactory( 'dl', [DependencyMethods.BUILTIN, DependencyMethods.SYSTEM], builtin_class=DlBuiltinDependency, system_class=DlSystemDependency, ) gpgme_factory = DependencyFactory( 'gpgme', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], configtool_class=GpgmeDependencyConfigTool, ) libgcrypt_factory = DependencyFactory( 'libgcrypt', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], configtool_class=LibGCryptDependencyConfigTool, ) libwmf_factory = DependencyFactory( 'libwmf', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], configtool_class=LibWmfDependencyConfigTool, ) pcap_factory = DependencyFactory( 'pcap', [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], configtool_class=PcapDependencyConfigTool, pkgconfig_name='libpcap', ) threads_factory = DependencyFactory( 'threads', [DependencyMethods.SYSTEM, DependencyMethods.CMAKE], cmake_name='Threads', system_class=ThreadDependency, ) iconv_factory = DependencyFactory( 'iconv', [DependencyMethods.BUILTIN, DependencyMethods.SYSTEM], builtin_class=IconvBuiltinDependency, system_class=IconvSystemDependency, ) intl_factory = DependencyFactory( 'intl', [DependencyMethods.BUILTIN, DependencyMethods.SYSTEM], builtin_class=IntlBuiltinDependency, system_class=IntlSystemDependency, ) openssl_factory = DependencyFactory( 'openssl', [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE], system_class=OpensslSystemDependency, cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::Crypto', 'OpenSSL::SSL'])), ) libcrypto_factory = DependencyFactory( 'libcrypto', [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE], system_class=OpensslSystemDependency, cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::Crypto'])), ) libssl_factory = DependencyFactory( 'libssl', [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE], system_class=OpensslSystemDependency, cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::SSL'])), )