blob: 72c7cf0a8825a5563f6a63569a3be0208a452229 [file] [log] [blame]
# SPDX-License-Identifier: Apache-2.0
# Copyright 2013-2019 The Meson development team
# 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, CMakeDependencyFactory
from .configtool import ConfigToolDependency
from .detect import packages
from .factory import DependencyFactory, factory_methods
from .pkgconfig import PkgConfigDependency
if T.TYPE_CHECKING:
from ..environment import Environment
from .factory import DependencyGenerator
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE})
def netcdf_factory(env: 'Environment',
for_machine: 'mesonlib.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
packages['netcdf'] = netcdf_factory
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 <dlfcn.h>', 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 = {
'202111': '5.2',
'202011': '5.1',
'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
if self.clib_compiler.get_id() == 'clang-cl':
# this is necessary for clang-cl, see https://github.com/mesonbuild/meson/issues/5298
clangcl_openmp_link_args = self.clib_compiler.find_library("libomp", self.env, [])
if not clangcl_openmp_link_args:
mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but libomp for clang-cl missing.')
return
self.link_args.extend(clangcl_openmp_link_args)
# 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.extend(self.clib_compiler.openmp_flags())
self.link_args.extend(self.clib_compiler.openmp_link_flags())
break
if not self.is_found:
mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.')
packages['openmp'] = OpenMPDependency
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
packages['blocks'] = BlocksDependency
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 <pcap.h>', 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 elegantly 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 <iconv.h>\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 <libintl.h>\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
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 <openssl/opensslv.h>', env, [], [self])[0]
# openssl < 3 only
if not self.version:
version_hex = self.clib_compiler.get_define('OPENSSL_VERSION_NUMBER', '#include <openssl/opensslv.h>', 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)
class ObjFWDependency(ConfigToolDependency):
tools = ['objfw-config']
tool_name = 'objfw-config'
def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
super().__init__('objfw', environment, kwargs)
self.feature_since = ('1.5.0', '')
if not self.is_found:
return
# TODO: Expose --reexport
# TODO: Expose --framework-libs
extra_flags = []
for module in mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')):
extra_flags.append('--package')
extra_flags.append(module)
# TODO: Once Meson supports adding flags per language, only add --objcflags to ObjC
self.compile_args = self.get_config_value(['--cppflags', '--cflags', '--objcflags'] + extra_flags, 'compile_args')
self.link_args = self.get_config_value(['--ldflags', '--libs'] + extra_flags, 'link_args')
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM})
def curses_factory(env: 'Environment',
for_machine: 'mesonlib.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
packages['curses'] = curses_factory
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM})
def shaderc_factory(env: 'Environment',
for_machine: 'mesonlib.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
packages['shaderc'] = shaderc_factory
packages['cups'] = cups_factory = DependencyFactory(
'cups',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE],
configtool_class=CupsDependencyConfigTool,
cmake_name='Cups',
)
packages['dl'] = dl_factory = DependencyFactory(
'dl',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=DlBuiltinDependency,
system_class=DlSystemDependency,
)
packages['gpgme'] = gpgme_factory = DependencyFactory(
'gpgme',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=GpgmeDependencyConfigTool,
)
packages['libgcrypt'] = libgcrypt_factory = DependencyFactory(
'libgcrypt',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=LibGCryptDependencyConfigTool,
)
packages['libwmf'] = libwmf_factory = DependencyFactory(
'libwmf',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=LibWmfDependencyConfigTool,
)
packages['pcap'] = pcap_factory = DependencyFactory(
'pcap',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=PcapDependencyConfigTool,
pkgconfig_name='libpcap',
)
packages['threads'] = threads_factory = DependencyFactory(
'threads',
[DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
cmake_name='Threads',
system_class=ThreadDependency,
)
packages['iconv'] = iconv_factory = DependencyFactory(
'iconv',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=IconvBuiltinDependency,
system_class=IconvSystemDependency,
)
packages['intl'] = intl_factory = DependencyFactory(
'intl',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=IntlBuiltinDependency,
system_class=IntlSystemDependency,
)
packages['openssl'] = openssl_factory = DependencyFactory(
'openssl',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::Crypto', 'OpenSSL::SSL']),
)
packages['libcrypto'] = libcrypto_factory = DependencyFactory(
'libcrypto',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::Crypto']),
)
packages['libssl'] = libssl_factory = DependencyFactory(
'libssl',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::SSL']),
)
packages['objfw'] = ObjFWDependency