diff --git a/.appveyor.yml b/.appveyor.yml index 655144599..77d00af8d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -76,6 +76,17 @@ install: - ps: If($Env:compiler -eq 'msys2-mingw') {(new-object Net.WebClient).DownloadFile('https://bootstrap.pypa.io/get-pip.py', 'C:\projects\meson\get-pip.py')} - cmd: if %compiler%==msys2-mingw ( %PYTHON% "C:\projects\meson\get-pip.py" ) - cmd: if %compiler%==cygwin ( call ci\appveyor-install.bat ) + - ps: | + If($Env:compiler -like 'msvc*') { + (new-object net.webclient).DownloadFile( + "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/msmpisdk.msi", + "C:\projects\msmpisdk.msi") + c:\windows\system32\msiexec.exe /i C:\projects\msmpisdk.msi /quiet + (new-object net.webclient).DownloadFile( + "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/MSMpiSetup.exe", + "C:\projects\MSMpiSetup.exe") + c:\projects\MSMpiSetup.exe -unattend -full + } # Install additional packages needed for all builds. - cmd: "%WRAPPER% %PYTHON% -m pip install codecov" diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index 18512336c..401e019a3 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -51,6 +51,21 @@ If your boost headers or libraries are in non-standard locations you can set the GTest and GMock come as sources that must be compiled as part of your project. With Meson you don't have to care about the details, just pass `gtest` or `gmock` to `dependency` and it will do everything for you. If you want to use GMock, it is recommended to use GTest as well, as getting it to work standalone is tricky. +## MPI ## + +MPI is supported for C, C++ and Fortran. Because dependencies are +language-specific, you must specify the requested language using the +`language` keyword argument, i.e., + * `dependency('mpi', language='c')` for the C MPI headers and libraries + * `dependency('mpi', language='cpp')` for the C++ MPI headers and libraries + * `dependency('mpi', language='fortran')` for the Fortran MPI headers and libraries + +Meson prefers pkg-config for MPI, but if your MPI implementation does not +provide them, it will search for the standard wrapper executables, `mpic`, +`mpicxx`, `mpic++`, `mpifort`, `mpif90`, `mpif77`. If these are not in your +path, they can be specified by setting the standard environment variables +`MPICC`, `MPICXX`, `MPIFC`, `MPIF90`, or `MPIF77`, during configuration. + ## Qt5 ## Meson has native Qt5 support. Its usage is best demonstrated with an example. diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index ebd7b394e..265f76d6a 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -193,6 +193,7 @@ Finds an external dependency with the given name with `pkg-config` if possible a - `fallback` specifies a subproject fallback to use in case the dependency is not found in the system. The value is an array `['subproj_name', 'subproj_dep']` where the first value is the name of the subproject and the second is the variable name in that subproject that contains the value of [`declare_dependency`](#declare_dependency). - `default_options` *(added 0.37.0)* an array of option values that override those set in the project's `default_options` invocation (like `default_options` in [`project()`](#project), they only have effect when Meson is run for the first time, and command line arguments override any default options in build files) - `method` defines the way the dependency is detected, the default is `auto` but can be overridden to be e.g. `qmake` for Qt development, and different dependencies support different values for this (though `auto` will work on all of them) +- `language` *(added 0.42.0)* defines what language-specific dependency to find if it's available for multiple languages. The returned object also has methods that are documented in the [object methods section](#dependency-object) below. diff --git a/docs/markdown/Release-notes-for-0.42.0.md b/docs/markdown/Release-notes-for-0.42.0.md index 0fa27c3d8..f3127de7f 100644 --- a/docs/markdown/Release-notes-for-0.42.0.md +++ b/docs/markdown/Release-notes-for-0.42.0.md @@ -104,3 +104,11 @@ By default Meson adds the current source and build directories to the header search path. On some rare occasions this is not desired. Setting the `implicit_include_directories` keyword argument to `false` these directories are not used. + +## Support for MPI dependency + +MPI is now supported as a dependency. Because dependencies are +language-specific, you must specify the requested language with the `language` +keyword, i.e., `dependency('mpi', language='c')` will request the C MPI headers +and libraries. See [the MPI dependency](Dependencies.md#mpi) for more +information. diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index e17cda0c5..49200bb70 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -120,6 +120,9 @@ end program prog def get_include_args(self, path, is_system): return ['-I' + path] + def get_module_incdir_args(self): + return ('-I', ) + def get_module_outdir_args(self, path): return ['-J' + path] @@ -209,6 +212,9 @@ class SunFortranCompiler(FortranCompiler): def get_warn_args(self, level): return [] + def get_module_incdir_args(self): + return ('-M', ) + def get_module_outdir_args(self, path): return ['-moddir=' + path] @@ -251,6 +257,9 @@ class PGIFortranCompiler(FortranCompiler): super().__init__(exelist, version, is_cross, exe_wrapper=None) self.id = 'pgi' + def get_module_incdir_args(self): + return ('-module', ) + def get_module_outdir_args(self, path): return ['-module', path] diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index f153b976b..c16b92e19 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -15,9 +15,9 @@ from .base import ( # noqa: F401 Dependency, DependencyException, DependencyMethods, ExternalProgram, ExternalDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, - PkgConfigDependency, find_external_dependency, get_dep_identifier, packages) + PkgConfigDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language) from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency -from .misc import BoostDependency, Python3Dependency, ThreadDependency +from .misc import (BoostDependency, MPIDependency, Python3Dependency, ThreadDependency) from .platform import AppleFrameworks from .ui import GLDependency, GnuStepDependency, Qt4Dependency, Qt5Dependency, SDL2Dependency, WxDependency, VulkanDependency @@ -31,6 +31,7 @@ packages.update({ # From misc: 'boost': BoostDependency, + 'mpi': MPIDependency, 'python3': Python3Dependency, 'threads': ThreadDependency, @@ -46,3 +47,6 @@ packages.update({ 'wxwidgets': WxDependency, 'vulkan': VulkanDependency, }) +_packages_accept_language.update({ + 'mpi', +}) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index f98de4423..1f18c5284 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -26,8 +26,9 @@ from .. import mesonlib from ..mesonlib import MesonException, Popen_safe, flatten, version_compare_many -# This must be defined in this file to avoid cyclical references. +# These must be defined in this file to avoid cyclical references. packages = {} +_packages_accept_language = set() class DependencyException(MesonException): @@ -610,10 +611,15 @@ def find_external_dependency(name, env, kwargs): raise DependencyException('Keyword "method" must be a string.') lname = name.lower() if lname in packages: + if lname not in _packages_accept_language and 'language' in kwargs: + raise DependencyException('%s dependency does not accept "language" keyword argument' % (lname, )) dep = packages[lname](env, kwargs) if required and not dep.found(): raise DependencyException('Dependency "%s" not found' % name) return dep + if 'language' in kwargs: + # Remove check when PkgConfigDependency supports language. + raise DependencyException('%s dependency does not accept "language" keyword argument' % (lname, )) pkg_exc = None pkgdep = None try: diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 99df587ed..f5cbb96bf 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -16,6 +16,8 @@ import glob import os +import re +import shlex import stat import sysconfig @@ -24,7 +26,7 @@ from .. import mesonlib from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods -from .base import ExternalDependency, ExtraFrameworkDependency, PkgConfigDependency +from .base import ExternalDependency, ExternalProgram, ExtraFrameworkDependency, PkgConfigDependency class BoostDependency(ExternalDependency): @@ -277,6 +279,210 @@ class BoostDependency(ExternalDependency): return 'thread' in self.requested_modules +class MPIDependency(ExternalDependency): + def __init__(self, environment, kwargs): + language = kwargs.get('language', 'c') + super().__init__('mpi', environment, language, kwargs) + required = kwargs.pop('required', True) + kwargs['required'] = False + kwargs['silent'] = True + self.is_found = False + + # NOTE: Only OpenMPI supplies a pkg-config file at the moment. + if language == 'c': + env_vars = ['MPICC'] + pkgconfig_files = ['ompi-c'] + default_wrappers = ['mpicc'] + elif language == 'cpp': + env_vars = ['MPICXX'] + pkgconfig_files = ['ompi-cxx'] + default_wrappers = ['mpic++', 'mpicxx', 'mpiCC'] + elif language == 'fortran': + env_vars = ['MPIFC', 'MPIF90', 'MPIF77'] + pkgconfig_files = ['ompi-fort'] + default_wrappers = ['mpifort', 'mpif90', 'mpif77'] + else: + raise DependencyException('Language {} is not supported with MPI.'.format(language)) + + for pkg in pkgconfig_files: + try: + pkgdep = PkgConfigDependency(pkg, environment, kwargs) + if pkgdep.found(): + self.compile_args = pkgdep.get_compile_args() + self.link_args = pkgdep.get_link_args() + self.version = pkgdep.get_version() + self.is_found = True + break + except Exception: + pass + + if not self.is_found: + # Prefer environment. + for var in env_vars: + if var in os.environ: + wrappers = [os.environ[var]] + break + else: + # Or search for default wrappers. + wrappers = default_wrappers + + for prog in wrappers: + result = self._try_openmpi_wrapper(prog) + if result is not None: + self.is_found = True + self.version = result[0] + self.compile_args = self._filter_compile_args(result[1]) + self.link_args = self._filter_link_args(result[2]) + break + result = self._try_other_wrapper(prog) + if result is not None: + self.is_found = True + self.version = result[0] + self.compile_args = self._filter_compile_args(result[1]) + self.link_args = self._filter_link_args(result[2]) + break + + if not self.is_found and mesonlib.is_windows(): + result = self._try_msmpi() + if result is not None: + self.is_found = True + self.version, self.compile_args, self.link_args = result + + if self.is_found: + mlog.log('Dependency', mlog.bold(self.name), 'for', self.language, 'found:', mlog.green('YES'), self.version) + else: + mlog.log('Dependency', mlog.bold(self.name), 'for', self.language, 'found:', mlog.red('NO')) + if required: + raise DependencyException('MPI dependency {!r} not found'.format(self.name)) + + def _filter_compile_args(self, args): + """ + MPI wrappers return a bunch of garbage args. + Drop -O2 and everything that is not needed. + """ + result = [] + multi_args = ('-I', ) + if self.language == 'fortran': + fc = self.env.coredata.compilers['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): + """ + 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 f.startswith(('-L', '-l', '-Xlinker')) or f == '-pthread' \ + or (f.startswith('-W') and f != '-Wall' and not f.startswith('-Werror')): + result.append(f) + if f in ('-L', '-Xlinker'): + include_next = True + elif include_next: + include_next = False + result.append(f) + return result + + def _try_openmpi_wrapper(self, prog): + prog = ExternalProgram(prog, silent=True) + if prog.found(): + cmd = prog.get_command() + ['--showme:compile'] + p, o, e = mesonlib.Popen_safe(cmd) + p.wait() + if p.returncode != 0: + mlog.debug('Command', mlog.bold(cmd), 'failed to run:') + mlog.debug(mlog.bold('Standard output\n'), o) + mlog.debug(mlog.bold('Standard error\n'), e) + return + cargs = shlex.split(o) + + cmd = prog.get_command() + ['--showme:link'] + p, o, e = mesonlib.Popen_safe(cmd) + p.wait() + if p.returncode != 0: + mlog.debug('Command', mlog.bold(cmd), 'failed to run:') + mlog.debug(mlog.bold('Standard output\n'), o) + mlog.debug(mlog.bold('Standard error\n'), e) + return + libs = shlex.split(o) + + cmd = prog.get_command() + ['--showme:version'] + p, o, e = mesonlib.Popen_safe(cmd) + p.wait() + if p.returncode != 0: + mlog.debug('Command', mlog.bold(cmd), 'failed to run:') + mlog.debug(mlog.bold('Standard output\n'), o) + mlog.debug(mlog.bold('Standard error\n'), e) + return + version = re.search('\d+.\d+.\d+', o) + if version: + version = version.group(0) + else: + version = 'none' + + return version, cargs, libs + + def _try_other_wrapper(self, prog): + prog = ExternalProgram(prog, silent=True) + if prog.found(): + cmd = prog.get_command() + ['-show'] + p, o, e = mesonlib.Popen_safe(cmd) + p.wait() + if p.returncode != 0: + mlog.debug('Command', mlog.bold(cmd), 'failed to run:') + mlog.debug(mlog.bold('Standard output\n'), o) + mlog.debug(mlog.bold('Standard error\n'), e) + return + args = shlex.split(o) + + version = 'none' + + return version, args, args + + def _try_msmpi(self): + if self.language == 'cpp': + # MS-MPI does not support the C++ version of MPI, only the standard C API. + return + if 'MSMPI_INC' not in os.environ: + return + incdir = os.environ['MSMPI_INC'] + arch = detect_cpu_family(self.env.coredata.compilers) + if arch == 'x86': + if 'MSMPI_LIB32' not in os.environ: + return + libdir = os.environ['MSMPI_LIB32'] + post = 'x86' + elif arch == 'x86_64': + if 'MSMPI_LIB64' not in os.environ: + return + libdir = os.environ['MSMPI_LIB64'] + post = 'x64' + else: + return + if self.language == 'fortran': + return ('none', + ['-I' + incdir, '-I' + os.path.join(incdir, post)], + [os.path.join(libdir, 'msmpi.lib'), os.path.join(libdir, 'msmpifec.lib')]) + else: + return ('none', + ['-I' + incdir, '-I' + os.path.join(incdir, post)], + [os.path.join(libdir, 'msmpi.lib')]) + + class ThreadDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('threads', environment, None, {}) diff --git a/test cases/frameworks/17 mpi/main.c b/test cases/frameworks/17 mpi/main.c new file mode 100644 index 000000000..e44357a97 --- /dev/null +++ b/test cases/frameworks/17 mpi/main.c @@ -0,0 +1,27 @@ +#include +#include + +int main(int argc, char **argv) +{ + int ier, flag; + ier = MPI_Init(&argc, &argv); + if (ier) { + printf("Unable to initialize MPI: %d\n", ier); + return 1; + } + ier = MPI_Initialized(&flag); + if (ier) { + printf("Unable to check MPI initialization state: %d\n", ier); + return 1; + } + if (!flag) { + printf("MPI did not initialize!\n"); + return 1; + } + ier = MPI_Finalize(); + if (ier) { + printf("Unable to finalize MPI: %d\n", ier); + return 1; + } + return 0; +} diff --git a/test cases/frameworks/17 mpi/main.cpp b/test cases/frameworks/17 mpi/main.cpp new file mode 100644 index 000000000..0e0b62173 --- /dev/null +++ b/test cases/frameworks/17 mpi/main.cpp @@ -0,0 +1,11 @@ +#include + +int main(int argc, char **argv) +{ + MPI::Init(argc, argv); + if (!MPI::Is_initialized()) { + printf("MPI did not initialize!\n"); + return 1; + } + MPI::Finalize(); +} diff --git a/test cases/frameworks/17 mpi/main.f90 b/test cases/frameworks/17 mpi/main.f90 new file mode 100644 index 000000000..d379e96f8 --- /dev/null +++ b/test cases/frameworks/17 mpi/main.f90 @@ -0,0 +1,21 @@ +program mpitest + implicit none + include 'mpif.h' + logical :: flag + integer :: ier + call MPI_Init(ier) + if (ier /= 0) then + print *, 'Unable to initialize MPI: ', ier + stop 1 + endif + call MPI_Initialized(flag, ier) + if (ier /= 0) then + print *, 'Unable to check MPI initialization state: ', ier + stop 1 + endif + call MPI_Finalize(ier) + if (ier /= 0) then + print *, 'Unable to finalize MPI: ', ier + stop 1 + endif +end program mpitest diff --git a/test cases/frameworks/17 mpi/meson.build b/test cases/frameworks/17 mpi/meson.build new file mode 100644 index 000000000..5e9bc563d --- /dev/null +++ b/test cases/frameworks/17 mpi/meson.build @@ -0,0 +1,33 @@ +project('mpi', 'c', 'cpp') + +cc = meson.get_compiler('c') + +if build_machine.system() == 'windows' and cc.get_id() != 'msvc' + error('MESON_SKIP_TEST: MPI not available on Windows without MSVC.') +endif + +mpic = dependency('mpi', language : 'c') +exec = executable('exec', + 'main.c', + dependencies : [mpic]) + +test('MPI C', exec) + +if build_machine.system() != 'windows' + # C++ MPI not supported by MS-MPI used on AppVeyor. + mpicpp = dependency('mpi', language : 'cpp') + execpp = executable('execpp', + 'main.cpp', + dependencies : [mpicpp]) + + test('MPI C++', execpp) +endif + +if add_languages('fortran', required : false) + mpifort = dependency('mpi', language : 'fortran') + exef = executable('exef', + 'main.f90', + dependencies : [mpifort]) + + test('MPI Fortran', exef) +endif