Skip to content
Open
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
32 changes: 18 additions & 14 deletions easybuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def process_easystack(easystack_path, args, logfile, testing, init_session_state

# Loop over each item in the EasyStack file, each time updating the config
# This is because each item in an EasyStack file can have options associated with it
do_cleanup = True
is_successful = True
for (path, ec_opts) in easystack.ec_opt_tuples:
_log.debug("Starting build for %s" % path)

Expand Down Expand Up @@ -313,10 +313,10 @@ def process_easystack(easystack_path, args, logfile, testing, init_session_state
modtool = modules_tool(testing=testing)

# Process actual item in the EasyStack file
do_cleanup &= process_eb_args([path], eb_go, cfg_settings, modtool, testing, init_session_state,
hooks, do_build)
is_successful &= process_eb_args([path], eb_go, cfg_settings, modtool, testing, init_session_state,
hooks, do_build)

return do_cleanup
return is_successful


def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session_state, hooks, do_build):
Expand All @@ -339,7 +339,7 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session

global _log
# Unpack cfg_settings
(build_specs, _log, logfile, robot_path, search_query, eb_tmpdir, try_to_generate,
(build_specs, _log, _logfile, robot_path, search_query, _eb_tmpdir, try_to_generate,
from_pr_list, tweaked_ecs_paths) = cfg_settings

# determine easybuild-easyconfigs package install path
Expand Down Expand Up @@ -589,7 +589,7 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session
# build software, will exit when errors occurs (except when testing)
start_time = datetime.now()
if not testing or (testing and do_build):
exit_on_failure = not (options.dump_test_report or options.upload_test_report)
exit_on_failure = not any((options.dump_test_report, options.upload_test_report, options.keep_going))

with rich_live_cm():
run_hook(PRE_PREF + BUILD_AND_INSTALL_LOOP, hooks, args=[ordered_ecs])
Expand Down Expand Up @@ -629,14 +629,16 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session
return overall_success


def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, prepared_cfg_data=None):
def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, prepared_cfg_data=None) -> EasyBuildExit:
"""
Main function: parse command line options, and act accordingly.
:param args: command line arguments to use
:param logfile: log file to use
:param do_build: whether or not to actually perform the build
:param testing: enable testing mode
:param prepared_cfg_data: prepared configuration data for main function, as returned by prepare_main (or None)

:return: error code
"""
if prepared_cfg_data is None or any([args, logfile, testing]):
init_session_state, eb_go, cfg_settings = prepare_main(args=args, logfile=logfile, testing=testing)
Expand All @@ -646,8 +648,8 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr
options, orig_paths = eb_go.options, eb_go.args

global _log
(build_specs, _log, logfile, robot_path, search_query, eb_tmpdir, try_to_generate,
from_pr_list, tweaked_ecs_paths) = cfg_settings
(_build_specs, _log, logfile, _robot_path, search_query, eb_tmpdir, _try_to_generate,
_from_pr_list, _tweaked_ecs_paths) = cfg_settings

# compare running Framework and EasyBlocks versions
if EASYBLOCKS_VERSION == UNKNOWN_EASYBLOCKS_VERSION:
Expand Down Expand Up @@ -773,15 +775,16 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr
"The following arguments will be ignored:",
] + orig_paths)
print_warning(msg)
do_cleanup = process_easystack(options.easystack, args, logfile, testing, init_session_state, do_build)
is_successful = process_easystack(options.easystack, args, logfile, testing, init_session_state, do_build)
else:
do_cleanup = process_eb_args(orig_paths, eb_go, cfg_settings, modtool, testing, init_session_state,
hooks, do_build)
is_successful = process_eb_args(orig_paths, eb_go, cfg_settings, modtool, testing, init_session_state,
hooks, do_build)

# stop logging and cleanup tmp log file, unless one build failed (individual logs are located in eb_tmpdir)
stop_logging(logfile, logtostdout=options.logtostdout)
if do_cleanup:
if is_successful:
cleanup(logfile, eb_tmpdir, testing, silent=options.terse)
return EasyBuildExit.SUCCESS if is_successful else EasyBuildExit.ERROR


def prepare_main(args=None, logfile=None, testing=None):
Expand Down Expand Up @@ -823,7 +826,8 @@ def main_with_hooks(args=None):
hooks = load_hooks(eb_go.options.hooks)

try:
main(args=args, prepared_cfg_data=(init_session_state, eb_go, cfg_settings))
exit_code: EasyBuildExit = main(args=args, prepared_cfg_data=(init_session_state, eb_go, cfg_settings))
sys.exit(int(exit_code))
except EasyBuildError as err:
run_hook(FAIL, hooks, args=[err])
print_error(err.msg, exit_on_error=True, exit_code=err.exit_code)
Expand Down
7 changes: 3 additions & 4 deletions easybuild/tools/build_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def init_logging(logfile, logtostdout=False, silent=False, colorize=fancylogger.
if tmp_logdir and not os.path.exists(tmp_logdir):
try:
os.makedirs(tmp_logdir)
except (IOError, OSError) as err:
except OSError as err:
raise EasyBuildError("Failed to create temporary log directory %s: %s", tmp_logdir, err)

# mkstemp returns (fd,filename), fd is from os.open, not regular open!
Expand Down Expand Up @@ -404,9 +404,8 @@ def print_error(msg, *args, **kwargs):
if exitCode is not None:
_init_easybuildlog.deprecated("'exitCode' option in print_error function is replaced with 'exit_code'", '6.0')

# use 1 as defaut exit code
if exit_code is None:
exit_code = 1
exit_code = EasyBuildExit.ERROR

log = kwargs.pop('log', None)
opt_parser = kwargs.pop('opt_parser', None)
Expand All @@ -420,7 +419,7 @@ def print_error(msg, *args, **kwargs):
if opt_parser:
opt_parser.print_shorthelp()
sys.stderr.write("ERROR: %s\n" % msg)
sys.exit(exit_code)
sys.exit(int(exit_code))
elif log is not None:
raise EasyBuildError(msg)

Expand Down
1 change: 1 addition & 0 deletions easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
'ignore_test_failure',
'install_latest_eb_release',
'keep_debug_symbols',
'keep_going',
'logtostdout',
'minimal_toolchains',
'module_only',
Expand Down
2 changes: 2 additions & 0 deletions easybuild/tools/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,8 @@ def github_options(self):
'close-pr-msg': ("Custom close message for pull request closed with --close-pr; ", str, 'store', None),
'close-pr-reasons': ("Close reason for pull request closed with --close-pr; "
"supported values: %s" % ", ".join(VALID_CLOSE_PR_REASONS), str, 'store', None),
'keep-going': ("Continue installation of remaining software after a failed installation. "
"Implied by --dump-test-report and --upload-test-report", None, 'store_true', False),
'list-prs': ("List pull requests", str, 'store_or_None',
",".join([DEFAULT_LIST_PR_STATE, DEFAULT_LIST_PR_ORDER, DEFAULT_LIST_PR_DIREC]),
{'metavar': 'STATE,ORDER,DIRECTION'}),
Expand Down
43 changes: 40 additions & 3 deletions test/framework/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3232,8 +3232,8 @@ def test_http_header_fields_urlpat(self):
mentionhdr = 'Custom HTTP header field set: %s'
mentionfile = 'File included in parse_http_header_fields_urlpat: %s'

def run_and_assert(args, msg, words_expected=None, words_unexpected=None):
stdout, stderr = self._run_mock_eb(args, do_build=True, raise_error=True, testing=False)
def run_and_assert(args, _msg, words_expected=None, words_unexpected=None):
stdout, _stderr = self._run_mock_eb(args, do_build=True, raise_error=True, testing=False)
if words_expected is not None:
self.assert_multi_regex(words_expected, stdout)
if words_unexpected is not None:
Expand Down Expand Up @@ -6435,7 +6435,7 @@ def test_force_download(self):
'--force-download',
'--sourcepath=%s' % self.test_prefix,
]
stdout, stderr = self._run_mock_eb(args, do_build=True, raise_error=True, verbose=True, strip=True)
_stdout, stderr = self._run_mock_eb(args, do_build=True, raise_error=True, verbose=True, strip=True)
regex = re.compile(r"^WARNING: Found file toy-0.0.tar.gz at .*, but re-downloading it anyway\.\.\.$")
self.assertTrue(regex.match(stderr), "Pattern '%s' matches: %s" % (regex.pattern, stderr))

