diff --git a/docs/configure.rst b/docs/configure.rst index 91514c9de5..a1b61a1ed0 100644 --- a/docs/configure.rst +++ b/docs/configure.rst @@ -94,8 +94,12 @@ The valid attributes of a system are the following: * ``descr``: A detailed description of the system (default is the system name). * ``hostnames``: This is a list of hostname patterns that will be used by ReFrame when it tries to `auto-detect <#system-auto-detection>`__ the current system (default ``[]``). -* ``modules_system``: The modules system that should be used for loading environment modules on this system. - The only available modules system backend is currently ``tmod``, which corresponds to the `TCL implementation `__ of the environment modules (default :class:`None`). +* ``modules_system``: The modules system that should be used for loading environment modules on this system (default :class:`None`). + Three types of modules systems are currently supported: + + - ``tmod``: The classic Tcl implementation of the `environment modules `__. + - ``tmod4``: The version 4 of the Tcl implementation of the `environment modules `__. + - ``lmod``: The Lua implementation of the `environment modules `__. * ``prefix``: Default regression prefix for this system (default ``.``). * ``stagedir``: Default stage directory for this system (default :class:`None`). * ``outputdir``: Default output directory for this system (default :class:`None`). diff --git a/reframe/core/modules.py b/reframe/core/modules.py index ff55270938..db6ed098b4 100644 --- a/reframe/core/modules.py +++ b/reframe/core/modules.py @@ -9,7 +9,8 @@ import reframe.core.fields as fields import reframe.utility.os_ext as os_ext -from reframe.core.exceptions import ConfigError, EnvironError +from reframe.core.exceptions import (ConfigError, EnvironError, + SpawnedProcessError) class Module: @@ -451,6 +452,46 @@ def emit_unload_instr(self, module): return 'module unload %s' % module +class TMod4Impl(TModImpl): + """Module system for TMod 4.""" + + def __init__(self): + self._command = 'modulecmd python' + try: + completed = os_ext.run_command(self._command + ' -V', check=True) + except OSError as e: + raise ConfigError( + 'could not find a sane Tmod4 installation') from e + except SpawnedProcessError as e: + raise ConfigError( + 'could not get the Python bindings for Tmod4') from e + + version_match = re.match('^Modules Release (\S+)\s+', completed.stderr) + if not version_match: + raise ConfigError('could not retrieve the TMod4 version') + + self._version = version_match.group(1) + + def name(self): + return 'tmod4' + + def _exec_module_command(self, *args, msg=None): + command = ' '.join([self._command, *args]) + completed = os_ext.run_command(command, check=True) + namespace = {} + exec(completed.stdout, {}, namespace) + if not namespace['_mlstatus']: + # _mlstatus is set by the TMod4 Python bindings + if msg is None: + msg = 'modules system command failed: ' + if isinstance(completed.args, str): + msg += completed.args + else: + msg += ' '.join(completed.args) + + raise EnvironError(msg) + + class LModImpl(TModImpl): """Module system for Lmod (Tcl/Lua).""" @@ -570,6 +611,8 @@ def init_modules_system(modules_kind=None): _modules_system = ModulesSystem(NoModImpl()) elif modules_kind == 'tmod': _modules_system = ModulesSystem(TModImpl()) + elif modules_kind == 'tmod4': + _modules_system = ModulesSystem(TMod4Impl()) elif modules_kind == 'lmod': _modules_system = ModulesSystem(LModImpl()) else: diff --git a/unittests/test_modules.py b/unittests/test_modules.py index 7cd2608bee..e0587d19eb 100644 --- a/unittests/test_modules.py +++ b/unittests/test_modules.py @@ -116,6 +116,22 @@ def expected_unload_instr(self, module): return 'module unload %s' % module +class TestTMod4ModulesSystem(_TestModulesSystem): + def setUp(self): + try: + modules.init_modules_system('tmod4') + except ConfigError: + self.skipTest('tmod4 not supported') + else: + super().setUp() + + def expected_load_instr(self, module): + return 'module load %s' % module + + def expected_unload_instr(self, module): + return 'module unload %s' % module + + class TestLModModulesSystem(_TestModulesSystem): def setUp(self): try: