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
17import subprocess
18import shutil
19from pathlib import Path
20
21from .. import mlog
22from ..mesonlib import split_args, listify
23from .base import (DependencyException, DependencyMethods, ExternalDependency, ExternalProgram,
24                   PkgConfigDependency)
25
26class HDF5Dependency(ExternalDependency):
27
28    def __init__(self, environment, kwargs):
29        language = kwargs.get('language', 'c')
30        super().__init__('hdf5', environment, kwargs, language=language)
31        kwargs['required'] = False
32        kwargs['silent'] = True
33        self.is_found = False
34        methods = listify(self.methods)
35
36        if language not in ('c', 'cpp', 'fortran'):
37            raise DependencyException('Language {} is not supported with HDF5.'.format(language))
38
39        if set([DependencyMethods.AUTO, DependencyMethods.PKGCONFIG]).intersection(methods):
40            pkgconfig_files = ['hdf5', 'hdf5-serial']
41            PCEXE = shutil.which('pkg-config')
42            if PCEXE:
43                # some distros put hdf5-1.2.3.pc with version number in .pc filename.
44                ret = subprocess.run([PCEXE, '--list-all'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
45                                     universal_newlines=True)
46                if ret.returncode == 0:
47                    for pkg in ret.stdout.split('\n'):
48                        if pkg.startswith(('hdf5')):
49                            pkgconfig_files.append(pkg.split(' ', 1)[0])
50                    pkgconfig_files = list(set(pkgconfig_files))  # dedupe
51
52            for pkg in pkgconfig_files:
53                pkgdep = PkgConfigDependency(pkg, environment, kwargs, language=self.language)
54                if not pkgdep.found():
55                    continue
56
57                self.compile_args = pkgdep.get_compile_args()
58                # some broken pkgconfig don't actually list the full path to the needed includes
59                newinc = []
60                for arg in self.compile_args:
61                    if arg.startswith('-I'):
62                        stem = 'static' if kwargs.get('static', False) else 'shared'
63                        if (Path(arg[2:]) / stem).is_dir():
64                            newinc.append('-I' + str(Path(arg[2:]) / stem))
65                self.compile_args += newinc
66
67                # derive needed libraries by language
68                pd_link_args = pkgdep.get_link_args()
69                link_args = []
70                for larg in pd_link_args:
71                    lpath = Path(larg)
72                    # some pkg-config hdf5.pc (e.g. Ubuntu) don't include the commonly-used HL HDF5 libraries,
73                    # so let's add them if they exist
74                    # additionally, some pkgconfig HDF5 HL files are malformed so let's be sure to find HL anyway
75                    if lpath.is_file():
76                        hl = []
77                        if language == 'cpp':
78                            hl += ['_hl_cpp', '_cpp']
79                        elif language == 'fortran':
80                            hl += ['_hl_fortran', 'hl_fortran', '_fortran']
81                        hl += ['_hl']  # C HL library, always needed
82
83                        suffix = '.' + lpath.name.split('.', 1)[1]  # in case of .dll.a
84                        for h in hl:
85                            hlfn = lpath.parent / (lpath.name.split('.', 1)[0] + h + suffix)
86                            if hlfn.is_file():
87                                link_args.append(str(hlfn))
88                        # HDF5 C libs are required by other HDF5 languages
89                        link_args.append(larg)
90                    else:
91                        link_args.append(larg)
92
93                self.link_args = link_args
94                self.version = pkgdep.get_version()
95                self.is_found = True
96                self.pcdep = pkgdep
97                return
98
99        if DependencyMethods.AUTO in methods:
100            wrappers = {'c': 'h5cc', 'cpp': 'h5c++', 'fortran': 'h5fc'}
101            comp_args = []
102            link_args = []
103            # have to always do C as well as desired language
104            for lang in set([language, 'c']):
105                prog = ExternalProgram(wrappers[lang], silent=True)
106                if not prog.found():
107                    return
108                cmd = prog.get_command() + ['-show']
109                p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=15)
110                if p.returncode != 0:
111                    mlog.debug('Command', mlog.bold(cmd), 'failed to run:')
112                    mlog.debug(mlog.bold('Standard output\n'), p.stdout)
113                    mlog.debug(mlog.bold('Standard error\n'), p.stderr)
114                    return
115                args = split_args(p.stdout)
116                for arg in args[1:]:
117                    if arg.startswith(('-I', '-f', '-D')) or arg == '-pthread':
118                        comp_args.append(arg)
119                    elif arg.startswith(('-L', '-l', '-Wl')):
120                        link_args.append(arg)
121                    elif Path(arg).is_file():
122                        link_args.append(arg)
123            self.compile_args = comp_args
124            self.link_args = link_args
125            self.is_found = True
126            return
127
128    @staticmethod
129    def get_methods():
130        return [DependencyMethods.AUTO, DependencyMethods.PKGCONFIG]
131