Repository: easybuilders/easybuild-framework

Issue:
Misdectection of EnvironnementModules version when use a non-released module version
Hi,
Using unreleased version of `cea-hpc/modules` leads to a misdetection of `module` version by EasyBuild.

When `module --version` for unreleased version
```
Modules Release 4.7.1+13-g15cdc75 (2021-04-25)
```
For instance, released version tell
```
Modules Release 4.8.0 (2021-07-14)
```
EasyBuild Configuration
```
[config]
module-syntax=Tcl
modules-tool=EnvironmentModules
```

EasyBuild failed with "invalid version number" error.
```
(venv_easybuild_u22) admin@ran:~$ eb 
== Temporary log file in case of crash /tmp/eb-hdr88n8f/easybuild-xvpquc0b.log
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/opt/linux/apps/tools/venv_easybuild_u22/lib/python3.10/site-packages/easybuild/main.py", line 597, in <module>
    main()
  File "/opt/linux/apps/tools/venv_easybuild_u22/lib/python3.10/site-packages/easybuild/main.py", line 249, in main
    modtool = modules_tool(testing=testing)
  File "/opt/linux/apps/tools/venv_easybuild_u22/lib/python3.10/site-packages/easybuild/tools/modules.py", line 1681, in modules_tool
    return modules_tool_class(mod_paths=mod_paths, testing=testing)
  File "/opt/linux/apps/tools/venv_easybuild_u22/lib/python3.10/site-packages/easybuild/tools/modules.py", line 207, in __init__
    self.set_and_check_version()
  File "/opt/linux/apps/tools/venv_easybuild_u22/lib/python3.10/site-packages/easybuild/tools/modules.py", line 263, in set_and_check_version
    if StrictVersion(self.version) < StrictVersion(self.REQ_VERSION):
  File "/usr/lib/python3.10/distutils/version.py", line 40, in __init__
    self.parse(vstring)
  File "/usr/lib/python3.10/distutils/version.py", line 137, in parse
    raise ValueError("invalid version number '%s'" % vstring)
ValueError: invalid version number '4.7.1+13-g15cdc75'
```

Software versions
- Ubuntu 22.04
- Python 3.10.6
- easybuild==4.5.2
- easybuild-easyblocks==4.5.2
- easybuild-easyconfigs==4.5.2
- easybuild-framework==4.5.2

Best regards,
Tristan



Discussion:
I guess this line should be adjusted
```
VERSION_REGEXP = r'^Modules\s+Release\s+(?P<version>\d\S*)\s'
```
[modules.py#L1329](https://github.com/easybuilders/easybuild-framework/blob/1ea5d5714db167284cc2825c3313ed2075601a31/easybuild/tools/modules.py#L1329)
@tristanlt Indeed, that regular expression doesn't take into account that there may be extra "junk" after the `4.7.1` version...

Resolving patch:
diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py
--- a/easybuild/tools/modules.py
+++ b/easybuild/tools/modules.py
@@ -1326,7 +1326,7 @@ class EnvironmentModules(EnvironmentModulesTcl):
     COMMAND_ENVIRONMENT = 'MODULES_CMD'
     REQ_VERSION = '4.0.0'
     MAX_VERSION = None
-    VERSION_REGEXP = r'^Modules\s+Release\s+(?P<version>\d\S*)\s'
+    VERSION_REGEXP = r'^Modules\s+Release\s+(?P<version>\d[^+\s]*)(\+\S*)?\s'
 
     def __init__(self, *args, **kwargs):
         """Constructor, set Environment Modules-specific class variable values."""


Test patch:
diff --git a/test/framework/modulestool.py b/test/framework/modulestool.py
--- a/test/framework/modulestool.py
+++ b/test/framework/modulestool.py
@@ -209,6 +209,21 @@ def test_environment_modules_specific(self):
             mt = EnvironmentModules(testing=True)
             self.assertIsInstance(mt.loaded_modules(), list)  # dummy usage
 
+            # initialize Environment Modules tool with non-official version number
+            # pass (fake) full path to 'modulecmd.tcl' via $MODULES_CMD
+            fake_path = os.path.join(self.test_installpath, 'libexec', 'modulecmd.tcl')
+            fake_modulecmd_txt = '\n'.join([
+                'puts stderr {Modules Release 5.3.1+unload-188-g14b6b59b (2023-10-21)}',
+                "puts {os.environ['FOO'] = 'foo'}",
+            ])
+            write_file(fake_path, fake_modulecmd_txt)
+            os.chmod(fake_path, stat.S_IRUSR | stat.S_IXUSR)
+            os.environ['_module_raw'] = "() {  eval `%s' bash $*`;\n}" % fake_path
+            os.environ['MODULES_CMD'] = fake_path
+            EnvironmentModules.COMMAND = fake_path
+            mt = EnvironmentModules(testing=True)
+            self.assertTrue(os.path.samefile(mt.cmd, fake_path), "%s - %s" % (mt.cmd, fake_path))
+
     def tearDown(self):
         """Testcase cleanup."""
         super(ModulesToolTest, self).tearDown()