Skip to content
79 changes: 77 additions & 2 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,51 @@ def available(self, mod_name=None, extra_args=None):

return ans

def module_wrapper_exists(self, mod_name, modulerc_fn='.modulerc', mod_wrapper_regex_template=None):
"""
Determine whether a module wrapper with specified name exists.
Only .modulerc file in Tcl syntax is considered here.
"""
if mod_wrapper_regex_template is None:
mod_wrapper_regex_template = "^module-version (?P<wrapped_mod>[^ ]*) %s$"

wrapped_mod = None

mod_dir = os.path.dirname(mod_name)
wrapper_regex = re.compile(mod_wrapper_regex_template % os.path.basename(mod_name), re.M)
for mod_path in curr_module_paths():
modulerc_cand = os.path.join(mod_path, mod_dir, modulerc_fn)
if os.path.exists(modulerc_cand):
self.log.debug("Found %s that may define %s as a wrapper for a module file", modulerc_cand, mod_name)
res = wrapper_regex.search(read_file(modulerc_cand))
if res:
wrapped_mod = res.group('wrapped_mod')
self.log.debug("Confirmed that %s is a module wrapper for %s", mod_name, wrapped_mod)
break

mod_dir = os.path.dirname(mod_name)
if wrapped_mod is not None and not wrapped_mod.startswith(mod_dir):
# module wrapper uses 'short' module name of module being wrapped,
# so we need to correct it in case a hierarchical module naming scheme is used...
# e.g. 'Java/1.8.0_181' should become 'Core/Java/1.8.0_181' for wrapper 'Core/Java/1.8'
self.log.debug("Full module name prefix mismatch between module wrapper '%s' and wrapped module '%s'",
mod_name, wrapped_mod)

mod_name_parts = mod_name.split(os.path.sep)
wrapped_mod_subdir = ''
while not os.path.join(wrapped_mod_subdir, wrapped_mod).startswith(mod_dir) and mod_name_parts:
wrapped_mod_subdir = os.path.join(wrapped_mod_subdir, mod_name_parts.pop(0))

full_wrapped_mod_name = os.path.join(wrapped_mod_subdir, wrapped_mod)
if full_wrapped_mod_name.startswith(mod_dir):
self.log.debug("Full module name for wrapped module %s: %s", wrapped_mod, full_wrapped_mod_name)
wrapped_mod = full_wrapped_mod_name
else:
raise EasyBuildError("Failed to determine full module name for module wrapped by %s: %s | %s",
mod_name, wrapped_mod_subdir, wrapped_mod)

return wrapped_mod

def exist(self, mod_names, mod_exists_regex_template=r'^\s*\S*/%s.*:\s*$', skip_avail=False):
"""
Check if modules with specified names exists.
Expand Down Expand Up @@ -491,11 +536,24 @@ def mod_exists_via_show(mod_name):
for (mod_name, visible) in mod_names:
if visible:
# module name may be partial, so also check via 'module show' as fallback
mods_exist.append(mod_name in avail_mod_names or mod_exists_via_show(mod_name))
mod_exists = mod_name in avail_mod_names or mod_exists_via_show(mod_name)
else:
# hidden modules are not visible in 'avail', need to use 'show' instead
self.log.debug("checking whether hidden module %s exists via 'show'..." % mod_name)
mods_exist.append(mod_exists_via_show(mod_name))
mod_exists = mod_exists_via_show(mod_name)

# if no module file was found, check whether specified module name can be a 'wrapper' module...
if not mod_exists:
self.log.debug("Module %s not found via module avail/show, checking whether it is a wrapper", mod_name)
wrapped_mod = self.module_wrapper_exists(mod_name)
if wrapped_mod is not None:
# module wrapper only really exists if the wrapped module file is also available
mod_exists = wrapped_mod in avail_mod_names or mod_exists_via_show(wrapped_mod)
self.log.debug("Result for existence check of wrapped module %s: %s", wrapped_mod, mod_exists)

self.log.debug("Result for existence check of %s module: %s", mod_name, mod_exists)

mods_exist.append(mod_exists)

return mods_exist

Expand Down Expand Up @@ -1241,6 +1299,23 @@ def prepend_module_path(self, path, set_mod_paths=True, priority=None):
if set_mod_paths:
self.set_mod_paths()

def module_wrapper_exists(self, mod_name):
"""
Determine whether a module wrapper with specified name exists.
First check for wrapper defined in .modulerc.lua, fall back to also checking .modulerc (Tcl syntax).
"""
# first consider .modulerc.lua with Lmod 7.8 (or newer)
if StrictVersion(self.version) >= StrictVersion('7.8'):
mod_wrapper_regex_template = '^module_version\("(?P<wrapped_mod>.*)", "%s"\)$'
res = super(Lmod, self).module_wrapper_exists(mod_name, modulerc_fn='.modulerc.lua',
mod_wrapper_regex_template=mod_wrapper_regex_template)

# fall back to checking for .modulerc in Tcl syntax
if not res:
res = super(Lmod, self).module_wrapper_exists(mod_name)

return res

def exist(self, mod_names, skip_avail=False):
"""
Check if modules with specified names exists.
Expand Down
66 changes: 63 additions & 3 deletions test/framework/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
import easybuild.tools.modules as mod
from easybuild.framework.easyblock import EasyBlock
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import copy_file, copy_dir, mkdir, read_file, write_file
from easybuild.tools.modules import EnvironmentModules, EnvironmentModulesTcl, Lmod, NoModulesTool
from easybuild.tools.filetools import copy_file, copy_dir, mkdir, read_file, remove_file, write_file
from easybuild.tools.modules import EnvironmentModules, EnvironmentModulesC, EnvironmentModulesTcl, Lmod, NoModulesTool
from easybuild.tools.modules import curr_module_paths, get_software_libdir, get_software_root, get_software_version
from easybuild.tools.modules import invalidate_module_caches_for, modules_tool, reset_module_caches
from easybuild.tools.run import run_cmd
Expand Down Expand Up @@ -139,8 +139,8 @@ def test_exists(self):
self.assertEqual(self.modtool.exist(['OpenMPI/1.6.4'], skip_avail=True), [False])

