The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
8.3 KiB

# SPDX-License-Identifier: Apache-2.0
# Copyright 2013-2019 The Meson development team
from __future__ import annotations
import functools
import typing as T
import os
import re
from ..environment import detect_cpu_family
from .base import 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 ..mesonlib import MachineChoice
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM})
def mpi_factory(env: 'Environment',
for_machine: 'MachineChoice',
kwargs: T.Dict[str, T.Any],
methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
language = kwargs.get('language', 'c')
if language not in {'c', 'cpp', 'fortran'}:
# OpenMPI doesn't work without any other languages
return []
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'}
# Only OpenMPI has pkg-config, and it doesn't work with the intel compilers
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(functools.partial(
PkgConfigDependency, pkg_name, env, kwargs, language=language))
if DependencyMethods.CONFIG_TOOL in methods:
nwargs = kwargs.copy()
if compiler_is_intel:
if env.machines[for_machine].is_windows():
nwargs['version_arg'] = '-v'
nwargs['returncode_value'] = 3
if language == 'c':
tool_names = [os.environ.get('I_MPI_CC'), 'mpiicc']
elif language == 'cpp':
tool_names = [os.environ.get('I_MPI_CXX'), 'mpiicpc']
elif language == 'fortran':
tool_names = [os.environ.get('I_MPI_F90'), 'mpiifort']
cls: T.Type[ConfigToolDependency] = IntelMPIConfigToolDependency
else: # OpenMPI, which doesn't work with intel
#
# We try the environment variables for the tools first, but then
# fall back to the hardcoded names
if language == 'c':
tool_names = [os.environ.get('MPICC'), 'mpicc']
elif language == 'cpp':
tool_names = [os.environ.get('MPICXX'), 'mpic++', 'mpicxx', 'mpiCC']
elif language == 'fortran':
tool_names = [os.environ.get(e) for e in ['MPIFC', 'MPIF90', 'MPIF77']]
tool_names.extend(['mpifort', 'mpif90', 'mpif77'])
cls = OpenMPIConfigToolDependency
tool_names = [t for t in tool_names if t] # remove empty environment variables
assert tool_names
nwargs['tools'] = tool_names
candidates.append(functools.partial(
cls, tool_names[0], env, nwargs, language=language))
if DependencyMethods.SYSTEM in methods:
candidates.append(functools.partial(
MSMPIDependency, 'msmpi', env, kwargs, language=language))
return candidates
packages['mpi'] = mpi_factory
class _MPIConfigToolDependency(ConfigToolDependency):
typing: fix some broken Sequence annotations T.Sequence is a questionable concept. The idea is to hammer out generic, maximally forgiving APIs that operate on protocols, which is a fancy way of saying "I don't care if you use tuples or lists". This is rarely needed, actually, and in exchange for this fancy behavior you get free bugs. Specifically, `somestr` is of type `T.Sequence[str]`, and also `somestr[0]` is another string of type you guessed it. It's ~~turtles~~ strings all the way down. It's worth noting that trying to code for "protocols" is a broken concept if the contents have semantic meaning, e.g. it operates on "the install tags of this object" rather than "an iterable that supports efficient element access". The other way to use T.Sequence is "I don't like that T.List is invariant, but also I don't like that T.Tuple makes you specify exact ordering". This sort of works. In fact it probably does work as long as you don't allow str in your sequences, which of course everyone allows anyway. Use of Sequence has cute side effects, such as actually passing lists around, knowing that you are going to get a list and knowing that you need to pass it on as a list, and then having to re-allocate as `list(mylist)` "because the type annotations says it could be a str or tuple". Except it cannot be a str, because if it is then the application is fatally flawed and logic errors occur to disastrous end user effects, and the type annotations: - do not enforce their promises of annotating types - fail to live up to "minimal runtime penalties" due to all the `list()` Shun this broken concept, by hardening the type annotations. As it turns out, we do not actually need any of this covariance or protocol-ism for a list of strings! The whole attempt was a slow, buggy waste of time.
2 years ago
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
typing: fix some broken Sequence annotations T.Sequence is a questionable concept. The idea is to hammer out generic, maximally forgiving APIs that operate on protocols, which is a fancy way of saying "I don't care if you use tuples or lists". This is rarely needed, actually, and in exchange for this fancy behavior you get free bugs. Specifically, `somestr` is of type `T.Sequence[str]`, and also `somestr[0]` is another string of type you guessed it. It's ~~turtles~~ strings all the way down. It's worth noting that trying to code for "protocols" is a broken concept if the contents have semantic meaning, e.g. it operates on "the install tags of this object" rather than "an iterable that supports efficient element access". The other way to use T.Sequence is "I don't like that T.List is invariant, but also I don't like that T.Tuple makes you specify exact ordering". This sort of works. In fact it probably does work as long as you don't allow str in your sequences, which of course everyone allows anyway. Use of Sequence has cute side effects, such as actually passing lists around, knowing that you are going to get a list and knowing that you need to pass it on as a list, and then having to re-allocate as `list(mylist)` "because the type annotations says it could be a str or tuple". Except it cannot be a str, because if it is then the application is fatally flawed and logic errors occur to disastrous end user effects, and the type annotations: - do not enforce their promises of annotating types - fail to live up to "minimal runtime penalties" due to all the `list()` Shun this broken concept, by hardening the type annotations. As it turns out, we do not actually need any of this covariance or protocol-ism for a list of strings! The whole attempt was a slow, buggy waste of time.
2 years ago
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')))
class IntelMPIConfigToolDependency(_MPIConfigToolDependency):
"""Wrapper around Intel's mpiicc and friends."""
version_arg = '-v' # --version is not the same as -v
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
language: T.Optional[str] = None):
super().__init__(name, env, kwargs, language=language)
if not self.is_found:
return
args = self.get_config_value(['-show'], 'link and compile args')
self.compile_args = self._filter_compile_args(args)
self.link_args = self._filter_link_args(args)
def _sanitize_version(self, out: str) -> str:
v = re.search(r'(\d{4}) Update (\d)', out)
if v:
return '{}.{}'.format(v.group(1), v.group(2))
return out
class OpenMPIConfigToolDependency(_MPIConfigToolDependency):
"""Wrapper around OpenMPI mpicc and friends."""
version_arg = '--showme:version'
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
language: T.Optional[str] = None):
super().__init__(name, env, kwargs, language=language)
if not self.is_found:
return
c_args = self.get_config_value(['--showme:compile'], 'compile_args')
self.compile_args = self._filter_compile_args(c_args)
l_args = self.get_config_value(['--showme:link'], 'link_args')
self.link_args = self._filter_link_args(l_args)
def _sanitize_version(self, out: str) -> str:
v = re.search(r'\d+.\d+.\d+', out)
if v:
return v.group(0)
return out
class MSMPIDependency(SystemDependency):
"""The Microsoft MPI."""
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
language: T.Optional[str] = None):
super().__init__(name, env, kwargs, language=language)
# MSMPI only supports the C API
if 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'))