diff --git a/easyblocks/a/aocl.py b/easyblocks/a/aocl.py new file mode 100644 index 0000000000000000000000000000000000000000..53e0f4781a7aa98922317176b9247e62971fc6cd --- /dev/null +++ b/easyblocks/a/aocl.py @@ -0,0 +1,93 @@ +## +# Copyright 2009-2021 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +## +""" +EasyBuild support for AMD Core Math Library (AOCL), implemented as an easyblock + +@author: Lukas Krupcik (IT4Innovations) +""" + +import os +from distutils.version import LooseVersion + +from easybuild.framework.easyblock import EasyBlock +from easybuild.framework.easyconfig import CUSTOM +from easybuild.tools.run import run_cmd_qa +from easybuild.tools.systemtools import get_shared_lib_ext + + +class EB_AOCL(EasyBlock): + + def __init__(self, *args, **kwargs): + """Constructor, adds extra class variables.""" + super(EB_AOCL, self).__init__(*args, **kwargs) + + self.basedir = '%s' % (self.version) + + def configure_step(self): + """No custom configure step for AOCL.""" + pass + + def build_step(self): + """No custom build step for AOCL.""" + pass + + def install_step(self): + """Install by running install script.""" + + cmd = "./install.sh -t %s" % (self.installdir) + + qa = {'The directory will be created if it does not already exist. >': self.installdir} + + run_cmd_qa(cmd, qa, log_all=True, simple=True) + + def make_module_extra(self): + """Add extra entries in module file (various paths).""" + + txt = super(EB_AOCL, self).make_module_extra() + + basepaths = ["%s" % (self.basedir)] + + txt += self.module_generator.set_environment('AOCL_ROOT', "%s/%s" % (self.installdir, self.basedir)) + + for path in basepaths: + txt += self.module_generator.prepend_paths('PATH', os.path.join(path, 'amd-fftw/bin')) + + for path in basepaths: + txt += self.module_generator.prepend_paths('CPATH', os.path.join(path, 'include')) + + for key in ['LD_LIBRARY_PATH', 'LIBRARY_PATH']: + for path in basepaths: + txt += self.module_generator.prepend_paths(key, os.path.join(path, 'lib')) + + return txt + + def sanity_check_step(self): + """Custom sanity check for AOCL.""" + + custom_paths = { + 'files': [], + 'dirs': ['%s/aocc/include' % (self.version), '%s/aocc/lib' % (self.version), '%s/aocc/amd-fftw' % (self.version)] + } + super(EB_AOCL, self).sanity_check_step(custom_paths=custom_paths) diff --git a/easyblocks/i/impi.py b/easyblocks/i/impi.py new file mode 100644 index 0000000000000000000000000000000000000000..df0a804636509153fba451bcbb6c6a156c42784e --- /dev/null +++ b/easyblocks/i/impi.py @@ -0,0 +1,377 @@ +# # +# Copyright 2009-2023 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +# # +""" +EasyBuild support for installing the Intel MPI library, implemented as an easyblock + +@author: Stijn De Weirdt (Ghent University) +@author: Dries Verdegem (Ghent University) +@author: Kenneth Hoste (Ghent University) +@author: Pieter De Baets (Ghent University) +@author: Jens Timmerman (Ghent University) +@author: Damian Alvarez (Forschungszentrum Juelich GmbH) +@author: Alex Domingo (Vrije Universiteit Brussel) +""" +import os +from distutils.version import LooseVersion + +import easybuild.tools.toolchain as toolchain +from easybuild.easyblocks.generic.intelbase import IntelBase, ACTIVATION_NAME_2012, LICENSE_FILE_NAME_2012 +from easybuild.framework.easyconfig import CUSTOM +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.config import build_option +from easybuild.tools.filetools import apply_regex_substitutions, change_dir, extract_file, mkdir, write_file +from easybuild.tools.modules import get_software_root +from easybuild.tools.run import run_cmd +from easybuild.tools.systemtools import get_shared_lib_ext +from easybuild.tools.toolchain.mpi import get_mpi_cmd_template + + +class EB_impi(IntelBase): + """ + Support for installing Intel MPI library + """ + @staticmethod + def extra_options(): + extra_vars = { + 'libfabric_configopts': ['', 'Configure options for the provided libfabric', CUSTOM], + 'libfabric_rebuild': [True, 'Try to rebuild internal libfabric instead of using provided binary', CUSTOM], + 'ofi_internal': [True, 'Use internal shipped libfabric instead of external libfabric', CUSTOM], + 'set_mpi_wrappers_compiler': [False, 'Override default compiler used by MPI wrapper commands', CUSTOM], + 'set_mpi_wrapper_aliases_gcc': [False, 'Set compiler for mpigcc/mpigxx via aliases', CUSTOM], + 'set_mpi_wrapper_aliases_intel': [False, 'Set compiler for mpiicc/mpiicpc/mpiifort via aliases', CUSTOM], + 'set_mpi_wrappers_all': [False, 'Set (default) compiler for all MPI wrapper commands', CUSTOM], + } + return IntelBase.extra_options(extra_vars) + + def prepare_step(self, *args, **kwargs): + if LooseVersion(self.version) >= LooseVersion('2017.2.174'): + kwargs['requires_runtime_license'] = False + super(EB_impi, self).prepare_step(*args, **kwargs) + else: + super(EB_impi, self).prepare_step(*args, **kwargs) + + def install_step(self): + """ + Actual installation + - create silent cfg file + - execute command + """ + impiver = LooseVersion(self.version) + + if impiver >= LooseVersion('2021'): + super(EB_impi, self).install_step() + + elif impiver >= LooseVersion('4.0.1'): + # impi starting from version 4.0.1.x uses standard installation procedure. + + silent_cfg_names_map = {} + + if impiver < LooseVersion('4.1.1'): + # since impi v4.1.1, silent.cfg has been slightly changed to be 'more standard' + silent_cfg_names_map.update({ + 'activation_name': ACTIVATION_NAME_2012, + 'license_file_name': LICENSE_FILE_NAME_2012, + }) + + super(EB_impi, self).install_step(silent_cfg_names_map=silent_cfg_names_map) + + # impi v4.1.1 and v5.0.1 installers create impi/<version> subdir, so stuff needs to be moved afterwards + if impiver == LooseVersion('4.1.1.036') or impiver >= LooseVersion('5.0.1.035'): + super(EB_impi, self).move_after_install() + else: + # impi up until version 4.0.0.x uses custom installation procedure. + silent = """[mpi] +INSTALLDIR=%(ins)s +LICENSEPATH=%(lic)s +INSTALLMODE=NONRPM +INSTALLUSER=NONROOT +UPDATE_LD_SO_CONF=NO +PROCEED_WITHOUT_PYTHON=yes +AUTOMOUNTED_CLUSTER=yes +EULA=accept +[mpi-rt] +INSTALLDIR=%(ins)s +LICENSEPATH=%(lic)s +INSTALLMODE=NONRPM +INSTALLUSER=NONROOT +UPDATE_LD_SO_CONF=NO +PROCEED_WITHOUT_PYTHON=yes +AUTOMOUNTED_CLUSTER=yes +EULA=accept + +""" % {'lic': self.license_file, 'ins': self.installdir} + + # already in correct directory + silentcfg = os.path.join(os.getcwd(), "silent.cfg") + write_file(silentcfg, silent) + self.log.debug("Contents of %s: %s", silentcfg, silent) + + tmpdir = os.path.join(os.getcwd(), self.version, 'mytmpdir') + mkdir(tmpdir, parents=True) + + cmd = "./install.sh --tmp-dir=%s --silent=%s" % (tmpdir, silentcfg) + run_cmd(cmd, log_all=True, simple=True) + + # recompile libfabric (if requested) + # some Intel MPI versions (like 2019 update 6) no longer ship libfabric sources + libfabric_path = os.path.join(self.installdir, 'libfabric') + if impiver >= LooseVersion('2019') and self.cfg['libfabric_rebuild']: + if self.cfg['ofi_internal']: + libfabric_src_tgz_fn = 'src.tgz' + if os.path.exists(os.path.join(libfabric_path, libfabric_src_tgz_fn)): + change_dir(libfabric_path) + srcdir = extract_file(libfabric_src_tgz_fn, os.getcwd(), change_into_dir=False) + change_dir(srcdir) + libfabric_installpath = os.path.join(self.installdir, 'intel64', 'libfabric') + + make = 'make' + if self.cfg['parallel']: + make += ' -j %d' % self.cfg['parallel'] + + cmds = [ + './configure --prefix=%s %s' % (libfabric_installpath, self.cfg['libfabric_configopts']), + make, + 'make install' + ] + for cmd in cmds: + run_cmd(cmd, log_all=True, simple=True) + else: + self.log.info("Rebuild of libfabric is requested, but %s does not exist, so skipping...", + libfabric_src_tgz_fn) + else: + raise EasyBuildError("Rebuild of libfabric is requested, but ofi_internal is set to False.") + + def post_install_step(self): + """Custom post install step for IMPI, fix broken env scripts after moving installed files.""" + super(EB_impi, self).post_install_step() + + impiver = LooseVersion(self.version) + + if impiver >= LooseVersion('2021'): + self.log.info("No post-install action for impi v%s", self.version) + + elif impiver == LooseVersion('4.1.1.036') or impiver >= LooseVersion('5.0.1.035'): + if impiver >= LooseVersion('2018.0.128'): + script_paths = [os.path.join('intel64', 'bin')] + else: + script_paths = [os.path.join('intel64', 'bin'), os.path.join('mic', 'bin')] + # fix broken env scripts after the move + regex_subs = [(r"^setenv I_MPI_ROOT.*", r"setenv I_MPI_ROOT %s" % self.installdir)] + for script in [os.path.join(script_path, 'mpivars.csh') for script_path in script_paths]: + apply_regex_substitutions(os.path.join(self.installdir, script), regex_subs) + regex_subs = [(r"^(\s*)I_MPI_ROOT=[^;\n]*", r"\1I_MPI_ROOT=%s" % self.installdir)] + for script in [os.path.join(script_path, 'mpivars.sh') for script_path in script_paths]: + apply_regex_substitutions(os.path.join(self.installdir, script), regex_subs) + + # fix 'prefix=' in compiler wrapper scripts after moving installation (see install_step) + wrappers = ['mpif77', 'mpif90', 'mpigcc', 'mpigxx', 'mpiicc', 'mpiicpc', 'mpiifort'] + regex_subs = [(r"^prefix=.*", r"prefix=%s" % self.installdir)] + for script_dir in script_paths: + for wrapper in wrappers: + wrapper_path = os.path.join(self.installdir, script_dir, wrapper) + if os.path.exists(wrapper_path): + apply_regex_substitutions(wrapper_path, regex_subs) + + def sanity_check_step(self): + """Custom sanity check paths for IMPI.""" + + impi_ver = LooseVersion(self.version) + + suff = '64' + if self.cfg['m32']: + suff = '' + + mpi_mods = ['mpi.mod'] + if impi_ver > LooseVersion('4.0'): + mpi_mods.extend(['mpi_base.mod', 'mpi_constants.mod', 'mpi_sizeofs.mod']) + + if impi_ver >= LooseVersion('2021'): + mpi_subdir = os.path.join('mpi', self.version) + bin_dir = os.path.join(mpi_subdir, 'bin') + include_dir = os.path.join(mpi_subdir, 'include') + lib_dir = os.path.join(mpi_subdir, 'lib', 'release') + + elif impi_ver >= LooseVersion('2019'): + bin_dir = os.path.join('intel64', 'bin') + include_dir = os.path.join('intel64', 'include') + lib_dir = os.path.join('intel64', 'lib', 'release') + else: + bin_dir = 'bin%s' % suff + include_dir = 'include%s' % suff + lib_dir = 'lib%s' % suff + mpi_mods.extend(['i_malloc.h']) + + shlib_ext = get_shared_lib_ext() + custom_paths = { + 'files': [os.path.join(bin_dir, 'mpi%s' % x) for x in ['icc', 'icpc', 'ifort']] + + [os.path.join(include_dir, 'mpi%s.h' % x) for x in ['cxx', 'f', '', 'o', 'of']] + + [os.path.join(include_dir, x) for x in mpi_mods] + + [os.path.join(lib_dir, 'libmpi.%s' % shlib_ext)] + + [os.path.join(lib_dir, 'libmpi.a')], + 'dirs': [], + } + + custom_commands = [] + + if build_option('mpi_tests'): + if impi_ver >= LooseVersion('2017'): + # Add minimal test program to sanity checks + if impi_ver >= LooseVersion('2021'): + impi_testsrc = os.path.join(self.installdir, 'mpi', self.version, 'test', 'test.c') + else: + impi_testsrc = os.path.join(self.installdir, 'test', 'test.c') + + impi_testexe = os.path.join(self.builddir, 'mpi_test') + self.log.info("Adding minimal MPI test program to sanity checks: %s", impi_testsrc) + + # Build test program with appropriate compiler from current toolchain + comp_fam = self.toolchain.comp_family() + if comp_fam == toolchain.INTELCOMP: + build_comp = 'mpiicc' + else: + build_comp = 'mpicc' + build_cmd = "%s %s -o %s" % (build_comp, impi_testsrc, impi_testexe) + + # Execute test program with appropriate MPI executable for target toolchain + params = {'nr_ranks': self.cfg['parallel'], 'cmd': impi_testexe} + mpi_cmd_tmpl, params = get_mpi_cmd_template(toolchain.INTELMPI, params, mpi_version=self.version) + + custom_commands.extend([ + build_cmd, # build test program + #mpi_cmd_tmpl % params, # run test program + ]) + + super(EB_impi, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) + + def make_module_req_guess(self): + """ + A dictionary of possible directories to look for + """ + guesses = super(EB_impi, self).make_module_req_guess() + if self.cfg['m32']: + lib_dirs = ['lib', 'lib/ia32', 'ia32/lib'] + guesses.update({ + 'PATH': ['bin', 'bin/ia32', 'ia32/bin'], + 'LD_LIBRARY_PATH': lib_dirs, + 'LIBRARY_PATH': lib_dirs, + 'MIC_LD_LIBRARY_PATH': ['mic/lib'], + }) + else: + manpath = 'man' + + impi_ver = LooseVersion(self.version) + if impi_ver >= LooseVersion('2021'): + mpi_subdir = os.path.join('mpi', self.version) + lib_dirs = [ + os.path.join(mpi_subdir, 'lib'), + os.path.join(mpi_subdir, 'lib', 'release'), + os.path.join(mpi_subdir, 'libfabric', 'lib'), + ] + include_dirs = [os.path.join(mpi_subdir, 'include')] + path_dirs = [ + os.path.join(mpi_subdir, 'bin'), + os.path.join(mpi_subdir, 'libfabric', 'bin'), + ] + manpath = os.path.join(mpi_subdir, 'man') + + if self.cfg['ofi_internal']: + libfabric_dir = os.path.join('mpi', self.version, 'libfabric') + lib_dirs.append(os.path.join(libfabric_dir, 'lib')) + path_dirs.append(os.path.join(libfabric_dir, 'bin')) + guesses['FI_PROVIDER_PATH'] = [os.path.join(libfabric_dir, 'lib', 'prov')] + + elif impi_ver >= LooseVersion('2019'): + # The "release" library is default in v2019. Give it precedence over intel64/lib. + # (remember paths are *prepended*, so the last path in the list has highest priority) + lib_dirs = [os.path.join('intel64', x) for x in ['lib', os.path.join('lib', 'release')]] + include_dirs = [os.path.join('intel64', 'include')] + path_dirs = [os.path.join('intel64', 'bin')] + if self.cfg['ofi_internal']: + lib_dirs.append(os.path.join('intel64', 'libfabric', 'lib')) + path_dirs.append(os.path.join('intel64', 'libfabric', 'bin')) + guesses['FI_PROVIDER_PATH'] = [os.path.join('intel64', 'libfabric', 'lib', 'prov')] + else: + lib_dirs = [os.path.join('lib', 'em64t'), 'lib64'] + include_dirs = ['include64'] + path_dirs = [os.path.join('bin', 'intel64'), 'bin64'] + guesses['MIC_LD_LIBRARY_PATH'] = [os.path.join('mic', 'lib')] + + guesses.update({ + 'PATH': path_dirs, + 'LD_LIBRARY_PATH': lib_dirs, + 'LIBRARY_PATH': lib_dirs, + 'MANPATH': [manpath], + 'CPATH': include_dirs, + }) + + return guesses + + def make_module_extra(self, *args, **kwargs): + """Overwritten from Application to add extra txt""" + + if LooseVersion(self.version) >= LooseVersion('2021'): + mpiroot = os.path.join(self.installdir, 'mpi', self.version) + else: + mpiroot = self.installdir + + txt = super(EB_impi, self).make_module_extra(*args, **kwargs) + txt += self.module_generator.set_environment('I_MPI_ROOT', mpiroot) + if self.cfg['set_mpi_wrappers_compiler'] or self.cfg['set_mpi_wrappers_all']: + for var in ['CC', 'CXX', 'F77', 'F90', 'FC']: + if var == 'FC': + # $FC isn't defined by EasyBuild framework, so use $F90 instead + src_var = 'F90' + else: + src_var = var + + target_var = 'I_MPI_%s' % var + + val = os.getenv(src_var) + if val: + txt += self.module_generator.set_environment(target_var, val) + else: + raise EasyBuildError("Environment variable $%s not set, can't define $%s", src_var, target_var) + + if self.cfg['set_mpi_wrapper_aliases_gcc'] or self.cfg['set_mpi_wrappers_all']: + # force mpigcc/mpigxx to use GCC compilers, as would be expected based on their name + txt += self.module_generator.set_alias('mpigcc', 'mpigcc -cc=gcc') + txt += self.module_generator.set_alias('mpigxx', 'mpigxx -cxx=g++') + + if self.cfg['set_mpi_wrapper_aliases_intel'] or self.cfg['set_mpi_wrappers_all']: + # do the same for mpiicc/mpiipc/mpiifort to be consistent, even if they may not exist + txt += self.module_generator.set_alias('mpiicc', 'mpiicc -cc=icc') + txt += self.module_generator.set_alias('mpiicpc', 'mpiicpc -cxx=icpc') + # -fc also works, but -f90 takes precedence + txt += self.module_generator.set_alias('mpiifort', 'mpiifort -f90=ifort') + + # set environment variable UCX_TLS to 'all', this works in all hardware configurations + # needed with UCX regardless of the transports available (even without a Mellanox HCA) + # more information in easybuilders/easybuild-easyblocks#2253 + if get_software_root('UCX'): + # do not overwrite settings in the easyconfig + if 'UCX_TLS' not in self.cfg['modextravars']: + txt += self.module_generator.set_environment('UCX_TLS', 'all') + + return txt diff --git a/easyblocks/o/openmpi.py b/easyblocks/o/openmpi.py new file mode 100644 index 0000000000000000000000000000000000000000..11870c6730988e741dc365ab035072910ee66d5a --- /dev/null +++ b/easyblocks/o/openmpi.py @@ -0,0 +1,243 @@ +## +# Copyright 2019-2023 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +## +""" +EasyBuild support for OpenMPI, implemented as an easyblock + +@author: Kenneth Hoste (Ghent University) +@author: Robert Mijakovic (LuxProvide) +""" +import os +import re +from easybuild.tools import LooseVersion + +import easybuild.tools.toolchain as toolchain +from easybuild.easyblocks.generic.configuremake import ConfigureMake +from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.config import build_option +from easybuild.tools.modules import get_software_root +from easybuild.tools.systemtools import check_os_dependency, get_shared_lib_ext +from easybuild.tools.toolchain.mpi import get_mpi_cmd_template + + +class EB_OpenMPI(ConfigureMake): + """OpenMPI easyblock.""" + + def configure_step(self): + """Custom configuration step for OpenMPI.""" + + def config_opt_used(key, enable_opt=False): + """Helper function to check whether a configure option is already specified in 'configopts'.""" + if enable_opt: + regex = '--(disable|enable)-%s' % key + else: + regex = '--(with|without)-%s' % key + + return bool(re.search(regex, self.cfg['configopts'])) + + config_opt_names = [ + # suppress failure modes in relation to mpirun path + 'mpirun-prefix-by-default', + # build shared libraries + 'shared', + ] + + for key in config_opt_names: + if not config_opt_used(key, enable_opt=True): + self.cfg.update('configopts', '--enable-%s' % key) + + # List of EasyBuild dependencies for which OMPI has known options + known_dependencies = ('CUDA', 'hwloc', 'libevent', 'libfabric', 'PMIx', 'UCX', 'UCC') + # Value to use for `--with-<dep>=<value>` if the dependency is not specified in the easyconfig + # No entry is interpreted as no option added at all + # This is to make builds reproducible even when the system libraries are changed and avoids failures + # due to e.g. finding only PMIx but not libevent on the system + unused_dep_value = dict() + # Known options since version 3.0 (no earlier ones checked) + if LooseVersion(self.version) >= LooseVersion('3.0'): + # Default to disable the option with "no" + unused_dep_value = {dep: 'no' for dep in known_dependencies} + # For these the default is to use an internal copy and not using any is not supported + for dep in ('hwloc', 'libevent', 'PMIx'): + unused_dep_value[dep] = 'internal' + + # handle dependencies + for dep in known_dependencies: + opt_name = dep.lower() + # If the option is already used, don't add it + if config_opt_used(opt_name): + continue + + # libfabric option renamed in OpenMPI 3.1.0 to ofi + if dep == 'libfabric' and LooseVersion(self.version) >= LooseVersion('3.1'): + opt_name = 'ofi' + # Check new option name. They are synonyms since 3.1.0 for backward compatibility + if config_opt_used(opt_name): + continue + + dep_root = get_software_root(dep) + # If the dependency is loaded, specify its path, else use the "unused" value, if any + if dep_root: + opt_value = dep_root + else: + opt_value = unused_dep_value.get(dep) + if opt_value is not None: + self.cfg.update('configopts', '--with-%s=%s' % (opt_name, opt_value)) + + if bool(get_software_root('PMIx')) != bool(get_software_root('libevent')): + raise EasyBuildError('You must either use both PMIx and libevent as dependencies or none of them. ' + 'This is to enforce the same libevent is used for OpenMPI as for PMIx or ' + 'the behavior may be unpredictable.') + + # check whether VERBS support should be enabled + if not config_opt_used('verbs'): + + # for OpenMPI v4.x, the openib BTL should be disabled when UCX is used; + # this is required to avoid "error initializing an OpenFabrics device" warnings, + # see also https://www.open-mpi.org/faq/?category=all#ofa-device-error + is_ucx_enabled = ('--with-ucx' in self.cfg['configopts'] and + '--with-ucx=no' not in self.cfg['configopts']) + if LooseVersion(self.version) >= LooseVersion('4.0.0') and is_ucx_enabled: + verbs = False + else: + # auto-detect based on available OS packages + os_packages = EASYCONFIG_CONSTANTS['OS_PKG_IBVERBS_DEV'][0] + verbs = any(check_os_dependency(osdep) for osdep in os_packages) + # for OpenMPI v5.x, the verbs support is removed, only UCX is available + # see https://github.com/open-mpi/ompi/pull/6270 + if LooseVersion(self.version) <= LooseVersion('5.0.0'): + if verbs: + self.cfg.update('configopts', '--with-verbs') + else: + self.cfg.update('configopts', '--without-verbs') + + super(EB_OpenMPI, self).configure_step() + + def test_step(self): + """Test step for OpenMPI""" + # Default to `make check` if nothing is set. Disable with "runtest = False" in the EC + if self.cfg['runtest'] is None: + self.cfg['runtest'] = 'check' + + super(EB_OpenMPI, self).test_step() + + def load_module(self, *args, **kwargs): + """ + Load (temporary) module file, after resetting to initial environment. + + Also put RPATH wrappers back in place if needed, to ensure that sanity check commands work as expected. + """ + super(EB_OpenMPI, self).load_module(*args, **kwargs) + + # ensure RPATH wrappers are in place, otherwise compiling minimal test programs will fail + if build_option('rpath'): + if self.toolchain.options.get('rpath', True): + self.toolchain.prepare_rpath_wrappers(rpath_filter_dirs=self.rpath_filter_dirs, + rpath_include_dirs=self.rpath_include_dirs) + + def sanity_check_step(self): + """Custom sanity check for OpenMPI.""" + + bin_names = ['mpicc', 'mpicxx', 'mpif90', 'mpifort', 'mpirun', 'ompi_info', 'opal_wrapper'] + if LooseVersion(self.version) >= LooseVersion('5.0.0'): + bin_names.append('prterun') + else: + bin_names.append('orterun') + bin_files = [os.path.join('bin', x) for x in bin_names] + + shlib_ext = get_shared_lib_ext() + lib_names = ['mpi_mpifh', 'mpi', 'open-pal'] + if LooseVersion(self.version) >= LooseVersion('5.0.0'): + lib_names.append('prrte') + else: + lib_names.extend(['ompitrace', 'open-rte']) + lib_files = [os.path.join('lib', 'lib%s.%s' % (x, shlib_ext)) for x in lib_names] + + inc_names = ['mpi-ext', 'mpif-config', 'mpif', 'mpi', 'mpi_portable_platform'] + if LooseVersion(self.version) >= LooseVersion('5.0.0'): + inc_names.append('prte') + inc_files = [os.path.join('include', x + '.h') for x in inc_names] + + custom_paths = { + 'files': bin_files + inc_files + lib_files, + 'dirs': [], + } + + # make sure MPI compiler wrappers pick up correct compilers + expected = { + 'mpicc': os.getenv('CC', 'gcc'), + 'mpicxx': os.getenv('CXX', 'g++'), + 'mpifort': os.getenv('FC', 'gfortran'), + 'mpif90': os.getenv('F90', 'gfortran'), + } + # actual pattern for gfortran is "GNU Fortran" + for key in ['mpifort', 'mpif90']: + if expected[key] == 'gfortran': + expected[key] = "GNU Fortran" + # for PGI, correct pattern is "pgfortran" with mpif90 + if expected['mpif90'] == 'pgf90': + expected['mpif90'] = 'pgfortran' + # for Clang the pattern is always clang + for key in ['mpicxx', 'mpifort', 'mpif90']: + if expected[key] in ['clang++', 'flang']: + expected[key] = 'clang' + + custom_commands = ["%s --version | grep '%s'" % (key, expected[key]) for key in sorted(expected.keys())] + + # Add minimal test program to sanity checks + # Run with correct MPI launcher + mpi_cmd_tmpl, params = get_mpi_cmd_template(toolchain.OPENMPI, dict(), mpi_version=self.version) + # Limit number of ranks to 8 to avoid it failing due to hyperthreading + ranks = min(8, self.cfg['parallel']) + for srcdir, src, compiler in ( + ('examples', 'hello_c.c', 'mpicc'), + ('examples', 'hello_mpifh.f', 'mpifort'), + ('examples', 'hello_usempi.f90', 'mpif90'), + ('examples', 'ring_c.c', 'mpicc'), + ('examples', 'ring_mpifh.f', 'mpifort'), + #('examples', 'ring_usempi.f90', 'mpif90'), + ('test/simple', 'thread_init.c', 'mpicc'), + ('test/simple', 'intercomm1.c', 'mpicc'), + ('test/simple', 'mpi_barrier.c', 'mpicc'), + ): + src_path = os.path.join(self.cfg['start_dir'], srcdir, src) + if os.path.exists(src_path): + test_exe = os.path.join(self.builddir, 'mpi_test_' + os.path.splitext(src)[0]) + self.log.info("Adding minimal MPI test program to sanity checks: %s", test_exe) + + # Build test binary + custom_commands.append("%s %s -o %s" % (compiler, src_path, test_exe)) + + # Run the test if chosen + if build_option('mpi_tests'): + params.update({'nr_ranks': ranks, 'cmd': test_exe}) + # Allow oversubscription for this test (in case of hyperthreading) + custom_commands.append("OMPI_MCA_rmaps_base_oversubscribe=1 " + mpi_cmd_tmpl % params) + # Run with 1 process which may trigger other bugs + # See https://github.com/easybuilders/easybuild-easyconfigs/issues/12978 + params['nr_ranks'] = 1 + custom_commands.append(mpi_cmd_tmpl % params) + + super(EB_OpenMPI, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) diff --git a/easyblocks/o/orca.py b/easyblocks/o/orca.py new file mode 100644 index 0000000000000000000000000000000000000000..67b786e8d0be80ab2cedec1b53ed83db7c2cf1a3 --- /dev/null +++ b/easyblocks/o/orca.py @@ -0,0 +1,177 @@ +## +# Copyright 2021-2024 Vrije Universiteit Brussel +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +## +""" +EasyBuild support for ORCA, implemented as an easyblock + +@author: Alex Domingo (Vrije Universiteit Brussel) +""" +import glob +import os + +from easybuild.tools import LooseVersion +from easybuild.easyblocks.generic.makecp import MakeCp +from easybuild.easyblocks.generic.packedbinary import PackedBinary +from easybuild.framework.easyconfig import CUSTOM +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.filetools import write_file +from easybuild.tools.py2vs3 import string_type +from easybuild.tools.systemtools import X86_64, get_cpu_architecture + + +class EB_ORCA(PackedBinary, MakeCp): + """ + ORCA installation files are extracted and placed in standard locations using 'files_to_copy' from MakeCp. + Sanity checks on files are automatically generated based on the contents of 'files_to_copy' by gathering + the target files in the build directory and checking their presence in the installation directory. + Sanity checks also include a quick test calculating the HF energy of a water molecule. + """ + + @staticmethod + def extra_options(extra_vars=None): + """Extra easyconfig parameters for ORCA.""" + extra_vars = MakeCp.extra_options() + extra_vars.update(PackedBinary.extra_options()) + + # files_to_copy is not mandatory here, since we set it by default in install_step + extra_vars['files_to_copy'][2] = CUSTOM + + return extra_vars + + def __init__(self, *args, **kwargs): + """Init and validate easyconfig parameters and system architecture""" + super(EB_ORCA, self).__init__(*args, **kwargs) + + # If user overwrites 'files_to_copy', custom 'sanity_check_paths' must be present + if self.cfg['files_to_copy'] and not self.cfg['sanity_check_paths']: + raise EasyBuildError("Found 'files_to_copy' option in easyconfig without 'sanity_check_paths'") + + # Add orcaarch template for supported architectures + myarch = get_cpu_architecture() + if myarch == X86_64: + orcaarch = 'x86-64' + else: + raise EasyBuildError("Architecture %s is not supported by ORCA on EasyBuild", myarch) + + self.cfg.template_values['orcaarch'] = orcaarch + self.cfg.generate_template_values() + + def install_step(self): + """Install ORCA with MakeCp easyblock""" + + if not self.cfg['files_to_copy']: + # Put installation files in standard locations + files_to_copy = [ + (['auto*', 'orca*', 'otool*'], 'bin'), + (['*.pdf'], 'share'), + ] + # Version 5 extra files + if LooseVersion('5.0.0') <= LooseVersion(self.version) < LooseVersion('6.0.0'): + compoundmethods = (['ORCACompoundMethods'], 'bin') + files_to_copy.append(compoundmethods) + # Shared builds have additional libraries + libs_to_copy = (['liborca*'], 'lib') + if all([glob.glob(p) for p in libs_to_copy[0]]): + files_to_copy.append(libs_to_copy) + + self.cfg['files_to_copy'] = files_to_copy + + MakeCp.install_step(self) + + def sanity_check_step(self): + """Custom sanity check for ORCA""" + custom_paths = None + + if not self.cfg['sanity_check_paths']: + custom_paths = {'files': [], 'dirs': []} + + if self.cfg['files_to_copy']: + # Convert 'files_to_copy' to list of files in build directory + for spec in self.cfg['files_to_copy']: + if isinstance(spec, tuple): + file_pattern = spec[0] + dest_dir = spec[1] + elif isinstance(spec, string_type): + file_pattern = spec + dest_dir = '' + else: + raise EasyBuildError( + "Found neither string nor tuple as file to copy: '%s' (type %s)", spec, type(spec) + ) + + if isinstance(file_pattern, string_type): + file_pattern = [file_pattern] + + source_files = [] + for pattern in file_pattern: + source_files.extend(glob.glob(pattern)) + + # Add files to custom sanity checks + for source in source_files: + if os.path.isfile(source): + custom_paths['files'].append(os.path.join(dest_dir, source)) + else: + custom_paths['dirs'].append(os.path.join(dest_dir, source)) + else: + # Minimal check of files (needed by --module-only) + custom_paths['files'] = ['bin/orca'] + +# # Simple test: HF energy of water molecule +# test_input_content = """ +#!HF DEF2-SVP +#%%PAL NPROCS %(nprocs)s END +#* xyz 0 1 +#O 0.0000 0.0000 0.0626 +#H -0.7920 0.0000 -0.4973 +#H 0.7920 0.0000 -0.4973 +#* +#""" +# nprocs = self.cfg.get('parallel', 1) +# test_input_content = test_input_content % {'nprocs': nprocs} +# test_input_path = os.path.join(self.builddir, 'eb_test_hf_water.inp') +# write_file(test_input_path, test_input_content) +# +# # Reference total energy +# test_output_energy = '-75.95934031' +# test_output_regex = 'FINAL SINGLE POINT ENERGY[ \t]*%s' % test_output_energy +# +# # Instruct openmpi to treat hardware threads as slot +# test_ompi_env = 'env OMPI_MCA_hwloc_base_use_hwthreads_as_cpus=1' +# +# # ORCA has to be executed using its full path to run in parallel +# if os.path.isdir(os.path.join(self.installdir, 'bin')): +# orca_bin = '$EBROOTORCA/bin/orca' +# else: +# orca_bin = '$(which orca)' +# +# test_orca_cmd = "%s %s %s" % (test_ompi_env, orca_bin, test_input_path) +# +# custom_commands = [ +# # Execute test in ORCA +# test_orca_cmd, +# # Repeat test and check total energy +# "%s | grep -c '%s'" % (test_orca_cmd, test_output_regex), +# ] +# +# super(EB_ORCA, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) diff --git a/easyblocks/v/visit.py b/easyblocks/v/visit.py index 47f1c18e435b8e578519249f0578a9415d70938d..0efa00e2fb6c1a108788699e8c1a92a38ab8e431 100644 --- a/easyblocks/v/visit.py +++ b/easyblocks/v/visit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -EasyBuild support for building and installing VisIt 3.1.0, +EasyBuild support for building and installing VisIt 3.3.3, implemented as an easyblock. @author: Lukas Krupcik (IT4Innovations) @@ -31,9 +31,11 @@ class EB_VisIt(EasyBlock): """Simply run installation script with configuration options""" self.log.info("Changing VisIt installer permission") os.chdir(self.cfg['start_dir']) - chmod_cmd = "chmod 755 visit-install3_1_0" + #chmod_cmd = "chmod 755 visit-install3_3_3" + chmod_cmd = "chmod 755 visit-install2_13_3" run_cmd(chmod_cmd, log_all=True) - install_cmd = "./visit-install3_1_0 -c none 3.1.0 \ + #install_cmd = "./visit-install3_3_3 -c none 3.3.3 \ + install_cmd = "./visit-install2_13_3 -c none 2.13.3 \ linux-x86_64-rhel7 %s" % self.installdir self.log.info("Running VisIt installer") run_cmd(install_cmd, log_all=True) diff --git a/w/wrf_sfire.py b/w/wrf_sfire.py new file mode 100644 index 0000000000000000000000000000000000000000..29f2058d932444a00292655d49128a6d9f1b3cfd --- /dev/null +++ b/w/wrf_sfire.py @@ -0,0 +1,233 @@ +## +# Copyright 2009-2023 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +## +""" +EasyBuild support for building and installing WRF-Fire, implemented as an easyblock + +author: Kenneth Hoste (HPC-UGent) +""" +import os + +import easybuild.tools.environment as env +import easybuild.tools.toolchain as toolchain +from easybuild.framework.easyblock import EasyBlock +from easybuild.framework.easyconfig import CUSTOM, MANDATORY +from easybuild.tools.filetools import apply_regex_substitutions, change_dir, patch_perl_script_autoflush +from easybuild.tools.modules import get_software_root +from easybuild.tools.run import run_cmd, run_cmd_qa +from easybuild.tools.build_log import EasyBuildError + + +class EB_WRF_minus_Sfire(EasyBlock): + """Support for building/installing WRF-Sfire.""" + + @staticmethod + def extra_options(): + """Custom easyconfig parameters for WRF-Sfire.""" + extra_vars = { + 'buildtype': [None, "Specify the type of build (serial, smpar (OpenMP), " + "dmpar (MPI), dm+sm (hybrid OpenMP/MPI)).", MANDATORY], + 'runtest': [True, "Build and run WRF tests", CUSTOM], + } + return EasyBlock.extra_options(extra_vars) + + def __init__(self, *args, **kwargs): + """Add extra config options specific to WRF.""" + super(EB_WRF_minus_Sfire, self).__init__(*args, **kwargs) + + self.build_in_installdir = True + + def extract_step(self): + """Extract WRF-Sfire sources.""" + self.cfg.update('unpack_options', '--strip-components=1') + super(EB_WRF_minus_Sfire, self).extract_step() + + def configure_step(self): + """Custom configuration procedure for WRF-Sfire.""" + + comp_fam = self.toolchain.comp_family() + + # define $NETCDF* for netCDF dependency + netcdf_fortran = get_software_root('netCDF-Fortran') + if netcdf_fortran: + env.setvar('NETCDF', netcdf_fortran) + else: + raise EasyBuildError("Required dependendy netCDF-Fortran is missing") + + # define $PHDF5 for parallel HDF5 dependency + hdf5 = get_software_root('HDF5') + if hdf5 and os.path.exists(os.path.join(hdf5, 'bin', 'h5pcc')): + env.setvar('PHDF5', hdf5) + + # first, configure WRF part + change_dir(os.path.join(self.cfg['start_dir'], 'WRFV3')) + + # instruct WRF-Sfire to create netCDF v4 output files + env.setvar('WRFIO_NETCDF4_FILE_SUPPORT', '1') + + # patch arch/Config_new.pl script, so that run_cmd_qa receives all output to answer questions + patch_perl_script_autoflush(os.path.join('arch', 'Config_new.pl')) + + # determine build type option to look for + known_build_type_options = { + toolchain.INTELCOMP: "Linux x86_64 i486 i586 i686, ifort compiler with icc", + toolchain.GCC: "x86_64 Linux, gfortran compiler with gcc", + toolchain.PGI: "Linux x86_64, PGI compiler with pgcc", + } + build_type_option = known_build_type_options.get(comp_fam) + if build_type_option is None: + raise EasyBuildError("Don't know which WPS configure option to select for compiler family %s", comp_fam) + + build_type_question = r"\s*(?P<nr>[0-9]+).\s*%s\s*\(%s\)" % (build_type_option, self.cfg['buildtype']) + qa = { + "Compile for nesting? (1=basic, 2=preset moves, 3=vortex following) [default 1]:": '1', + } + std_qa = { + # named group in match will be used to construct answer + r"%s.*\n(.*\n)*Enter selection\s*\[[0-9]+-[0-9]+\]\s*:" % build_type_question: '%(nr)s', + } + run_cmd_qa('./configure', qa, std_qa=std_qa, log_all=True, simple=True) + + cpp_flag = None + if comp_fam == toolchain.INTELCOMP: + cpp_flag = '-fpp' + elif comp_fam == toolchain.GCC: + cpp_flag = '-cpp' + else: + raise EasyBuildError("Don't know which flag to use to specify that Fortran files were preprocessed") + + # patch configure.wrf to get things right + comps = { + 'CFLAGS_LOCAL': os.getenv('CFLAGS'), + 'DM_FC': os.getenv('MPIF90'), + 'DM_CC': "%s -DMPI2_SUPPORT" % os.getenv('MPICC'), + 'FCOPTIM': os.getenv('FFLAGS'), + # specify that Fortran files have been preprocessed with cpp, + # see http://forum.wrfforum.com/viewtopic.php?f=5&t=6086 + 'FORMAT_FIXED': "-FI %s" % cpp_flag, + 'FORMAT_FREE': "-FR %s" % cpp_flag, + } + regex_subs = [(r"^(%s\s*=\s*).*$" % k, r"\1 %s" % v) for (k, v) in comps.items()] + apply_regex_substitutions('configure.wrf', regex_subs) + + # also configure WPS part + change_dir(os.path.join(self.cfg['start_dir'], 'WPS')) + + # patch arch/Config_new.pl script, so that run_cmd_qa receives all output to answer questions + patch_perl_script_autoflush(os.path.join('arch', 'Config.pl')) + + # determine build type option to look for + known_build_type_options = { + toolchain.INTELCOMP: "PC Linux x86_64, Intel compiler", + toolchain.GCC: "PC Linux x86_64, g95 compiler", + toolchain.PGI: "PC Linux x86_64 (IA64 and Opteron), PGI compiler 5.2 or higher", + } + build_type_option = known_build_type_options.get(comp_fam) + if build_type_option is None: + raise EasyBuildError("Don't know which WPS configure option to select for compiler family %s", comp_fam) + + known_wps_build_types = { + 'dmpar': 'DM parallel', + 'smpar': 'serial', + } + wps_build_type = known_wps_build_types.get(self.cfg['buildtype']) + if wps_build_type is None: + raise EasyBuildError("Don't know which WPS build type to pick for '%s'", self.cfg['builddtype']) + + build_type_question = r"\s*(?P<nr>[0-9]+).\s*%s.*%s(?!NO GRIB2)" % (build_type_option, wps_build_type) + std_qa = { + # named group in match will be used to construct answer + r"%s.*\n(.*\n)*Enter selection\s*\[[0-9]+-[0-9]+\]\s*:" % build_type_question: '%(nr)s', + } + run_cmd_qa('./configure', {}, std_qa=std_qa, log_all=True, simple=True) + + # patch configure.wps to get things right + comps = { + 'CC': '%s %s' % (os.getenv('MPICC'), os.getenv('CFLAGS')), + 'FC': '%s %s' % (os.getenv('MPIF90'), os.getenv('F90FLAGS')) + } + regex_subs = [(r"^(%s\s*=\s*).*$" % k, r"\1 %s" % v) for (k, v) in comps.items()] + # specify that Fortran90 files have been preprocessed with cpp + regex_subs.extend([ + (r"^(F77FLAGS\s*=\s*)", r"\1 %s " % cpp_flag), + (r"^(FFLAGS\s*=\s*)", r"\1 %s " % cpp_flag), + ]) + apply_regex_substitutions('configure.wps', regex_subs) + + def build_step(self): + """Custom build procedure for WRF-Sfire.""" + + cmd = './compile' + if self.cfg['parallel']: + cmd += " -j %d" % self.cfg['parallel'] + + # first, build WRF part + change_dir(os.path.join(self.cfg['start_dir'], 'WRFV3')) + (out, ec) = run_cmd(cmd + ' em_fire', log_all=True, simple=False, log_ok=True) + + # next, build WPS part + change_dir(os.path.join(self.cfg['start_dir'], 'WPS')) + (out, ec) = run_cmd('./compile', log_all=True, simple=False, log_ok=True) + + def test_step(self): + """Custom built-in test procedure for WRF-Sfire.""" + if self.cfg['runtest']: + change_dir(os.path.join(self.cfg['start_dir'], 'WRFV3', 'test', 'em_fire', 'hill')) + + if self.cfg['buildtype'] in ['dmpar', 'smpar', 'dm+sm']: + test_cmd = "ulimit -s unlimited && %s && %s" % (self.toolchain.mpi_cmd_for("./ideal.exe", 1), + self.toolchain.mpi_cmd_for("./wrf.exe", 2)) + else: + test_cmd = "ulimit -s unlimited && ./ideal.exe && ./wrf.exe" + run_cmd(test_cmd, simple=True, log_all=True, log_ok=True) + + # building/installing is done in build_step, so we can run tests + def install_step(self): + """Building was done in install dir, so nothing to do in install_step.""" + pass + + def sanity_check_step(self): + """Custom sanity check for WRF-Sfire.""" + custom_paths = { + 'files': [os.path.join('WRFV3', 'main', f) for f in ['ideal.exe', 'libwrflib.a', 'wrf.exe']] + + [os.path.join('WPS', f) for f in ['geogrid.exe', 'metgrid.exe', 'ungrib.exe']], + 'dirs': [os.path.join('WRFV3', d) for d in ['main', 'run']], + } + super(EB_WRF_minus_Sfire, self).sanity_check_step(custom_paths=custom_paths) + + def make_module_req_guess(self): + """Custom guesses for generated WRF-Sfire module file.""" + wrf_maindir = os.path.join('WRFV3', 'main') + return { + 'LD_LIBRARY_PATH': [wrf_maindir], + 'PATH': [wrf_maindir, 'WPS'], + } + + def make_module_extra(self): + """Add netCDF environment variables to module file.""" + txt = super(EB_WRF_minus_Sfire, self).make_module_extra() + netcdf_fortran = get_software_root('netCDF-Fortran') + if netcdf_fortran: + txt += self.module_generator.set_environment('NETCDF', netcdf_fortran) + return txt