1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
# SPDX-License-Identifier: Apache-2.0
# Copyright 2013-2019 The Meson development team
from __future__ import annotations
import typing as T
import os
import re
from ..envconfig import detect_cpu_family
from ..mesonlib import Popen_safe
from .base import DependencyCandidate, DependencyException, DependencyMethods, detect_compiler, SystemDependency
from .configtool import ConfigToolDependency
from .detect import packages
from .factory import factory_methods
from .pkgconfig import PkgConfigDependency
if T.TYPE_CHECKING:
from .factory import DependencyGenerator
from ..environment import Environment
from .base import DependencyObjectKWs
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM})
def mpi_factory(env: 'Environment',
kwargs: DependencyObjectKWs,
methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
language = kwargs.get('language') or 'c'
if language not in {'c', 'cpp', 'fortran'}:
# OpenMPI doesn't work without any other languages
return []
for_machine = kwargs['native']
candidates: T.List['DependencyGenerator'] = []
compiler = detect_compiler('mpi', env, for_machine, language)
if not compiler:
return []
compiler_is_intel = compiler.get_id() in {'intel', 'intel-cl'}
if DependencyMethods.CONFIG_TOOL in methods and not env.machines[for_machine].is_windows():
nwargs = kwargs.copy()
# We try the environment variables for the tools first, but then
# fall back to the hardcoded names
if language == 'c':
env_vars = ['MPICC']
elif language == 'cpp':
env_vars = ['MPICXX']
elif language == 'fortran':
env_vars = ['MPIFC', 'MPIF90', 'MPIF77']
tool_names = [os.environ.get(env_name) for env_name in env_vars]
tool_names = [t for t in tool_names if t] # remove empty environment variables
if compiler_is_intel:
if env.machines[for_machine].is_windows():
nwargs['returncode_value'] = 3
if language == 'c':
tool_names.append('mpiicc')
elif language == 'cpp':
tool_names.append('mpiicpc')
elif language == 'fortran':
tool_names.append('mpiifort')
# even with intel compilers, mpicc has to be considered
if language == 'c':
tool_names.append('mpicc')
elif language == 'cpp':
tool_names.extend(['mpic++', 'mpicxx', 'mpiCC'])
elif language == 'fortran':
tool_names.extend(['mpifort', 'mpif90', 'mpif77'])
nwargs['tools'] = tool_names
candidates.append(DependencyCandidate.from_dependency(
tool_names[0], MPIConfigToolDependency, (env, nwargs)))
if DependencyMethods.SYSTEM in methods and env.machines[for_machine].is_windows():
candidates.append(DependencyCandidate.from_dependency(
'msmpi', MSMPIDependency, (env, kwargs)))
candidates.append(DependencyCandidate.from_dependency(
'impi', IMPIDependency, (env, kwargs)))
# Only OpenMPI has pkg-config, and it doesn't work with the intel compilers
# for MPI, environment variables and commands like mpicc should have priority
if DependencyMethods.PKGCONFIG in methods and not compiler_is_intel:
pkg_name = None
if language == 'c':
pkg_name = 'ompi-c'
elif language == 'cpp':
pkg_name = 'ompi-cxx'
elif language == 'fortran':
pkg_name = 'ompi-fort'
candidates.append(DependencyCandidate.from_dependency(
pkg_name, PkgConfigDependency, (env, kwargs)))
return candidates
packages['mpi'] = mpi_factory
class MPIConfigToolDependency(ConfigToolDependency):
"""Wrapper around mpicc, Intel's mpiicc and friends."""
def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs):
super().__init__(name, env, kwargs)
if not self.is_found:
return
# --showme for OpenMPI, -compile_info/-link_info for MPICH and IntelMPI
for comp, link in [('--showme:compile', '--showme:link'), ('-compile_info', '-link_info'), ('-show', None)]:
try:
c_args = self.get_config_value([comp], 'compile_args')
l_args = self.get_config_value([link], 'link_args') if link is not None else c_args
except DependencyException:
continue
else:
break
else:
self.is_found = False
return
self.compile_args = self._filter_compile_args(c_args)
self.link_args = self._filter_link_args(l_args)
def _filter_compile_args(self, args: T.List[str]) -> T.List[str]:
"""
MPI wrappers return a bunch of garbage args.
Drop -O2 and everything that is not needed.
"""
result = []
multi_args: T.Tuple[str, ...] = ('-I', )
if self.language == 'fortran':
fc = self.env.coredata.compilers[self.for_machine]['fortran']
multi_args += fc.get_module_incdir_args()
include_next = False
for f in args:
if f.startswith(('-D', '-f') + multi_args) or f == '-pthread' \
or (f.startswith('-W') and f != '-Wall' and not f.startswith('-Werror')):
result.append(f)
if f in multi_args:
# Path is a separate argument.
include_next = True
elif include_next:
include_next = False
result.append(f)
return result
def _filter_link_args(self, args: T.List[str]) -> T.List[str]:
"""
MPI wrappers return a bunch of garbage args.
Drop -O2 and everything that is not needed.
"""
result = []
include_next = False
for f in args:
if self._is_link_arg(f):
result.append(f)
if f in {'-L', '-Xlinker'}:
include_next = True
elif include_next:
include_next = False
result.append(f)
return result
def _is_link_arg(self, f: str) -> bool:
if self.clib_compiler.id == 'intel-cl':
return f == '/link' or f.startswith('/LIBPATH') or f.endswith('.lib') # always .lib whether static or dynamic
else:
return (f.startswith(('-L', '-l', '-Xlinker')) or
f == '-pthread' or
(f.startswith('-W') and f != '-Wall' and not f.startswith('-Werror')))
def _check_and_get_version(self, tool: T.List[str], returncode: int) -> T.Tuple[bool, T.Union[str, None]]:
p, out = Popen_safe(tool + ['--showme:version'])[:2]
valid = p.returncode == returncode
if valid:
# OpenMPI
v = re.search(r'\d+.\d+.\d+', out)
if v:
version = v.group(0)
else:
version = None
return valid, version
# --version is not the same as -v
p, out = Popen_safe(tool + ['-v'])[:2]
valid = p.returncode == returncode
first_line = out.split('\n', maxsplit=1)[0]
# cases like "mpicc for MPICH version 4.2.2"
v = re.search(r'\d+.\d+.\d+', first_line)
if v:
return valid, v.group(0)
# cases like "mpigcc for Intel(R) MPI library 2021.13"
v = re.search(r'\d+.\d+', first_line)
if v:
return valid, v.group(0)
# cases like "mpiifort for the Intel(R) MPI Library 2019 Update 9 for Linux*"
v = re.search(r'(\d{4}) Update (\d)', first_line)
if v:
return valid, f'{v.group(1)}.{v.group(2)}'
return valid, None
class MSMPIDependency(SystemDependency):
"""The Microsoft MPI."""
def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs):
super().__init__(name, env, kwargs)
# MSMPI only supports the C API
if self.language not in {'c', 'fortran', None}:
self.is_found = False
return
# MSMPI is only for windows, obviously
if not self.env.machines[self.for_machine].is_windows():
return
incdir = os.environ.get('MSMPI_INC')
arch = detect_cpu_family(self.env.coredata.compilers.host)
libdir = None
if arch == 'x86':
libdir = os.environ.get('MSMPI_LIB32')
post = 'x86'
elif arch == 'x86_64':
libdir = os.environ.get('MSMPI_LIB64')
post = 'x64'
if libdir is None or incdir is None:
self.is_found = False
return
self.is_found = True
self.link_args = ['-l' + os.path.join(libdir, 'msmpi')]
self.compile_args = ['-I' + incdir, '-I' + os.path.join(incdir, post)]
if self.language == 'fortran':
self.link_args.append('-l' + os.path.join(libdir, 'msmpifec'))
class IMPIDependency(SystemDependency):
"""Intel(R) MPI for Windows."""
def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs):
super().__init__(name, env, kwargs)
# only for windows
if not self.env.machines[self.for_machine].is_windows():
return
# only for x86_64
if self.env.machines[self.for_machine].cpu_family != 'x86_64':
return
rootdir = os.environ.get('I_MPI_ROOT')
if rootdir is None:
self.is_found = False
return
incdir = os.path.join(rootdir, 'include')
libdir = os.path.join(rootdir, 'lib')
debug = env.coredata.optstore.get_value_for('debug')
assert isinstance(debug, bool)
libdir_post = 'debug' if debug else 'release'
for subdirs in (['mpi', libdir_post], [libdir_post]):
libdir_buildtype = os.path.join(libdir, *subdirs)
if os.path.isdir(libdir_buildtype):
libdir = libdir_buildtype
break
found_header = os.path.isfile(os.path.join(incdir, 'mpi.h'))
found_library = os.path.isfile(os.path.join(libdir, 'impi.lib'))
if not found_header or not found_library:
self.is_found = False
return
self.is_found = True
self.compile_args = ['-I' + incdir]
self.link_args = ['-l' + os.path.join(libdir, 'impi')]
if self.language == 'cpp':
# Some installations do not have the MPI C++ bindings library
if not os.path.isfile(os.path.join(libdir, 'impicxx.lib')):
self.is_found = False
return
self.link_args = ['-l' + os.path.join(libdir, 'impicxx')]
|