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: