Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,7 @@ def load_fake_module(self, purge=False):
fake_mod_path = self.make_module_step(fake=True)

# load fake module
self.modules_tool.prepend_module_path(os.path.join(fake_mod_path, self.mod_subdir))
self.modules_tool.prepend_module_path(os.path.join(fake_mod_path, self.mod_subdir), priority=10000)
self.load_module(purge=purge)

return (fake_mod_path, env)
Expand Down Expand Up @@ -2291,17 +2291,17 @@ def xs2str(xs):
else:
self.log.debug("Sanity check passed!")

def _set_module_as_default(self):
def _set_module_as_default(self, fake=False):
"""
Defining default module Version
Sets the default module version except if we are in dry run

sets the default module version except if we are in dry run.
:param fake: set default for 'fake' module in temporary location
"""
version = self.full_mod_name.split('/')[-1]
if self.dry_run:
dry_run_msg("Marked %s v%s as default version" % (self.name, version))
else:
mod_folderpath = os.path.dirname(self.module_generator.get_module_filepath())
mod_folderpath = os.path.dirname(self.module_generator.get_module_filepath(fake=fake))
self.module_generator.set_as_default(mod_folderpath, version)

def cleanup_step(self):
Expand Down Expand Up @@ -2409,8 +2409,10 @@ def make_module_step(self, fake=False):
else:
self.log.info("Skipping devel module...")

if build_option('set_default_module'):
self._set_module_as_default()
# always set default for temporary module file,
# to avoid that it gets overruled by an existing module file that is set as default
if fake or build_option('set_default_module'):
self._set_module_as_default(fake=fake)

return modpath

Expand Down
78 changes: 43 additions & 35 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from vsc.utils.missing import get_subclasses

from easybuild.tools.build_log import EasyBuildError, print_warning
from easybuild.tools.config import ERROR, IGNORE, PURGE, UNLOAD, UNSET, WARN
from easybuild.tools.config import ERROR, IGNORE, PURGE, UNLOAD, UNSET
from easybuild.tools.config import EBROOT_ENV_VAR_ACTIONS, LOADED_MODULES_ACTIONS
from easybuild.tools.config import build_option, get_modules_tool, install_path
from easybuild.tools.environment import ORIG_OS_ENVIRON, restore_env, setvar, unset_env_vars
Expand Down Expand Up @@ -203,11 +203,6 @@ def buildstats(self):
"""Return tuple with data to be included in buildstats"""
return (self.__class__.__name__, self.cmd, self.version)

@property
def modules(self):
"""(NO LONGER SUPPORTED!) Property providing access to 'modules' class variable"""
self.log.nosupport("'modules' class variable is not supported anymore, use load([<list of modules>]) instead", '2.0')

def set_and_check_version(self):
"""Get the module version, and check any requirements"""
if self.COMMAND in MODULE_VERSION_CACHE:
Expand Down Expand Up @@ -330,8 +325,17 @@ def set_mod_paths(self, mod_paths=None):

self.log.debug("$MODULEPATH after set_mod_paths: %s" % os.environ.get('MODULEPATH', ''))

def use(self, path):
"""Add module path via 'module use'."""
def use(self, path, priority=None):
"""
Add path to $MODULEPATH via 'module use'.

:param path: path to add to $MODULEPATH
:param priority: priority for this path in $MODULEPATH (Lmod-specific)
"""
if priority:
self.log.info("Ignoring specified priority '%s' when running 'module use %s' (Lmod-specific)",
priority, path)

# make sure path exists before we add it
mkdir(path, parents=True)
self.run_module(['use', path])
Expand Down Expand Up @@ -367,13 +371,18 @@ def remove_module_path(self, path, set_mod_paths=True):
if set_mod_paths:
self.set_mod_paths()

def prepend_module_path(self, path, set_mod_paths=True):
def prepend_module_path(self, path, set_mod_paths=True, priority=None):
"""
Prepend given module path to list of module paths, or bump it to 1st place.

:param path: path to prepend to $MODULEPATH
:param set_mod_paths: (re)set self.mod_paths
:param priority: priority for this path in $MODULEPATH (Lmod-specific)
"""
if priority:
self.log.info("Ignoring specified priority '%s' when prepending %s to $MODULEPATH (Lmod-specific)",
priority, path)

# generic approach: remove the path first (if it's there), then add it again (to the front)
modulepath = curr_module_paths()
if not modulepath:
Expand Down Expand Up @@ -489,10 +498,6 @@ def mod_exists_via_show(mod_name):

return mods_exist

def exists(self, mod_name):
"""NO LONGER SUPPORTED: use exist method instead"""
self.log.nosupport("exists(<mod_name>) is not supported anymore, use exist([<mod_name>]) instead", '2.0')

