diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index c537003de5..d6b33012d7 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -899,8 +899,13 @@ def load_module(self, mod_name, recursive_unload=None, depends_on=False, unload_ cond_tmpl = None + # Environment Modules v4+ safely handles automatic module load by not reloading already + # loaded module. No safe guard test is required and it should even be avoided to get the + # module dependency correctly tracked. + safe_auto_load = self.modules_tool.supports_safe_auto_load + if recursive_unload is None: - recursive_unload = build_option('recursive_mod_unload') or depends_on + recursive_unload = build_option('recursive_mod_unload') or depends_on or safe_auto_load if recursive_unload: # wrapping the 'module load' statement with an 'is-loaded or mode == unload' @@ -911,7 +916,7 @@ def load_module(self, mod_name, recursive_unload=None, depends_on=False, unload_ # see also http://lmod.readthedocs.io/en/latest/210_load_storms.html cond_tmpl = "[ module-info mode remove ] || %s" - if depends_on: + if depends_on or safe_auto_load: if multi_dep_mods and len(multi_dep_mods) > 1: parent_mod_name = os.path.dirname(mod_name) guard = self.is_loaded(multi_dep_mods[1:]) diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py index 846916ee62..940acf02fd 100644 --- a/easybuild/tools/modules.py +++ b/easybuild/tools/modules.py @@ -209,6 +209,7 @@ def __init__(self, mod_paths=None, testing=False): self.check_module_function(allow_mismatch=build_option('allow_modules_tool_mismatch')) self.set_and_check_version() self.supports_depends_on = False + self.supports_safe_auto_load = False def __str__(self): """String representation of this ModulesTool instance.""" @@ -1317,6 +1318,7 @@ class EnvironmentModules(EnvironmentModulesTcl): REQ_VERSION = '4.0.0' DEPR_VERSION = '4.0.0' # needs to be set as EnvironmentModules inherits from EnvironmentModulesTcl MAX_VERSION = None + REQ_VERSION_SAFE_AUTO_LOAD = '4.2.4' VERSION_REGEXP = r'^Modules\s+Release\s+(?P\d[^+\s]*)(\+\S*)?\s' SHOW_HIDDEN_OPTION = '--all' @@ -1343,6 +1345,8 @@ def __init__(self, *args, **kwargs): setvar('MODULES_LIST_TERSE_OUTPUT', '', verbose=False) super(EnvironmentModules, self).__init__(*args, **kwargs) + version = LooseVersion(self.version) + self.supports_safe_auto_load = version >= LooseVersion(self.REQ_VERSION_SAFE_AUTO_LOAD) def check_module_function(self, allow_mismatch=False, regex=None): """Check whether selected module tool matches 'module' function definition.""" diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 075cc3adbd..fb89571b38 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -266,24 +266,32 @@ def test_load(self): """Test load part in generated module file.""" if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: - # default: guarded module load (which implies no recursive unloading) - expected = '\n'.join([ - '', - "if { ![ is-loaded mod_name ] } {", - " module load mod_name", - "}", - '', - ]) + if not self.modtool.supports_safe_auto_load: + # default: guarded module load (which implies no recursive unloading) + expected = '\n'.join([ + '', + "if { ![ is-loaded mod_name ] } {", + " module load mod_name", + "}", + '', + ]) + else: + expected = '\n'.join([ + '', + "module load mod_name", + '', + ]) self.assertEqual(expected, self.modgen.load_module("mod_name")) - # with recursive unloading: no if is-loaded guard - expected = '\n'.join([ - '', - "if { [ module-info mode remove ] || ![ is-loaded mod_name ] } {", - " module load mod_name", - "}", - '', - ]) + if not self.modtool.supports_safe_auto_load: + # with recursive unloading: no if is-loaded guard + expected = '\n'.join([ + '', + "if { [ module-info mode remove ] || ![ is-loaded mod_name ] } {", + " module load mod_name", + "}", + '', + ]) self.assertEqual(expected, self.modgen.load_module("mod_name", recursive_unload=True)) init_config(build_options={'recursive_mod_unload': True}) @@ -353,13 +361,24 @@ def test_load_multi_deps(self): res = self.modgen.load_module('Python/3.7.4', multi_dep_mods=multi_dep_mods) if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: - expected = '\n'.join([ - '', - "if { ![ is-loaded Python/3.7.4 ] && ![ is-loaded Python/2.7.16 ] } {", - " module load Python/3.7.4", - '}', - '', - ]) + if not self.modtool.supports_safe_auto_load: + expected = '\n'.join([ + '', + "if { ![ is-loaded Python/3.7.4 ] && ![ is-loaded Python/2.7.16 ] } {", + " module load Python/3.7.4", + '}', + '', + ]) + else: + expected = '\n'.join([ + '', + "if { [ module-info mode remove ] || [ is-loaded Python/2.7.16 ] } {", + " module load Python", + '} else {', + " module load Python/3.7.4", + '}', + '', + ]) else: # Lua syntax expected = '\n'.join([ '', @@ -401,14 +420,26 @@ def test_load_multi_deps(self): res = self.modgen.load_module('foo/1.2.3', multi_dep_mods=multi_dep_mods) if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: - expected = '\n'.join([ - '', - "if { ![ is-loaded foo/1.2.3 ] && ![ is-loaded foo/2.3.4 ] && " + - "![ is-loaded foo/3.4.5 ] && ![ is-loaded foo/4.5.6 ] } {", - " module load foo/1.2.3", - '}', - '', - ]) + if not self.modtool.supports_safe_auto_load: + expected = '\n'.join([ + '', + "if { ![ is-loaded foo/1.2.3 ] && ![ is-loaded foo/2.3.4 ] && " + + "![ is-loaded foo/3.4.5 ] && ![ is-loaded foo/4.5.6 ] } {", + " module load foo/1.2.3", + '}', + '', + ]) + else: + expected = '\n'.join([ + '', + "if { [ module-info mode remove ] || [ is-loaded foo/2.3.4 ] || [ is-loaded foo/3.4.5 ] " + + "|| [ is-loaded foo/4.5.6 ] } {", + " module load foo", + "} else {", + " module load foo/1.2.3", + '}', + '', + ]) else: # Lua syntax expected = '\n'.join([ '', @@ -453,13 +484,16 @@ def test_load_multi_deps(self): res = self.modgen.load_module('one/1.0', multi_dep_mods=['one/1.0']) if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: - expected = '\n'.join([ - '', - "if { ![ is-loaded one/1.0 ] } {", - " module load one/1.0", - '}', - '', - ]) + if not self.modtool.supports_safe_auto_load: + expected = '\n'.join([ + '', + "if { ![ is-loaded one/1.0 ] } {", + " module load one/1.0", + '}', + '', + ]) + else: + expected = '\nmodule load one/1.0\n' else: # Lua syntax expected = '\n'.join([ '',