# exists works on hidden modules in Lua syntax (only with Lmod)
test_modules_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'modules'))
if isinstance(self.modtool, Lmod):
test_modules_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'modules'))
# make sure only the .lua module file is there, otherwise this test doesn't work as intended
self.assertTrue(os.path.exists(os.path.join(test_modules_path, 'bzip2', '.1.0.6.lua')))
self.assertFalse(os.path.exists(os.path.join(test_modules_path, 'bzip2', '.1.0.6')))
Expand All @@ -155,6 +155,66 @@ def test_exists(self):
self.assertEqual(self.modtool.exist(mod_names), [True, False, True, False, True, True, True])
self.assertEqual(self.modtool.exist(mod_names, skip_avail=True), [True, False, True, False, True, True, True])

# verify whether checking for existence of a module wrapper works
self.modtool.unuse(test_modules_path)
self.modtool.use(self.test_prefix)

java_mod_dir = os.path.join(self.test_prefix, 'Java')
write_file(os.path.join(java_mod_dir, '1.8.0_181'), '#%Module')

if self.modules_tool.__class__ == EnvironmentModulesC:
modulerc_tcl_txt = '\n'.join([
'#%Module',
'if {"Java/1.8" eq [module-info version Java/1.8]} {',
' module-version Java/1.8.0_181 1.8',
'}',
])
else:
modulerc_tcl_txt = 'module-version Java/1.8.0_181 1.8'

write_file(os.path.join(java_mod_dir, '.modulerc'), modulerc_tcl_txt)

avail_mods = self.modtool.available()
self.assertTrue('Java/1.8.0_181' in avail_mods)
if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.0'):
self.assertTrue('Java/1.8' in avail_mods)
self.assertEqual(self.modtool.exist(['Java/1.8', 'Java/1.8.0_181']), [True, True])
self.assertEqual(self.modtool.module_wrapper_exists('Java/1.8'), 'Java/1.8.0_181')

reset_module_caches()

# what if we're in an HMNS setting...
mkdir(os.path.join(self.test_prefix, 'Core'))
shutil.move(java_mod_dir, os.path.join(self.test_prefix, 'Core', 'Java'))

self.assertTrue('Core/Java/1.8.0_181' in self.modtool.available())
self.assertEqual(self.modtool.exist(['Core/Java/1.8.0_181']), [True])
self.assertEqual(self.modtool.exist(['Core/Java/1.8']), [True])
self.assertEqual(self.modtool.module_wrapper_exists('Core/Java/1.8'), 'Core/Java/1.8.0_181')

# also check with .modulerc.lua for Lmod 7.8 or newer
if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.8'):
shutil.move(os.path.join(self.test_prefix, 'Core', 'Java'), java_mod_dir)
reset_module_caches()

remove_file(os.path.join(java_mod_dir, '.modulerc'))
write_file(os.path.join(java_mod_dir, '.modulerc.lua'), 'module_version("Java/1.8.0_181", "1.8")')

avail_mods = self.modtool.available()
self.assertTrue('Java/1.8.0_181' in avail_mods)
self.assertTrue('Java/1.8' in avail_mods)
self.assertEqual(self.modtool.exist(['Java/1.8', 'Java/1.8.0_181']), [True, True])
self.assertEqual(self.modtool.module_wrapper_exists('Java/1.8'), 'Java/1.8.0_181')

reset_module_caches()

# back to HMNS setup
shutil.move(java_mod_dir, os.path.join(self.test_prefix, 'Core', 'Java'))
self.assertTrue('Core/Java/1.8.0_181' in self.modtool.available())
self.assertEqual(self.modtool.exist(['Core/Java/1.8.0_181']), [True])
self.assertEqual(self.modtool.exist(['Core/Java/1.8']), [True])
self.assertEqual(self.modtool.module_wrapper_exists('Core/Java/1.8'), 'Core/Java/1.8.0_181')

def test_load(self):
""" test if we load one module it is in the loaded_modules """
self.init_testmods()
Expand Down