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 functools 18import os 19import re 20import shutil 21import subprocess 22from pathlib import Path 23 24from ..mesonlib import OrderedSet, join_args 25from .base import DependencyException, DependencyMethods 26from .configtool import ConfigToolDependency 27from .pkgconfig import PkgConfigDependency 28from .factory import factory_methods 29import typing as T 30 31if T.TYPE_CHECKING: 32 from .base import Dependency 33 from .factory import DependencyGenerator 34 from ..environment import Environment 35 from ..mesonlib import MachineChoice 36 37 38class HDF5PkgConfigDependency(PkgConfigDependency): 39 40 """Handle brokenness in the HDF5 pkg-config files.""" 41 42 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: 43 language = language or 'c' 44 if language not in {'c', 'cpp', 'fortran'}: 45 raise DependencyException(f'Language {language} is not supported with HDF5.') 46 47 super().__init__(name, environment, kwargs, language) 48 if not self.is_found: 49 return 50 51 # some broken pkgconfig don't actually list the full path to the needed includes 52 newinc = [] # type: T.List[str] 53 for arg in self.compile_args: 54 if arg.startswith('-I'): 55 stem = 'static' if kwargs.get('static', False) else 'shared' 56 if (Path(arg[2:]) / stem).is_dir(): 57 newinc.append('-I' + str(Path(arg[2:]) / stem)) 58 self.compile_args += newinc 59 60 link_args = [] # type: T.List[str] 61 for larg in self.get_link_args(): 62 lpath = Path(larg) 63 # some pkg-config hdf5.pc (e.g. Ubuntu) don't include the commonly-used HL HDF5 libraries, 64 # so let's add them if they exist 65 # additionally, some pkgconfig HDF5 HL files are malformed so let's be sure to find HL anyway 66 if lpath.is_file(): 67 hl = [] 68 if language == 'cpp': 69 hl += ['_hl_cpp', '_cpp'] 70 elif language == 'fortran': 71 hl += ['_hl_fortran', 'hl_fortran', '_fortran'] 72 hl += ['_hl'] # C HL library, always needed 73 74 suffix = '.' + lpath.name.split('.', 1)[1] # in case of .dll.a 75 for h in hl: 76 hlfn = lpath.parent / (lpath.name.split('.', 1)[0] + h + suffix) 77 if hlfn.is_file(): 78 link_args.append(str(hlfn)) 79 # HDF5 C libs are required by other HDF5 languages 80 link_args.append(larg) 81 else: 82 link_args.append(larg) 83 84 self.link_args = link_args 85 86 87class HDF5ConfigToolDependency(ConfigToolDependency): 88 89 """Wrapper around hdf5 binary config tools.""" 90 91 version_arg = '-showconfig' 92 93 def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: 94 language = language or 'c' 95 if language not in {'c', 'cpp', 'fortran'}: 96 raise DependencyException(f'Language {language} is not supported with HDF5.') 97 98 if language == 'c': 99 cenv = 'CC' 100 tools = ['h5cc'] 101 elif language == 'cpp': 102 cenv = 'CXX' 103 tools = ['h5c++'] 104 elif language == 'fortran': 105 cenv = 'FC' 106 tools = ['h5fc'] 107 else: 108 raise DependencyException('How did you get here?') 109 110 # We need this before we call super() 111 for_machine = self.get_for_machine_from_kwargs(kwargs) 112 113 nkwargs = kwargs.copy() 114 nkwargs['tools'] = tools 115 116 # Override the compiler that the config tools are going to use by 117 # setting the environment variables that they use for the compiler and 118 # linkers. 119 compiler = environment.coredata.compilers[for_machine][language] 120 try: 121 os.environ[f'HDF5_{cenv}'] = join_args(compiler.get_exelist()) 122 os.environ[f'HDF5_{cenv}LINKER'] = join_args(compiler.get_linker_exelist()) 123 super().__init__(name, environment, nkwargs, language) 124 finally: 125 del os.environ[f'HDF5_{cenv}'] 126 del os.environ[f'HDF5_{cenv}LINKER'] 127 if not self.is_found: 128 return 129 130 # We first need to call the tool with -c to get the compile arguments 131 # and then without -c to get the link arguments. 132 args = self.get_config_value(['-show', '-c'], 'args')[1:] 133 args += self.get_config_value(['-show', '-noshlib' if kwargs.get('static', False) else '-shlib'], 'args')[1:] 134 for arg in args: 135 if arg.startswith(('-I', '-f', '-D')) or arg == '-pthread': 136 self.compile_args.append(arg) 137 elif arg.startswith(('-L', '-l', '-Wl')): 138 self.link_args.append(arg) 139 elif Path(arg).is_file(): 140 self.link_args.append(arg) 141 142 # If the language is not C we need to add C as a subdependency 143 if language != 'c': 144 nkwargs = kwargs.copy() 145 nkwargs['language'] = 'c' 146 # I'm being too clever for mypy and pylint 147 self.is_found = self._add_sub_dependency(hdf5_factory(environment, for_machine, nkwargs)) # pylint: disable=no-value-for-parameter 148 149 def _sanitize_version(self, ver: str) -> str: 150 v = re.search(r'\s*HDF5 Version: (\d+\.\d+\.\d+)', ver) 151 return v.group(1) 152 153 154@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL}) 155def hdf5_factory(env: 'Environment', for_machine: 'MachineChoice', 156 kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: 157 language = kwargs.get('language') 158 candidates: T.List['DependencyGenerator'] = [] 159 160 if DependencyMethods.PKGCONFIG in methods: 161 # Use an ordered set so that these remain the first tried pkg-config files 162 pkgconfig_files = OrderedSet(['hdf5', 'hdf5-serial']) 163 # FIXME: This won't honor pkg-config paths, and cross-native files 164 PCEXE = shutil.which('pkg-config') 165 if PCEXE: 166 # some distros put hdf5-1.2.3.pc with version number in .pc filename. 167 ret = subprocess.run([PCEXE, '--list-all'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, 168 universal_newlines=True) 169 if ret.returncode == 0: 170 for pkg in ret.stdout.split('\n'): 171 if pkg.startswith('hdf5'): 172 pkgconfig_files.add(pkg.split(' ', 1)[0]) 173 174 for pkg in pkgconfig_files: 175 candidates.append(functools.partial(HDF5PkgConfigDependency, pkg, env, kwargs, language)) 176 177 if DependencyMethods.CONFIG_TOOL in methods: 178 candidates.append(functools.partial(HDF5ConfigToolDependency, 'hdf5', env, kwargs, language)) 179 180 return candidates 181