def load(self, modules, mod_paths=None, purge=False, init_env=None, allow_reload=True):
"""
Load all requested modules.
Expand Down Expand Up @@ -532,9 +537,6 @@ def unload(self, modules=None):
"""
Unload all requested modules.
"""
if modules is None:
self.log.nosupport("Unloading modules listed in _modules class variable", '2.0')

for mod in modules:
self.run_module('unload', mod)

Expand Down Expand Up @@ -643,14 +645,6 @@ def run_module(self, *args, **kwargs):
else:
args = list(args)

module_path_key = None
if 'mod_paths' in kwargs:
module_path_key = 'mod_paths'
elif 'modulePath' in kwargs:
module_path_key = 'modulePath'
if module_path_key is not None:
self.log.nosupport("Use of '%s' named argument in 'run_module'" % module_path_key, '2.0')

self.log.debug('Current MODULEPATH: %s' % os.environ.get('MODULEPATH', ''))

# restore selected original environment variables before running module command
Expand Down Expand Up @@ -854,7 +848,10 @@ def interpret_raw_path_tcl(self, txt):
res = txt.strip('"')

# first interpret (outer) 'file join' statement (if any)
file_join = lambda res: os.path.join(*[x.strip('"') for x in res.groups()])
def file_join(res):
"""Helper function to compose joined path."""
return os.path.join(*[x.strip('"') for x in res.groups()])

res = re.sub('\[\s+file\s+join\s+(.*)\s+(.*)\s+\]', file_join, res)

# also interpret all $env(...) parts
Expand Down Expand Up @@ -1017,6 +1014,7 @@ def update(self):
"""Update after new modules were added."""
pass


class EnvironmentModulesTcl(EnvironmentModulesC):
"""Interface to (Tcl) environment modules (modulecmd.tcl)."""
# Tcl environment modules have no --terse (yet),
Expand Down Expand Up @@ -1129,7 +1127,7 @@ def __init__(self, *args, **kwargs):

def check_module_function(self, *args, **kwargs):
"""Check whether selected module tool matches 'module' function definition."""
if not 'regex' in kwargs:
if 'regex' not in kwargs:
kwargs['regex'] = r".*(%s|%s)" % (self.COMMAND, self.COMMAND_ENVIRONMENT)
super(Lmod, self).check_module_function(*args, **kwargs)

Expand Down Expand Up @@ -1212,17 +1210,33 @@ def update(self):
except (IOError, OSError), err:
raise EasyBuildError("Failed to update Lmod spider cache %s: %s", cache_fp, err)

def prepend_module_path(self, path, set_mod_paths=True):
def use(self, path, priority=None):
"""
Add path to $MODULEPATH via 'module use'.

:param path: path to add to $MODULEPATH
:param priority: priority for this path in $MODULEPATH (Lmod-specific)
"""
# make sure path exists before we add it
mkdir(path, parents=True)

if priority:
self.run_module(['use', '--priority', str(priority), path])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was Lmod specific?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, this use implementation is custom to the Lmod class.

else:
self.run_module(['use', path])

def prepend_module_path(self, path, set_mod_paths=True, priority=None):
"""
Prepend given module path to list of module paths, or bump it to 1st place.

:param path: path to prepend to $MODULEPATH
:param set_mod_paths: (re)set self.mod_paths
:param priority: priority for this path in $MODULEPATH (Lmod-specific)
"""
# Lmod pushes a path to the front on 'module use', no need for (costly) 'module unuse'
modulepath = curr_module_paths()
if not modulepath or os.path.realpath(modulepath[0]) != os.path.realpath(path):
self.use(path)
self.use(path, priority=priority)
if set_mod_paths:
self.set_mod_paths()

Expand Down Expand Up @@ -1251,15 +1265,11 @@ def get_software_root(name, with_env_var=False):
Return the software root set for a particular software name.
"""
env_var = get_software_root_env_var_name(name)
legacy_key = "SOFTROOT%s" % convert_name(name, upper=True)

root = None
if env_var in os.environ:
root = os.getenv(env_var)

elif legacy_key in os.environ:
_log.nosupport("Legacy env var %s is being relied on!" % legacy_key, "2.0")

if with_env_var:
res = (root, env_var)
else:
Expand Down Expand Up @@ -1317,16 +1327,14 @@ def get_software_version(name):
Return the software version set for a particular software name.
"""
env_var = get_software_version_env_var_name(name)
legacy_key = "SOFTVERSION%s" % convert_name(name, upper=True)

version = None
if env_var in os.environ:
version = os.getenv(env_var)
elif legacy_key in os.environ:
_log.nosupport("Legacy env var %s is being relied on!" % legacy_key, "2.0")

return version


def curr_module_paths():
"""
Return a list of current module paths.
Expand Down
15 changes: 15 additions & 0 deletions test/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,21 @@ def test_fake_module_load(self):
eb = EasyBlock(EasyConfig(self.eb_file))
eb.installdir = config.build_path()
fake_mod_data = eb.load_fake_module()

