Skip to content

add support for --sanity-check-only + also run sanity check for extensions when using --module-only #3655

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

Merged
merged 11 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
38 changes: 27 additions & 11 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1502,13 +1502,14 @@ def make_module_req_guess(self):
'CMAKE_LIBRARY_PATH': ['lib64'], # lib and lib32 are searched through the above
}

def load_module(self, mod_paths=None, purge=True, extra_modules=None):
def load_module(self, mod_paths=None, purge=True, extra_modules=None, verbose=True):
"""
Load module for this software package/version, after purging all currently loaded modules.

:param mod_paths: list of (additional) module paths to take into account
:param purge: boolean indicating whether or not to purge currently loaded modules first
:param extra_modules: list of extra modules to load (these are loaded *before* loading the 'self' module)
:param verbose: print modules being loaded when trace mode is enabled
"""
# self.full_mod_name might not be set (e.g. during unit tests)
if self.full_mod_name is not None:
Expand All @@ -1530,6 +1531,9 @@ def load_module(self, mod_paths=None, purge=True, extra_modules=None):
if self.mod_subdir and not self.toolchain.is_system_toolchain():
mods.insert(0, self.toolchain.det_short_module_name())

if verbose:
trace_msg("loading modules: %s..." % ', '.join(mods))

# pass initial environment, to use it for resetting the environment before loading the modules
self.modules_tool.load(mods, mod_paths=all_mod_paths, purge=purge, init_env=self.initial_environ)

Expand All @@ -1541,7 +1545,7 @@ def load_module(self, mod_paths=None, purge=True, extra_modules=None):
else:
self.log.warning("Not loading module, since self.full_mod_name is not set.")