Expand Down Expand Up @@ -6719,6 +6719,43 @@ def test_sanity_check_only(self):
import easybuild.easyblocks.generic.toy_extension
reload(easybuild.easyblocks.generic.toy_extension)

def test_keep_going(self):
"""Test use of --keep-going."""
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.eb')
test_ec_txt = read_file(toy_ec)
test_ec_txt += '\nsources=["toy-0.0.tar.gz"]'
write_file(test_ec, test_ec_txt + '\nversion="broken"\npreconfigopts = "false && "')
test_ec2 = os.path.join(self.test_prefix, 'test2.eb')
write_file(test_ec2, test_ec_txt + '\nversion="working"')

args = [test_ec, test_ec2, '--rebuild']
with self.mocked_stdout_stderr():
outtxt, exit_code, error_thrown = self.eb_main(args, do_build=True, return_error=True,
return_exit_code=True)
self.assertIn("Installation of test.eb failed", str(error_thrown))
self.assertNotEqual(exit_code, 0)
self.assertRegex(outtxt, r'\[FAILED\] *toy/broken')
self.assertRegex(outtxt, r'\[SKIPPED\] *toy/working')

args.append('--keep-going')
with self.mocked_stdout_stderr():
outtxt, exit_code = self.eb_main(args, do_build=True, raise_error=True,
return_exit_code=True)
self.assertNotEqual(exit_code, 0)
self.assertRegex(outtxt, r'\[FAILED\] *toy/broken')
self.assertRegex(outtxt, r'\[SUCCESS\] *toy/working')

args.append(f"--dump-test-report={os.path.join(tempfile.gettempdir(), 'report.md')}")
with self.mocked_stdout_stderr():
outtxt, exit_code = self.eb_main(args, do_build=True, raise_error=True,
return_exit_code=True)
self.assertEqual(exit_code, 1) # Return failure also when creating a test report
self.assertRegex(outtxt, r'\[FAILED\] *toy/broken')
self.assertRegex(outtxt, r'\[SUCCESS\] *toy/working')

def test_skip_extensions(self):
"""Test use of --skip-extensions."""
topdir = os.path.abspath(os.path.dirname(__file__))
Expand Down
17 changes: 12 additions & 5 deletions test/framework/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,9 @@ def reset_modulepath(self, modpaths):
self.modtool.add_module_path(modpath, set_mod_paths=False)
self.modtool.set_mod_paths()

def eb_main(self, args, do_build=False, return_error=False, logfile=None, verbose=False, raise_error=False,
reset_env=True, raise_systemexit=False, testing=True, redo_init_config=True, clear_caches=True):
def eb_main(self, args, do_build=False, return_error=False, return_exit_code=False, logfile=None, verbose=False,
raise_error=False, reset_env=True, raise_systemexit=False, testing=True, redo_init_config=True,
clear_caches=True):
"""Helper method to call EasyBuild main function."""

cleanup(clear_caches=clear_caches)
Expand All @@ -325,21 +326,24 @@ def eb_main(self, args, do_build=False, return_error=False, logfile=None, verbos

env_before = copy.deepcopy(os.environ)

exit_code = eb_build_log.EasyBuildExit.ERROR
try:
if '--fetch' in args:
# The config sets modules_tool to None if --fetch is specified,
# so do the same here to keep the behavior consistent
modtool = None
else:
modtool = self.modtool
main(args=main_args, logfile=logfile, do_build=do_build, testing=testing, modtool=modtool)
exit_code = main(args=main_args, logfile=logfile, do_build=do_build, testing=testing, modtool=modtool)
except SystemExit as err:
if raise_systemexit:
raise err
except Exception as err:
myerr = err
if verbose:
print("err: %s" % err)
if isinstance(err, eb_build_log.EasyBuildError):
exit_code = err.exit_code

if logfile and os.path.exists(logfile):
logtxt = read_file(logfile)
Expand All @@ -362,9 +366,12 @@ def eb_main(self, args, do_build=False, return_error=False, logfile=None, verbos
raise myerr

if return_error:
if return_exit_code:
return logtxt, exit_code, myerr
return logtxt, myerr
else:
return logtxt
if return_exit_code:
return logtxt, exit_code
return logtxt

def setup_hierarchical_modules(self):
"""Setup hierarchical modules to run tests on."""
Expand Down