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