def load_fake_module(self, purge=False, extra_modules=None):
def load_fake_module(self, purge=False, extra_modules=None, verbose=False):
"""
Create and load fake module.

Expand All @@ -1556,7 +1560,7 @@ def load_fake_module(self, purge=False, extra_modules=None):

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

return (fake_mod_path, env)

Expand Down Expand Up @@ -2817,12 +2821,16 @@ def xs2str(xs):

fake_mod_data = None

# skip loading of fake module when using --sanity-check-only, load real module instead
if build_option('sanity_check_only') and not extension:
self.load_module(extra_modules=extra_modules)

# only load fake module for non-extensions, and not during dry run
if not (extension or self.dry_run):
elif not (extension or self.dry_run):
try:
# unload all loaded modules before loading fake module
# this ensures that loading of dependencies is tested, and avoids conflicts with build dependencies
fake_mod_data = self.load_fake_module(purge=True, extra_modules=extra_modules)
fake_mod_data = self.load_fake_module(purge=True, extra_modules=extra_modules, verbose=True)
except EasyBuildError as err:
self.sanity_check_fail_msgs.append("loading fake module failed: %s" % err)
self.log.warning("Sanity check: %s" % self.sanity_check_fail_msgs[-1])
Expand Down Expand Up @@ -3095,16 +3103,19 @@ def update_config_template_run_step(self):

def skip_step(self, step, skippable):
"""Dedice whether or not to skip the specified step."""
module_only = build_option('module_only')
force = build_option('force') or build_option('rebuild')

skip = False
force = build_option('force') or build_option('rebuild')
module_only = build_option('module_only')
sanity_check_only = build_option('sanity_check_only')
skipsteps = self.cfg['skipsteps']

# under --skip, sanity check is not skipped
cli_skip = self.skip and step != SANITYCHECK_STEP

# skip step if specified as individual (skippable) step, or if --skip is used
if skippable and (cli_skip or step in self.cfg['skipsteps']):
self.log.info("Skipping %s step (skip: %s, skipsteps: %s)", step, self.skip, self.cfg['skipsteps'])
if skippable and (cli_skip or step in skipsteps):
self.log.info("Skipping %s step (skip: %s, skipsteps: %s)", step, self.skip, skipsteps)
skip = True

# skip step when only generating module file
Expand All @@ -3119,9 +3130,14 @@ def skip_step(self, step, skippable):
self.log.info("Skipping %s step because of forced module-only mode", step)
skip = True

elif sanity_check_only and step != SANITYCHECK_STEP:
self.log.info("Skipping %s step because of sanity-check-only mode", step)
skip = True

else:
self.log.debug("Not skipping %s step (skippable: %s, skip: %s, skipsteps: %s, module_only: %s, force: %s",
step, skippable, self.skip, self.cfg['skipsteps'], module_only, force)
msg = "Not skipping %s step (skippable: %s, skip: %s, skipsteps: %s, module_only: %s, force: %s, "
msg += "sanity_check_only: %s"
self.log.debug(msg, step, skippable, self.skip, skipsteps, module_only, force, sanity_check_only)

return skip

Expand Down
5 changes: 4 additions & 1 deletion easybuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,11 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None):
forced = options.force or options.rebuild
dry_run_mode = options.dry_run or options.dry_run_short or options.missing_modules

keep_available_modules = forced or dry_run_mode or options.extended_dry_run or pr_options
keep_available_modules = keep_available_modules or options.inject_checksums or options.sanity_check_only

# skip modules that are already installed unless forced, or unless an option is used that warrants not skipping
if not (forced or dry_run_mode or options.extended_dry_run or pr_options or options.inject_checksums):
if not keep_available_modules:
retained_ecs = skip_available(easyconfigs, modtool)
if not testing:
for skipped_ec in [ec for ec in easyconfigs if ec not in retained_ecs]:
Expand Down
1 change: 1 addition & 0 deletions easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
'rebuild',
'robot',
'rpath',
'sanity_check_only',
'search_paths',
'sequential',
'set_gid_bit',
Expand Down
2 changes: 2 additions & 0 deletions easybuild/tools/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ def override_options(self):
None, 'store_true', False),
'rpath': ("Enable use of RPATH for linking with libraries", None, 'store_true', False),
'rpath-filter': ("List of regex patterns to use for filtering out RPATH paths", 'strlist', 'store', None),
'sanity-check-only': ("Only run sanity check (module is expected to be installed already",
None, 'store_true', False),
'set-default-module': ("Set the generated module as default", None, 'store_true', False),
'set-gid-bit': ("Set group ID bit on newly created directories", None, 'store_true', False),
'silence-deprecation-warnings': ("Silence specified deprecation warnings", 'strlist', 'extend', None),
Expand Down
59 changes: 59 additions & 0 deletions test/framework/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5736,6 +5736,65 @@ def test_tmp_logdir(self):
logtxt = read_file(os.path.join(tmp_logdir, tmp_logs[0]))
self.assertTrue("COMPLETED: Installation ended successfully" in logtxt)

def test_sanity_check_only(self):
"""Test use of --sanity-check-only."""
topdir = os.path.abspath(os.path.dirname(__file__))
toy_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')

test_ec = os.path.join(self.test_prefix, 'test.ec')
test_ec_txt = read_file(toy_ec)
test_ec_txt += "\nsanity_check_commands = ['toy']"
write_file(test_ec, test_ec_txt)

# sanity check fails if software was not installed yet
outtxt, error_thrown = self.eb_main([test_ec, '--sanity-check-only'], do_build=True, return_error=True)
self.assertTrue("Sanity check failed: no file found at \\'bin/yot\\' or \\'bin/toy\\'" in str(error_thrown))

# actually install, then try --sanity-check-only again;
# need to use --force to install toy because module already exists (but installation doesn't)
self.eb_main([test_ec, '--force'], do_build=True, raise_error=True)

self.mock_stdout(True)
self.mock_stderr(True)
self.eb_main([test_ec, '--sanity-check-only', '--trace'], do_build=True, raise_error=True, testing=False)
stdout = self.get_stdout().strip()
stderr = self.get_stderr().strip()
self.mock_stdout(False)
self.mock_stderr(False)

self.assertFalse(stderr)
skipped = [
"fetching files",
"creating build dir, resetting environment",
"unpacking",
"patching",
"preparing",
"configuring",
"building",
"testing",
"installing",
"taking care of extensions",
"restore after iterating",
"postprocessing",
"cleaning up",
"creating module",
"permissions",
"packaging"
]
for skip in skipped:
self.assertTrue("== %s [skipped]" % skip)

self.assertTrue("== sanity checking..." in stdout)
self.assertTrue("COMPLETED: Installation ended successfully" in stdout)
msgs = [
"file 'bin/yot' or 'bin/toy' found: OK",
"(non-empty) directory 'bin' found: OK",
"loading modules: toy/0.0...",
"result for command 'toy': OK",
]
for msg in msgs:
self.assertTrue(" >> %s" % msg in stdout)

def test_fake_vsc_include(self):
"""Test whether fake 'vsc' namespace is triggered for modules included via --include-*."""

Expand Down
1 change: 1 addition & 0 deletions test/framework/toy_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,7 @@ def test_toy_build_trace(self):
r"== sanity checking\.\.\.",
r" >> file 'bin/yot' or 'bin/toy' found: OK",
r" >> \(non-empty\) directory 'bin' found: OK",
r" >> loading modules: toy/0.0\.\.\.",
r" >> running command 'toy' \.\.\.",
r" >> result for command 'toy': OK",
]) + r'$',
Expand Down