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 15import functools 16import typing as T 17import os 18import re 19 20from ..environment import detect_cpu_family 21from .base import DependencyMethods, detect_compiler, SystemDependency 22from .configtool import ConfigToolDependency 23from .factory import factory_methods 24from .pkgconfig import PkgConfigDependency 25 26if T.TYPE_CHECKING: 27 from .factory import DependencyGenerator 28 from ..environment import Environment, MachineChoice 29 30 31@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM}) 32def mpi_factory(env: 'Environment', 33 for_machine: 'MachineChoice', 34 kwargs: T.Dict[str, T.Any], 35 methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: 36 language = kwargs.get('language', 'c') 37 if language not in {'c', 'cpp', 'fortran'}: 38 # OpenMPI doesn't work without any other languages 39 return [] 40 41 candidates: T.List['DependencyGenerator'] = [] 42 compiler = detect_compiler('mpi', env, for_machine, language) 43 if compiler is None: 44 return [] 45 compiler_is_intel = compiler.get_id() in {'intel', 'intel-cl'} 46 47 # Only OpenMPI has pkg-config, and it doesn't work with the intel compilers 48 if DependencyMethods.PKGCONFIG in methods and not compiler_is_intel: 49 pkg_name = None 50 if language == 'c': 51 pkg_name = 'ompi-c' 52 elif language == 'cpp': 53 pkg_name = 'ompi-cxx' 54 elif language == 'fortran': 55 pkg_name = 'ompi-fort' 56 candidates.append(functools.partial( 57 PkgConfigDependency, pkg_name, env, kwargs, language=language)) 58 59 if DependencyMethods.CONFIG_TOOL in methods: 60 nwargs = kwargs.copy() 61 62 if compiler_is_intel: 63 if env.machines[for_machine].is_windows(): 64 nwargs['version_arg'] = '-v' 65 nwargs['returncode_value'] = 3 66 67 if language == 'c': 68 tool_names = [os.environ.get('I_MPI_CC'), 'mpiicc'] 69 elif language == 'cpp': 70 tool_names = [os.environ.get('I_MPI_CXX'), 'mpiicpc'] 71 elif language == 'fortran': 72 tool_names = [os.environ.get('I_MPI_F90'), 'mpiifort'] 73 74 cls = IntelMPIConfigToolDependency # type: T.Type[ConfigToolDependency] 75 else: # OpenMPI, which doesn't work with intel 76 # 77 # We try the environment variables for the tools first, but then 78 # fall back to the hardcoded names 79 if language == 'c': 80 tool_names = [os.environ.get('MPICC'), 'mpicc'] 81 elif language == 'cpp': 82 tool_names = [os.environ.get('MPICXX'), 'mpic++', 'mpicxx', 'mpiCC'] 83 elif language == 'fortran': 84 tool_names = [os.environ.get(e) for e in ['MPIFC', 'MPIF90', 'MPIF77']] 85 tool_names.extend(['mpifort', 'mpif90', 'mpif77']) 86 87 cls = OpenMPIConfigToolDependency 88 89 tool_names = [t for t in tool_names if t] # remove empty environment variables 90 assert tool_names 91 92 nwargs['tools'] = tool_names 93 candidates.append(functools.partial( 94 cls, tool_names[0], env, nwargs, language=language)) 95 96 if DependencyMethods.SYSTEM in methods: 97 candidates.append(functools.partial( 98 MSMPIDependency, 'msmpi', env, kwargs, language=language)) 99 100 return candidates 101 102 103class _MPIConfigToolDependency(ConfigToolDependency): 104 105 def _filter_compile_args(self, args: T.Sequence[str]) -> T.List[str]: 106 """ 107 MPI wrappers return a bunch of garbage args. 108 Drop -O2 and everything that is not needed. 109 """ 110 result = [] 111 multi_args: T.Tuple[str, ...] = ('-I', ) 112 if self.language == 'fortran': 113 fc = self.env.coredata.compilers[self.for_machine]['fortran'] 114 multi_args += fc.get_module_incdir_args() 115 116 include_next = False 117 for f in args: 118 if f.startswith(('-D', '-f') + multi_args) or f == '-pthread' \ 119 or (f.startswith('-W') and f != '-Wall' and not f.startswith('-Werror')): 120 result.append(f) 121 if f in multi_args: 122 # Path is a separate argument. 123 include_next = True 124 elif include_next: 125 include_next = False 126 result.append(f) 127 return result 128 129 def _filter_link_args(self, args: T.Sequence[str]) -> T.List[str]: 130 """ 131 MPI wrappers return a bunch of garbage args. 132 Drop -O2 and everything that is not needed. 133 """ 134 result = [] 135 include_next = False 136 for f in args: 137 if self._is_link_arg(f): 138 result.append(f) 139 if f in ('-L', '-Xlinker'): 140 include_next = True 141 elif include_next: 142 include_next = False 143 result.append(f) 144 return result 145 146 def _is_link_arg(self, f: str) -> bool: 147 if self.clib_compiler.id == 'intel-cl': 148 return f == '/link' or f.startswith('/LIBPATH') or f.endswith('.lib') # always .lib whether static or dynamic 149 else: 150 return (f.startswith(('-L', '-l', '-Xlinker')) or 151 f == '-pthread' or 152 (f.startswith('-W') and f != '-Wall' and not f.startswith('-Werror'))) 153 154 155class IntelMPIConfigToolDependency(_MPIConfigToolDependency): 156 157 """Wrapper around Intel's mpiicc and friends.""" 158 159 version_arg = '-v' # --version is not the same as -v 160 161 def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], 162 language: T.Optional[str] = None): 163 super().__init__(name, env, kwargs, language=language) 164 if not self.is_found: 165 return 166 167 args = self.get_config_value(['-show'], 'link and compile args') 168 self.compile_args = self._filter_compile_args(args) 169 self.link_args = self._filter_link_args(args) 170 171 def _sanitize_version(self, out: str) -> str: 172 v = re.search(r'(\d{4}) Update (\d)', out) 173 if v: 174 return '{}.{}'.format(v.group(1), v.group(2)) 175 return out 176 177 178class OpenMPIConfigToolDependency(_MPIConfigToolDependency): 179 180 """Wrapper around OpenMPI mpicc and friends.""" 181 182 version_arg = '--showme:version' 183 184 def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], 185 language: T.Optional[str] = None): 186 super().__init__(name, env, kwargs, language=language) 187 if not self.is_found: 188 return 189 190 c_args = self.get_config_value(['--showme:compile'], 'compile_args') 191 self.compile_args = self._filter_compile_args(c_args) 192 193 l_args = self.get_config_value(['--showme:link'], 'link_args') 194 self.link_args = self._filter_link_args(l_args) 195 196 def _sanitize_version(self, out: str) -> str: 197 v = re.search(r'\d+.\d+.\d+', out) 198 if v: 199 return v.group(0) 200 return out 201 202 203class MSMPIDependency(SystemDependency): 204 205 """The Microsoft MPI.""" 206 207 def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], 208 language: T.Optional[str] = None): 209 super().__init__(name, env, kwargs, language=language) 210 # MSMPI only supports the C API 211 if language not in {'c', 'fortran', None}: 212 self.is_found = False 213 return 214 # MSMPI is only for windows, obviously 215 if not self.env.machines[self.for_machine].is_windows(): 216 return 217 218 incdir = os.environ.get('MSMPI_INC') 219 arch = detect_cpu_family(self.env.coredata.compilers.host) 220 libdir = None 221 if arch == 'x86': 222 libdir = os.environ.get('MSMPI_LIB32') 223 post = 'x86' 224 elif arch == 'x86_64': 225 libdir = os.environ.get('MSMPI_LIB64') 226 post = 'x64' 227 228 if libdir is None or incdir is None: 229 self.is_found = False 230 return 231 232 self.is_found = True 233 self.link_args = ['-l' + os.path.join(libdir, 'msmpi')] 234 self.compile_args = ['-I' + incdir, '-I' + os.path.join(incdir, post)] 235 if self.language == 'fortran': 236 self.link_args.append('-l' + os.path.join(libdir, 'msmpifec')) 237