-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for marking installed module file as new default version #2110
Conversation
release EasyBuild v3.1.0
This commit adds support to define a default module version on command line. The command line is: --set-as-default And both Tcl and Lua modules are supported.
LGTM |
Adding unittest to * symlink and readlink (test.framework.filetools) * set_default (test.framework.module_generator) Currently the unittests pass, except for test.framework.module_generator test_set_default because the environment is cleaned up in the test and LMod complains about the cache. The results for the unittests are: python -m test.framework.filetools test_symlink_readlink_file INFO: This is (based on) vsc.install.shared_setup 0.10.22 Filtered FileToolsTest tests using 'test_symlink_readlink_file', retained 1/33 tests: test_symlink_readlink_file . ---------------------------------------------------------------------- Ran 1 test in 0.740s OK python -m test.framework.module_generator test_set_default INFO: This is (based on) vsc.install.shared_setup 0.10.22 Filtered TclModuleGeneratorTest tests using 'test_set_default', retained 1/16 tests: test_set_default Filtered LuaModuleGeneratorTest tests using 'test_set_default', retained 1/16 tests: test_set_default .E ====================================================================== ERROR: test_set_default (__main__.LuaModuleGeneratorTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/hvictor/Programs/easybuild/easybuild-framework/test/framework/module_generator.py", line 186, in test_set_default self.modtool.load([module_name]) File "easybuild/tools/modules.py", line 509, in load self.run_module('load', mod) File "easybuild/tools/modules.py", line 674, in run_module raise EasyBuildError("Changing environment as dictated by module failed: %s (%s)", err, out) EasyBuildError: 'Changing environment as dictated by module failed: name \'false\' is not defined (stdout: \nfalse\n, stderr: Lmod has detected the following error: The following module(s) are unknown: "dummy"\n\nPlease check the spelling or version number. Also try "module spider ..."\nIt is also possible your cache file is out-of-date try:\n $ module --ignore-cache load "dummy"\n\n\n\n)' ---------------------------------------------------------------------- Ran 2 tests in 2.653s FAILED (errors=1)
easybuild/tools/options.py
Outdated
@@ -383,6 +383,9 @@ def override_options(self): | |||
None, 'store_true', False), | |||
'zip-logs': ("Zip logs that are copied to install directory, using specified command", | |||
None, 'store_or_None', 'gzip'), | |||
|
|||
'set-as-default': ("Set the generated module as default", None, 'store_true', False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent is off here
easybuild/tools/module_generator.py
Outdated
mod_filepath = os.path.join(module_folder_path, module_file) | ||
|
||
default_filepath = os.path.join(module_folder_path, 'default') | ||
os.symlink(mod_filepath, default_filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if there's already a symlink?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, use symlink
from filetools
easybuild/tools/module_generator.py
Outdated
@@ -692,6 +704,14 @@ def getenv_cmd(self, envvar): | |||
""" | |||
return 'os.getenv("%s")' % envvar | |||
|
|||
def set_as_default(self, module_folder_path, module_version): | |||
|
|||
module_file = module_version + '.lua' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use self.MODULE_FILE_EXTENSION
rather than hardcoding .lua
easybuild/framework/easyblock.py
Outdated
@@ -2132,6 +2172,8 @@ def make_module_step(self, fake=False): | |||
txt += self.make_module_extra() | |||
txt += self.make_module_footer() | |||
|
|||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moar whitespace!
easybuild/framework/easyblock.py
Outdated
@@ -2163,6 +2205,8 @@ def make_module_step(self, fake=False): | |||
if not fake: | |||
self.make_devel_module() | |||
|
|||
self._set_module_as_default(fake=fake) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd include the if build_option(...)
here instead?
easybuild/framework/easyblock.py
Outdated
self.dry_run_msg("Generating default module file %s\n", mod_filepath) | ||
else: | ||
self.module_generator.set_as_default(mod_folderpath, self.version) | ||
self.log.info("Module default version file %s written", mod_filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should not be here, should log in modulegenerator instead
easybuild/framework/easyblock.py
Outdated
self.module_generator.set_as_default(mod_folderpath, self.version) | ||
self.log.info("Module default version file %s written", mod_filepath) | ||
|
||
# invalidate relevant 'module avail'/'module show' cache entries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?! comment does not make any sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of this can be removed totally
easybuild/framework/easyblock.py
Outdated
@@ -2075,6 +2075,46 @@ def _sanity_check_step(self, custom_paths=None, custom_commands=None, extension= | |||
else: | |||
self.log.debug("Sanity check passed!") | |||
|
|||
def _set_module_as_default(self, fake=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring?
easybuild/framework/easyblock.py
Outdated
sets the default module version except if we are in dry run. | ||
""" | ||
mod_filepath = self.module_generator.get_module_filepath() | ||
mod_folderpath = os.path.dirname(mod_filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not combine these lines together, mod_filepath
is only used in one place?
easybuild/framework/easyblock.py
Outdated
mod_folderpath = os.path.dirname(mod_filepath) | ||
|
||
if not self.dry_run: | ||
self.module_generator.set_as_default(mod_folderpath, self.version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe emit a meaningful message in the case of dry run?
if self.dry_run:
dry_run_msg("Marked %s v%s as default version" % (self.name, self.version))
else:
self.module_generator.set_as_default(mod_folderpath, self.version)
easybuild/framework/easyblock.py
Outdated
@@ -2139,6 +2151,7 @@ def make_module_step(self, fake=False): | |||
for line in txt.split('\n'): | |||
self.dry_run_msg(' ' * 4 + line) | |||
|
|||
self.dry_run_msg("Generating file to set default module version to %s\n", self.version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, drop this, see dry run msg in _set_module_as_default
?
easybuild/tools/filetools.py
Outdated
:param symlink_path: symlink file path | ||
""" | ||
|
||
# note: we can't use try-except-finally, because Python 2.4 doesn't support it as a single block |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimal required version is Python 2.6 now, so you can use try
/except
/finally
but, not relevant here, so please just drop this comment
easybuild/tools/filetools.py
Outdated
# note: we can't use try-except-finally, because Python 2.4 doesn't support it as a single block | ||
try: | ||
target_symlink_path = os.readlink(symlink_path) | ||
except IOError, err: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be OSError
rather than IOError
?
also, use
except OSError as err:
test/framework/module_generator.py
Outdated
@@ -40,13 +40,16 @@ | |||
from easybuild.tools import config | |||
from easybuild.tools.module_generator import ModuleGeneratorLua, ModuleGeneratorTcl | |||
from easybuild.tools.module_naming_scheme.utilities import is_valid_module_name | |||
from easybuild.tools.filetools import mkdir, write_file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort line alphabetically please?
test/framework/module_generator.py
Outdated
from easybuild.framework.easyblock import EasyBlock | ||
from easybuild.framework.easyconfig.easyconfig import EasyConfig, ActiveMNS | ||
from easybuild.tools.build_log import EasyBuildError | ||
from easybuild.tools.modules import Lmod | ||
from easybuild.tools.utilities import quote_str | ||
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, find_full_path, init_config | ||
|
||
from vsc.utils.missing import nub | ||
from easybuild.tools.modules import curr_module_paths, reset_module_caches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be moved up...
test/framework/module_generator.py
Outdated
Test load part in generated module file. | ||
""" | ||
# if not self.MODULE_GENERATOR_CLASS == ModuleGeneratorTcl: | ||
# return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove?
test/framework/module_generator.py
Outdated
mkdir(base_path) | ||
|
||
# creating package module | ||
module_name = 'dummy' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dummy
has a particular meaning, use just a random module name instead like foobar
or whatever
test/framework/module_generator.py
Outdated
if txt: | ||
txt += '\n' | ||
|
||
foo_version = 'foo_version' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not make this 'proper' versions like 1.0
and 2.0
?
Fixed MR and all unittests pass now python -m test.framework.filetools test_symlink_readlink_file INFO: This is (based on) vsc.install.shared_setup 0.10.22 Filtered FileToolsTest tests using 'test_symlink_readlink_file', retained 1/33 tests: test_symlink_readlink_file . ---------------------------------------------------------------------- Ran 1 test in 0.763s OK python -m test.framework.module_generator test_set_default INFO: This is (based on) vsc.install.shared_setup 0.10.22 Filtered TclModuleGeneratorTest tests using 'test_set_default', retained 1/16 tests: test_set_default Filtered LuaModuleGeneratorTest tests using 'test_set_default', retained 1/16 tests: test_set_default .. ---------------------------------------------------------------------- Ran 2 tests in 2.831s OK
* Removed bug on remove_file - that did not remove invalid links * Removed own introduced bug on symlink that called build_options too * early * Removed own introduced bug that set full path links to default LMod * modules
This commit is comprised of: * tests remove_file for valid and invalid symlinks * improved error message for symlink errors
@@ -180,7 +204,8 @@ def remove_file(path): | |||
return | |||
|
|||
try: | |||
if os.path.exists(path): | |||
# note: file may also be a broken symlink... | |||
if os.path.exists(path) or os.path.islink(path): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorusu please add a dedicated test case for this fixed bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added as test_remove_symlinks
easybuild/tools/module_generator.py
Outdated
@@ -452,6 +456,20 @@ def getenv_cmd(self, envvar): | |||
""" | |||
return '$env(%s)' % envvar | |||
|
|||
# noinspection PyPackageRequirements,PyPackageRequirements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorusu what's this about (and why twice the same thing)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed. It was a mistake...
easybuild/tools/module_generator.py
Outdated
@@ -693,6 +711,29 @@ def getenv_cmd(self, envvar): | |||
return 'os.getenv("%s")' % envvar | |||
|
|||
|
|||
def set_as_default(self, module_folder_path, module_version): | |||
""" | |||
Create file called "default" inside the package's module folder in order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not a file, but a symlink?
easybuild/tools/module_generator.py
Outdated
default_filepath = os.path.join(module_folder_path, 'default') | ||
|
||
if os.path.islink(default_filepath): | ||
points_to = readlink(default_filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to link_target
?
easybuild/tools/module_generator.py
Outdated
cwd = os.getcwd() | ||
#os.chdir(module_folder_path) | ||
symlink(module_version + self.MODULE_FILE_EXTENSION, default_filepath) | ||
#os.chdir(cwd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please clean up the uncommented code, and you can drop the cwd =
line too (not used anymore)
test/framework/filetools.py
Outdated
# checking if file is symlink | ||
self.assertTrue(os.path.islink(link)) | ||
# reading link target and comparing to file name | ||
self.assertEqual(fp, ft.readlink(link)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, be careful here, what if fp
is a file in a symlinked location?
maybe use os.path.realpath
or ft.readlink
when defining fp
?
test/framework/module_generator.py
Outdated
@@ -46,7 +48,7 @@ | |||
from easybuild.tools.modules import Lmod | |||
from easybuild.tools.utilities import quote_str | |||
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, find_full_path, init_config | |||
|
|||
from vsc.utils.missing import nub |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this up, next to the other imports from vsc.utils
?
test/framework/module_generator.py
Outdated
# removing all available modules from the environment | ||
mod_paths = [p for p in nub(curr_module_paths()) if os.path.exists(p)] | ||
for mod_path in nub(mod_paths): | ||
self.modtool.unuse(mod_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you really need to do this? if not, then don't?
This is an attempt to remove the issues related to LMod 5.8
@victorusu if you consider this fit for rereview, please update PR title and ping me in a comment |
@boegel, I changed the symlink function in order to allow non-full absolute source paths. Which is a required for older versions of LMod (< 7.1 at least). So now, all the LMod tests are passing.
Thanks! |
@victorusu How can the test still be creating Lua module file, we're using Unless I'm missing something, the test should work unconditionally regardless of which modules tool & syntax is being used? |
OK, I think I see the problem now... The tests in However, running them with Lua syntax only works when Lmod is being used as a modules tool, of course. So, yes @victorusu, you'll need to add a check like: if self.MODULE_GENERATOR_CLASS == ModuleGeneratorLua and not isinstance(self.modtool, Lmod):
pass |
@boegel, If think the issue is related to the fact that the
So, the generator is Isn't it? |
This commit skips the creation of luamodules for the Tcl Environment module system
test/framework/module_generator.py
Outdated
Test load part in generated module file. | ||
""" | ||
if self.MODULE_GENERATOR_CLASS == ModuleGeneratorLua and not isinstance(self.modtool, Lmod): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorusu: you'll need to make this a return
rather than a pass
to actually skip the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, thanks!
@boegel, The unittests are finally passing! 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final review, a couple more minor things
largest remark is about making the option name a bit clearer
Create a symlink at the specified path to the given path. | ||
|
||
:param source_path: source file path | ||
:param symlink_path: symlink file path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use_abspath_source
is not documented in docstring, please fix
easybuild/tools/filetools.py
Outdated
_log.info("Symlinked %s to %s", os.path.abspath(source_path), symlink_path) | ||
else: | ||
os.symlink(source_path, symlink_path) | ||
_log.info("Symlinked %s to %s", source_path, symlink_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can shorten this a bit:
if use_abspath_source:
source_path = os.path.abspath(source_path)
os.symlink(source_path, symlink_path)
_log.info("Symlinked %s to %s", source_path, symlink_path)
easybuild/tools/module_generator.py
Outdated
|
||
# noinspection PyPackageRequirements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove?
easybuild/tools/module_generator.py
Outdated
remove_file(default_filepath) | ||
self.log.info("Removed default version marking from %s.", link_target) | ||
elif os.path.exists(default_filepath): | ||
raise EasyBuildError('Found an unexpected file called default in dir %s' % module_folder_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/called default/named 'default'/g
easybuild/tools/options.py
Outdated
@@ -383,6 +383,8 @@ def override_options(self): | |||
None, 'store_true', False), | |||
'zip-logs': ("Zip logs that are copied to install directory, using specified command", | |||
None, 'store_or_None', 'gzip'), | |||
'set-as-default': ("Set the generated module as default", None, 'store_true', False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename to --set-default-module
? leaves less room for misinterpretation...
test/framework/filetools.py
Outdated
# checking if file is symlink | ||
self.assertTrue(os.path.islink(link)) | ||
# reading link target and comparing to file name | ||
self.assertEqual(os.path.realpath(fp), ft.readlink(link)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I think this may fail, e.g. if self.test_prefix
is a symlink...
os.path.realpath
will give you the fully resolved location, while ft.readlink
will only resolve one symlink 'level', I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right! Setting
self.assertEqual(os.path.realpath(fp), os.path.realpath(ft.readlink(link)))
worked for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, ok, but then the test is worthless, no?
I'm starting to think we should change ft.readlink
to ft.resolve_symlink
, and basically make it call os.path.realpath
in a try-except
?
test/framework/module_generator.py
Outdated
Test load part in generated module file. | ||
""" | ||
if self.MODULE_GENERATOR_CLASS == ModuleGeneratorLua and not isinstance(self.modtool, Lmod): | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
include a short comment to explain why you're bailing out here, this looks suspicious, but we're not cheating here :)
…path, enhance test for symlink & resolve_path
@victorusu victorusu#1 should make this good to go |
rename filetools.readlink to filetools.resolve_path, use os.path.realpath, enhance test for symlink & resolve_path
Going in, thanks @victorusu! |
This commit adds support to define a default module version on command
line.
The command line is:
--set-default-module
And both Tcl and Lua modules are supported.