diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 04343f0dc8..746339725c 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -1186,14 +1186,10 @@ def make_module_extend_modpath(self): # add user-specific module path; use statement will be guarded so no need to create the directories user_modpath = build_option('subdir_user_modules') if user_modpath: - # If a mod_path_suffix is being used, we should respect it - mod_path_suffix = build_option('suffix_modules_path') - user_modpath = os.path.join(user_modpath, mod_path_suffix) user_modpath_exts = ActiveMNS().det_user_modpath_extensions(self.cfg) - user_modpath_exts = [os.path.join(user_modpath, e) for e in user_modpath_exts] self.log.debug("Including user module path extensions returned by naming scheme: %s", user_modpath_exts) txt += self.module_generator.use(user_modpath_exts, prefix=self.module_generator.getenv_cmd('HOME'), - guarded=True) + guarded=True, user_modpath=user_modpath) else: self.log.debug("Not including module path extensions, as specified.") return txt diff --git a/easybuild/tools/module_generator.py b/easybuild/tools/module_generator.py index 375fe7e160..ed8c43702c 100644 --- a/easybuild/tools/module_generator.py +++ b/easybuild/tools/module_generator.py @@ -307,12 +307,49 @@ def update_paths(self, key, paths, prepend=True, allow_abs=False, expand_relpath """ raise NotImplementedError - def use(self, paths, prefix=None, guarded=False): + def _det_user_modpath_common(self, user_modpath): + """ + Helper function for det_user_modpath. + """ + # Check for occurences of {RUNTIME_ENV::SOME_ENV_VAR} + # SOME_ENV_VAR will be expanded at module load time. + runtime_env_re = re.compile(r'{RUNTIME_ENV::(\w+)}') + sub_paths = [] + expanded_user_modpath = [] + for sub_path in re.split(os.path.sep, user_modpath): + matched_re = runtime_env_re.match(sub_path) + if matched_re: + if sub_paths: + path = quote_str(os.path.join(*sub_paths)) + expanded_user_modpath.extend([path]) + sub_paths = [] + expanded_user_modpath.extend([self.getenv_cmd(matched_re.group(1))]) + else: + sub_paths.append(sub_path) + if sub_paths: + expanded_user_modpath.extend([quote_str(os.path.join(*sub_paths))]) + + # if a mod_path_suffix is being used, we should respect it + mod_path_suffix = build_option('suffix_modules_path') + if mod_path_suffix: + expanded_user_modpath.extend([quote_str(mod_path_suffix)]) + + return expanded_user_modpath + + def det_user_modpath(self, user_modpath): + """ + Determine user-specific modules subdirectory, to be used in 'use' statements + (cfr. implementation of use() method). + """ + raise NotImplementedError + + def use(self, paths, prefix=None, guarded=False, user_modpath=None): """ Generate module use statements for given list of module paths. :param paths: list of module path extensions to generate use statements for; paths will be quoted :param prefix: optional path prefix; not quoted, i.e., can be a statement :param guarded: use statements will be guarded to only apply if path exists + :param user_modpath: user-specific modules subdirectory to include in use statements """ raise NotImplementedError @@ -493,7 +530,7 @@ def getenv_cmd(self, envvar): """ Return module-syntax specific code to get value of specific environment variable. """ - return '$env(%s)' % envvar + return '$::env(%s)' % envvar def load_module(self, mod_name, recursive_unload=False, depends_on=False, unload_modules=None): """ @@ -643,16 +680,30 @@ def unload_module(self, mod_name): """ return '\n'.join(['', "module unload %s" % mod_name]) - def use(self, paths, prefix=None, guarded=False): + def det_user_modpath(self, user_modpath): + """ + Determine user-specific modules subdirectory, to be used in 'use' statements + (cfr. implementation of use() method). + """ + if user_modpath: + user_modpath = ' '.join(self._det_user_modpath_common(user_modpath)) + + return user_modpath + + def use(self, paths, prefix=None, guarded=False, user_modpath=None): """ Generate module use statements for given list of module paths. :param paths: list of module path extensions to generate use statements for; paths will be quoted :param prefix: optional path prefix; not quoted, i.e., can be a statement :param guarded: use statements will be guarded to only apply if path exists + :param user_modpath: user-specific modules subdirectory to include in use statements """ + user_modpath = self.det_user_modpath(user_modpath) use_statements = [] for path in paths: quoted_path = quote_str(path) + if user_modpath: + quoted_path = '[ file join %s %s ]' % (user_modpath, quoted_path) if prefix: full_path = '[ file join %s %s ]' % (prefix, quoted_path) else: @@ -954,20 +1005,35 @@ def unload_module(self, mod_name): """ return '\n'.join(['', 'unload("%s")' % mod_name]) - def use(self, paths, prefix=None, guarded=False): + def det_user_modpath(self, user_modpath): + """ + Determine user-specific modules subdirectory, to be used in 'use' statements + (cfr. implementations of use() method). + """ + if user_modpath: + user_modpath = ', '.join(self._det_user_modpath_common(user_modpath)) + + return user_modpath + + def use(self, paths, prefix=None, guarded=False, user_modpath=None): """ Generate module use statements for given list of module paths. :param paths: list of module path extensions to generate use statements for; paths will be quoted :param prefix: optional path prefix; not quoted, i.e., can be a statement :param guarded: use statements will be guarded to only apply if path exists + :param user_modpath: user-specific modules subdirectory to include in use statements """ + user_modpath = self.det_user_modpath(user_modpath) use_statements = [] for path in paths: quoted_path = quote_str(path) + if user_modpath: + quoted_path = 'pathJoin(%s, %s)' % (user_modpath, quoted_path) if prefix: full_path = 'pathJoin(%s, %s)' % (prefix, quoted_path) else: full_path = quoted_path + prepend_modulepath = self.UPDATE_PATH_TEMPLATE % ('prepend', 'MODULEPATH', full_path) if guarded: cond_statement = self.conditional_statement('isDir(%s)' % full_path, prepend_modulepath) diff --git a/test/framework/easyblock.py b/test/framework/easyblock.py index bda482b05c..bcf69e1bab 100644 --- a/test/framework/easyblock.py +++ b/test/framework/easyblock.py @@ -271,21 +271,23 @@ def test_make_module_extend_modpath(self): txt = eb.make_module_extend_modpath() if get_module_syntax() == 'Tcl': regexs = [r'^module use ".*/modules/funky/Compiler/pi/3.14/%s"$' % c for c in modclasses] - home = r'\$env\(HOME\)' + home = r'\$::env\(HOME\)' + fj_usermodsdir = 'file join "%s" "funky" "Compiler/pi/3.14"' % usermodsdir regexs.extend([ # extension for user modules is guarded - r'if { \[ file isdirectory \[ file join %s "%s/funky/Compiler/pi/3.14" \] \] } {$' % (home, usermodsdir), + r'if { \[ file isdirectory \[ file join %s \[ %s \] \] \] } {$' % (home, fj_usermodsdir), # no per-moduleclass extension for user modules - r'^\s+module use \[ file join %s "%s/funky/Compiler/pi/3.14"\ ]$' % (home, usermodsdir), + r'^\s+module use \[ file join %s \[ %s \] \]$' % (home, fj_usermodsdir), ]) elif get_module_syntax() == 'Lua': regexs = [r'^prepend_path\("MODULEPATH", ".*/modules/funky/Compiler/pi/3.14/%s"\)$' % c for c in modclasses] home = r'os.getenv\("HOME"\)' + pj_usermodsdir = 'pathJoin\("%s", "funky", "Compiler/pi/3.14"\)' % usermodsdir regexs.extend([ # extension for user modules is guarded - r'if isDir\(pathJoin\(%s, "%s/funky/Compiler/pi/3.14"\)\) then' % (home, usermodsdir), + r'if isDir\(pathJoin\(%s, %s\)\) then' % (home, pj_usermodsdir), # no per-moduleclass extension for user modules - r'\s+prepend_path\("MODULEPATH", pathJoin\(%s, "%s/funky/Compiler/pi/3.14"\)\)' % (home, usermodsdir), + r'\s+prepend_path\("MODULEPATH", pathJoin\(%s, %s\)\)' % (home, pj_usermodsdir), ]) else: self.assertTrue(False, "Unknown module syntax: %s" % get_module_syntax()) diff --git a/test/framework/module_generator.py b/test/framework/module_generator.py index 1220946d62..88ff06d98b 100644 --- a/test/framework/module_generator.py +++ b/test/framework/module_generator.py @@ -471,6 +471,25 @@ def test_prepend_paths(self): "which only expects relative paths." % self.modgen.app.installdir, self.modgen.prepend_paths, "key2", ["bar", "%s/foo" % self.modgen.app.installdir]) + def test_det_user_modpath(self): + """Test for generic det_user_modpath method.""" + # None by default + self.assertEqual(self.modgen.det_user_modpath(None), None) + + if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: + self.assertEqual(self.modgen.det_user_modpath('my/own/modules'), '"my/own/modules" "all"') + else: + self.assertEqual(self.modgen.det_user_modpath('my/own/modules'), '"my/own/modules", "all"') + + # result is affected by --suffix-modules-path + # {RUNTIME_ENV::FOO} gets translated into Tcl/Lua syntax for resolving $FOO at runtime + init_config(build_options={'suffix_modules_path': ''}) + user_modpath = 'my/{RUNTIME_ENV::TEST123}/modules' + if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: + self.assertEqual(self.modgen.det_user_modpath(user_modpath), '"my" $::env(TEST123) "modules"') + else: + self.assertEqual(self.modgen.det_user_modpath(user_modpath), '"my", os.getenv("TEST123"), "modules"') + def test_use(self): """Test generating module use statements.""" if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: @@ -518,8 +537,8 @@ def test_env(self): def test_getenv_cmd(self): """Test getting value of environment variable.""" if self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: - self.assertEqual('$env(HOSTNAME)', self.modgen.getenv_cmd('HOSTNAME')) - self.assertEqual('$env(HOME)', self.modgen.getenv_cmd('HOME')) + self.assertEqual('$::env(HOSTNAME)', self.modgen.getenv_cmd('HOSTNAME')) + self.assertEqual('$::env(HOME)', self.modgen.getenv_cmd('HOME')) else: self.assertEqual('os.getenv("HOSTNAME")', self.modgen.getenv_cmd('HOSTNAME')) self.assertEqual('os.getenv("HOME")', self.modgen.getenv_cmd('HOME'))