pi_modfile = os.path.join(fake_mod_data[0], 'pi', '3.14')
if get_module_syntax() == 'Lua':
pi_modfile += '.lua'

self.assertTrue(os.path.exists(pi_modfile))

# check whether temporary module file is marked as default
if get_module_syntax() == 'Lua':
default_symlink = os.path.join(fake_mod_data[0], 'pi', 'default')
self.assertTrue(os.path.samefile(default_symlink, pi_modfile))
else:
dot_version_txt = read_file(os.path.join(fake_mod_data[0], 'pi', '.version'))
self.assertTrue("set ModulesVersion 3.14" in dot_version_txt)

eb.clean_up_fake_module(fake_mod_data)

# cleanup
Expand Down
49 changes: 47 additions & 2 deletions test/framework/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@

import easybuild.tools.modules as mod
from easybuild.framework.easyblock import EasyBlock
from easybuild.framework.easyconfig.easyconfig import EasyConfig
from easybuild.tools import config
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
Expand Down Expand Up @@ -240,6 +238,19 @@ def test_prepend_module_path(self):
self.modtool.prepend_module_path(symlink_path)
self.assertEqual(modulepath, curr_module_paths())

# test prepending with high priority
test_path_bis = tempfile.mkdtemp(prefix=self.test_prefix)
test_path_tris = tempfile.mkdtemp(prefix=self.test_prefix)
self.modtool.prepend_module_path(test_path_bis, priority=10000)
self.assertEqual(test_path_bis, curr_module_paths()[0])

# check whether prepend with priority actually works (only for Lmod)
if isinstance(self.modtool, Lmod):
self.modtool.prepend_module_path(test_path_tris)
modulepath = curr_module_paths()
self.assertEqual(test_path_bis, modulepath[0])
self.assertEqual(test_path_tris, modulepath[1])

def test_ld_library_path(self):
"""Make sure LD_LIBRARY_PATH is what it should be when loaded multiple modules."""
self.init_testmods()
Expand Down Expand Up @@ -757,6 +768,25 @@ def test_module_caches(self):
self.assertEqual(mod.MODULE_AVAIL_CACHE, {})
self.assertEqual(mod.MODULE_SHOW_CACHE, {})

def test_module_use(self):
"""Test 'module use'."""
test_dir1 = os.path.join(self.test_prefix, 'one')
test_dir2 = os.path.join(self.test_prefix, 'two')
test_dir3 = os.path.join(self.test_prefix, 'three')

self.assertFalse(test_dir1 in os.environ.get('MODULEPATH', ''))
self.modtool.use(test_dir1)
self.assertTrue(os.environ.get('MODULEPATH', '').startswith('%s:' % test_dir1))

# also test use with high priority
self.modtool.use(test_dir2, priority=10000)
self.assertTrue(os.environ['MODULEPATH'].startswith('%s:' % test_dir2))

# check whether prepend with priority actually works (only for Lmod)
if isinstance(self.modtool, Lmod):
self.modtool.use(test_dir3)
self.assertTrue(os.environ['MODULEPATH'].startswith('%s:%s:' % (test_dir2, test_dir3)))

def test_module_use_bash(self):
"""Test whether effect of 'module use' is preserved when a new bash session is started."""
# this test is here as check for a nasty bug in how the modules tool is deployed
Expand Down Expand Up @@ -814,6 +844,21 @@ def test_load_in_hierarchy(self):
self.modtool.load(['hwloc/1.6.2'])
self.assertEqual(os.environ['EBROOTHWLOC'], '/tmp/software/Compiler/GCC/4.7.2/hwloc/1.6.2')

# also test whether correct temporary module is loaded even though same module file already exists elsewhere
# with Lmod, this requires prepending the temporary module path to $MODULEPATH with high priority
tmp_moddir = os.path.join(self.test_prefix, 'tmp_modules')
hwloc_mod = os.path.join(tmp_moddir, 'hwloc', '1.6.2')
hwloc_mod_txt = '\n'.join([
'#%Module',
"module load GCC/4.7.2",
"setenv EBROOTHWLOC /path/to/tmp/hwloc-1.6.2",
])
write_file(hwloc_mod, hwloc_mod_txt)
self.modtool.purge()
self.modtool.use(tmp_moddir, priority=10000)
self.modtool.load(['hwloc/1.6.2'])
self.assertTrue(os.environ['EBROOTHWLOC'], "/path/to/tmp/hwloc-1.6.2")

def test_exit_code_check(self):
"""Verify that EasyBuild checks exit code of executed module commands"""
if isinstance(self.modtool, Lmod):
Expand Down