diff --git a/easybuild/main.py b/easybuild/main.py index bbeff64f33..24f994da14 100644 --- a/easybuild/main.py +++ b/easybuild/main.py @@ -69,7 +69,8 @@ from easybuild.tools.github import add_pr_labels, install_github_token, list_prs, merge_pr, new_branch_github, new_pr from easybuild.tools.github import new_pr_from_branch from easybuild.tools.github import sync_branch_with_develop, sync_pr_with_develop, update_branch, update_pr -from easybuild.tools.hooks import BUILD_AND_INSTALL_LOOP, PRE_PREF, POST_PREF, START, END, load_hooks, run_hook +from easybuild.tools.hooks import BUILD_AND_INSTALL_LOOP, PRE_PREF, POST_PREF, CRASH, START, END, CANCEL, FAIL +from easybuild.tools.hooks import load_hooks, run_hook from easybuild.tools.modules import modules_tool from easybuild.tools.options import opts_dict_to_eb_opts, set_up_configuration, use_color from easybuild.tools.output import COLOR_GREEN, COLOR_RED, STATUS_BAR, colorize, print_checks, rich_live_cm @@ -579,30 +580,20 @@ 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): +def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, prepared_cfg_data=None): """ 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) """ + 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) + else: + init_session_state, eb_go, cfg_settings = prepared_cfg_data - register_lock_cleanup_signal_handlers() - - # if $CDPATH is set, unset it, it'll only cause trouble... - # see https://github.com/easybuilders/easybuild-framework/issues/2944 - if 'CDPATH' in os.environ: - del os.environ['CDPATH'] - - # When EB is run via `exec` the special bash variable $_ is not set - # So emulate this here to allow (module) scripts depending on that to work - if '_' not in os.environ: - os.environ['_'] = sys.executable - - # purposely session state very early, to avoid modules loaded by EasyBuild meddling in - init_session_state = session_state() - eb_go, cfg_settings = set_up_configuration(args=args, logfile=logfile, testing=testing) options, orig_paths = eb_go.options, eb_go.args if 'python2' not in build_option('silence_deprecation_warnings'): @@ -731,10 +722,44 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None): cleanup(logfile, eb_tmpdir, testing, silent=False) +def prepare_main(args=None, logfile=None, testing=None): + """ + Prepare for calling main function by setting up the EasyBuild configuration + :param args: command line arguments to take into account when parsing the EasyBuild configuration settings + :param logfile: log file to use + :param testing: enable testing mode + :return: 3-tuple with initial session state data, EasyBuildOptions instance, and tuple with configuration settings + """ + register_lock_cleanup_signal_handlers() + + # if $CDPATH is set, unset it, it'll only cause trouble... + # see https://github.com/easybuilders/easybuild-framework/issues/2944 + if 'CDPATH' in os.environ: + del os.environ['CDPATH'] + + # When EB is run via `exec` the special bash variable $_ is not set + # So emulate this here to allow (module) scripts depending on that to work + if '_' not in os.environ: + os.environ['_'] = sys.executable + + # purposely session state very early, to avoid modules loaded by EasyBuild meddling in + init_session_state = session_state() + eb_go, cfg_settings = set_up_configuration(args=args, logfile=logfile, testing=testing) + + return init_session_state, eb_go, cfg_settings + + if __name__ == "__main__": + init_session_state, eb_go, cfg_settings = prepare_main() + hooks = load_hooks(eb_go.options.hooks) try: - main() + main(prepared_cfg_data=(init_session_state, eb_go, cfg_settings)) except EasyBuildError as err: + run_hook(FAIL, hooks, args=[err]) print_error(err.msg) except KeyboardInterrupt as err: + run_hook(CANCEL, hooks, args=[err]) print_error("Cancelled by user: %s" % err) + except Exception as err: + run_hook(CRASH, hooks, args=[err]) + print_error("Encountered an unrecoverable error: %s" % err) diff --git a/easybuild/tools/hooks.py b/easybuild/tools/hooks.py index dc7fdea8df..8ff81be50d 100644 --- a/easybuild/tools/hooks.py +++ b/easybuild/tools/hooks.py @@ -66,6 +66,10 @@ MODULE_WRITE = 'module_write' END = 'end' +CANCEL = 'cancel' +CRASH = 'crash' +FAIL = 'fail' + PRE_PREF = 'pre_' POST_PREF = 'post_' HOOK_SUFF = '_hook' @@ -101,6 +105,9 @@ for p in [PRE_PREF, POST_PREF]] + [ POST_PREF + BUILD_AND_INSTALL_LOOP, END, + CANCEL, + CRASH, + FAIL, ] KNOWN_HOOKS = [h + HOOK_SUFF for h in HOOK_NAMES] diff --git a/test/framework/options.py b/test/framework/options.py index 65bdbb290b..5b1ef19825 100644 --- a/test/framework/options.py +++ b/test/framework/options.py @@ -719,6 +719,9 @@ def test_avail_hooks(self): " post_testcases_hook", " post_build_and_install_loop_hook", " end_hook", + " cancel_hook", + " crash_hook", + " fail_hook", '', ]) self.assertEqual(stdout, expected)