Skip to content
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

use more granular exit codes when EasyBuildError is raised #4534

Merged
merged 38 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
27cea15
WIP: Add kwargs for error class initialization, enable get error catc…
dagonzalezfo May 9, 2024
cdbb029
pass exit_code equals to for missing os dependencies
dagonzalezfo May 9, 2024
199663f
add error codes from 3 to 7
dagonzalezfo May 10, 2024
d8041df
Add some exit codes for bash commands, sanity check and missing files
dagonzalezfo May 15, 2024
a384a04
add exit codes for eb validation and for writing module
dagonzalezfo May 16, 2024
58de0a5
homogenize syntax
dagonzalezfo May 16, 2024
81b0e19
restore sources.easybuild.io
dagonzalezfo May 16, 2024
341bff5
fix spaces
dagonzalezfo May 16, 2024
43b7f16
fix spaces, tabs etc
dagonzalezfo May 16, 2024
02967a2
Minior fixes regarding syntax
dagonzalezfo May 17, 2024
51f6d6f
Define exit code for successfull installation, homogenise exit code s…
dagonzalezfo May 22, 2024
e9f201a
Add exceptions for test pass
dagonzalezfo May 29, 2024
08ea315
Merge branch '5.0.x' into granular_exit_code
lexming Jun 10, 2024
0d31d34
add EasyBuildExit to easybuild.tools.build_log to define table of gra…
lexming Jun 10, 2024
c81572c
Merge pull request #1 from lexming/granular_exit_code
dagonzalezfo Aug 20, 2024
4366686
Fixing hound related errors
dagonzalezfo Aug 20, 2024
0f1cbbd
Merge branch '5.0.x' into 4426_granular_exit_code
dagonzalezfo Aug 20, 2024
04e7574
fix formatting in easybuild.tools.build_log
lexming Aug 28, 2024
688ca4d
add missing import of print_error
lexming Aug 28, 2024
fa0fbbf
Merge branch '5.0.x' into 4426_granular_exit_code
lexming Sep 2, 2024
6c56d91
correct regression on easyblock import error handling
lexming Sep 2, 2024
409ec08
update error message reference in test_toy_extension_extract_cmd
lexming Sep 2, 2024
85b94a6
Merge branch '5.0.x' into 4426_granular_exit_code
boegel Sep 7, 2024
1a900e7
Apply suggestions from code review
dagonzalezfo Sep 10, 2024
151b959
simplify code to inform about files not found with no download option
lexming Sep 12, 2024
6b31f39
add safeguard in case of step missing its exit code in EasyBuildExit
lexming Sep 17, 2024
4273c90
fix error message reference in test_obtain_file
lexming Sep 17, 2024
bcdba6d
Merge branch '5.0.x' into 4426_granular_exit_code
lexming Sep 17, 2024
60e06ce
ensure that exit_code is always set at the end of build_and_install_one
lexming Sep 17, 2024
a5e805a
lock down options incorporated as attributes of EasyBuildError objects
lexming Sep 17, 2024
1b4e95a
use VALUE_ERROR as exit code for wrong commit hashes in easyconfigs
lexming Sep 17, 2024
b5d5bc8
use VALUE_ERROR as exit code for wrong download_repo
lexming Sep 17, 2024
8f4304f
use FAIL_GITHUB exit code for failure to fetch files of easyconfig PR
lexming Sep 17, 2024
4e700b5
use FAIL_GITHUB exit code for failure to fetch files of easyconfig co…
lexming Sep 17, 2024
79e019b
use OPTION_ERROR as exit code for wrong PR number
lexming Sep 17, 2024
9a57cc9
remove hardcoded mention to develop branch in error message
lexming Sep 17, 2024
c7ae4af
remove hardcoded mention to develop branch in error message
lexming Sep 17, 2024
9eaec3b
replace MISS_EASYCONFIG with new MISSING_EASYCONFIG exit code reference
lexming Sep 17, 2024
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
30 changes: 21 additions & 9 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,6 @@ def collect_exts_file_info(self, fetch_files=True, verify_checksums=True):
"""
exts_sources = []
exts_list = self.cfg.get_ref('exts_list')

if verify_checksums and not fetch_files:
raise EasyBuildError("Can't verify checksums for extension files if they are not being fetched")

Expand Down Expand Up @@ -963,7 +962,7 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
error_msg += "Paths attempted (in order): %s " % failedpaths_msg

if not warning_only:
raise EasyBuildError(error_msg, filename)
raise EasyBuildError(error_msg, filename, exit_code=9)
else:
self.log.warning(error_msg, filename)
return None
Expand Down Expand Up @@ -1894,7 +1893,6 @@ def install_extensions_sequential(self, install=True):
exts_cnt = len(self.ext_instances)

for idx, ext in enumerate(self.ext_instances):

self.log.info("Starting extension %s", ext.name)

run_hook(SINGLE_EXTENSION, self.hooks, pre_step_hook=True, args=[ext])
Expand Down Expand Up @@ -2791,7 +2789,6 @@ def init_ext_instances(self):
exts_cnt = len(self.exts)

self.update_exts_progress_bar("creating internal datastructures for extensions")

for idx, ext in enumerate(self.exts):
ext_name = ext['name']
self.log.debug("Creating class instance for extension %s...", ext_name)
Expand Down Expand Up @@ -3622,6 +3619,7 @@ def xs2str(xs):
if not found:
sanity_check_fail_msg = "no %s found at %s in %s" % (typ, xs2str(xs), self.installdir)
self.sanity_check_fail_msgs.append(sanity_check_fail_msg)
self.exit_code = 10
self.log.warning("Sanity check: %s", sanity_check_fail_msg)

trace_msg("%s %s found: %s" % (typ, xs2str(xs), ('FAILED', 'OK')[found]))
Expand All @@ -3647,6 +3645,7 @@ def xs2str(xs):
if res.exit_code != 0:
fail_msg = f"sanity check command {cmd} exited with code {res.exit_code} (output: {res.output})"
self.sanity_check_fail_msgs.append(fail_msg)
self.exit_code = res.exit_code
self.log.warning(f"Sanity check: {fail_msg}")
else:
self.log.info(f"sanity check command {cmd} ran successfully! (output: {res.output})")
Expand Down Expand Up @@ -3689,7 +3688,12 @@ def xs2str(xs):

# pass or fail
if self.sanity_check_fail_msgs:
raise EasyBuildError("Sanity check failed: " + '\n'.join(self.sanity_check_fail_msgs))
try:
self.exit_code
except AttributeError:
self.exit_code = 1
raise EasyBuildError(
"Sanity check failed: " + '\n'.join(self.sanity_check_fail_msgs), exit_code=self.exit_code)
else:
self.log.debug("Sanity check passed!")

Expand Down Expand Up @@ -3791,8 +3795,11 @@ def make_module_step(self, fake=False):
for line in txt.split('\n'):
self.dry_run_msg(INDENT_4SPACES + line)
else:
write_file(mod_filepath, txt)
self.log.info("Module file %s written: %s", mod_filepath, txt)
try:
write_file(mod_filepath, txt)
self.log.info("Module file %s written: %s", mod_filepath, txt)
except EasyBuildError:
raise EasyBuildError("Unable to write Module file %s", mod_filepath, exit_code=11)

# if backup module file is there, print diff with newly generated module file
if self.mod_file_backup and not fake:
Expand Down Expand Up @@ -4174,7 +4181,7 @@ def run_all_steps(self, run_test_cases):
err.print()
ec_path = os.path.basename(self.cfg.path)
error_msg = f"shell command '{err.cmd_name} ...' failed in {step_name} step for {ec_path}"
raise EasyBuildError(error_msg)
raise EasyBuildError(error_msg, exit_code=err.exit_code)
finally:
if not self.dry_run:
step_duration = datetime.now() - start_time
Expand Down Expand Up @@ -4314,6 +4321,10 @@ def build_and_install_one(ecdict, init_env):

except EasyBuildError as err:
error_msg = err.msg
try:
exit_code
except NameError:
exit_code = 1
result = False

ended = 'ended'
Expand Down Expand Up @@ -4431,6 +4442,7 @@ def ensure_writable_log_dir(log_dir):
success = True
summary = 'COMPLETED'
succ = 'successfully'
exit_code = 0
else:
# build failed
success = False
Expand Down Expand Up @@ -4463,7 +4475,7 @@ def ensure_writable_log_dir(log_dir):

del app

return (success, application_log, error_msg)
return (success, application_log, error_msg, exit_code)


def copy_easyblocks_for_reprod(easyblock_instances, reprod_dir):
Expand Down
18 changes: 12 additions & 6 deletions easybuild/framework/easyconfig/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ def validate_os_deps(self):
not_found.append(dep)

if not_found:
raise EasyBuildError("One or more OS dependencies were not found: %s", not_found)
raise EasyBuildError("One or more OS dependencies were not found: %s", not_found, exit_code=8)
else:
self.log.info("OS dependencies ok: %s" % self['osdependencies'])

Expand Down Expand Up @@ -1264,7 +1264,7 @@ def _validate(self, attr, values): # private method
if values is None:
values = []
if self[attr] and self[attr] not in values:
raise EasyBuildError("%s provided '%s' is not valid: %s", attr, self[attr], values)
raise EasyBuildError("%s provided '%s' is not valid: %s", attr, self[attr], values, exit_code=12)

def probe_external_module_metadata(self, mod_name, existing_metadata=None):
"""
Expand Down Expand Up @@ -1913,9 +1913,10 @@ def get_easyblock_class(easyblock, name=None, error_on_failed_import=True, error
_log.debug("error regexp for ImportError on '%s' easyblock: %s", modname, error_re.pattern)
if error_re.match(str(err)):
if error_on_missing_easyblock:
raise EasyBuildError("No software-specific easyblock '%s' found for %s", class_name, name)
raise EasyBuildError(
"No software-specific easyblock '%s' found for %s", class_name, name, exit_code=4)
elif error_on_failed_import:
raise EasyBuildError("Failed to import %s easyblock: %s", class_name, err)
raise EasyBuildError("Failed to import %s easyblock: %s", class_name, err, exit_code=5)
else:
_log.debug("Failed to import easyblock for %s, but ignoring it: %s" % (class_name, err))

Expand All @@ -1931,7 +1932,8 @@ def get_easyblock_class(easyblock, name=None, error_on_failed_import=True, error
# simply reraise rather than wrapping it into another error
raise err
except Exception as err:
raise EasyBuildError("Failed to obtain class for %s easyblock (not available?): %s", easyblock, err)
raise EasyBuildError(
"Failed to obtain class for %s easyblock (not available?): %s", easyblock, err, exit_code=6)


def get_module_path(name, generic=None, decode=True):
Expand Down Expand Up @@ -2047,7 +2049,11 @@ def process_easyconfig(path, build_specs=None, validate=True, parse_only=False,
try:
ec = EasyConfig(spec, build_specs=build_specs, validate=validate, hidden=hidden)
except EasyBuildError as err:
raise EasyBuildError("Failed to process easyconfig %s: %s", spec, err.msg)
try:
err.exit_code
except AttributeError:
err.exit_code = 1
raise EasyBuildError("Failed to process easyconfig %s: %s", spec, err.msg, exit_code=err.exit_code)

name = ec['name']

Expand Down
8 changes: 4 additions & 4 deletions easybuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ def build_and_install_software(ecs, init_session_state, exit_on_failure=True):

ec_res = {}
try:
(ec_res['success'], app_log, err) = build_and_install_one(ec, init_env)
(ec_res['success'], app_log, err_msg, err_code) = build_and_install_one(ec, init_env)
ec_res['log_file'] = app_log
if not ec_res['success']:
ec_res['err'] = EasyBuildError(err)
ec_res['err'] = EasyBuildError(err_msg, exit_code=err_code)
except Exception as err:
# purposely catch all exceptions
ec_res['success'] = False
Expand Down Expand Up @@ -172,7 +172,7 @@ def build_and_install_software(ecs, init_session_state, exit_on_failure=True):
if not isinstance(ec_res['err'], EasyBuildError):
raise ec_res['err']
else:
raise EasyBuildError(test_msg)
raise EasyBuildError(test_msg, exit_code=err_code)

res.append((ec, ec_res))

Expand Down Expand Up @@ -774,7 +774,7 @@ def main_with_hooks(args=None):
main(args=args, prepared_cfg_data=(init_session_state, eb_go, cfg_settings))
except EasyBuildError as err:
run_hook(FAIL, hooks, args=[err])
print_error(err.msg, exit_on_error=True, exit_code=1)
print_error(err.msg, exit_on_error=True, exit_code=err.exit_code)
except KeyboardInterrupt as err:
run_hook(CANCEL, hooks, args=[err])
print_error("Cancelled by user: %s" % err)
Expand Down
6 changes: 4 additions & 2 deletions easybuild/tools/build_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ class EasyBuildError(LoggedException):
# always include location where error was raised from, even under 'python -O'
INCLUDE_LOCATION = True

def __init__(self, msg, *args):
def __init__(self, msg, *args, **kwargs):
"""Constructor: initialise EasyBuildError instance."""
if args:
msg = msg % args
LoggedException.__init__(self, msg)
LoggedException.__init__(self, msg, **kwargs)
self.msg = msg
if kwargs:
self.__dict__.update(kwargs)
lexming marked this conversation as resolved.
Show resolved Hide resolved

def __str__(self):
"""Return string representation of this EasyBuildError instance."""
Expand Down
6 changes: 4 additions & 2 deletions easybuild/tools/filetools.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,9 @@ def download_file(filename, url, path, forced=False, trace=True):
if error_re.match(str(err)):
switch_to_requests = True
except Exception as err:
raise EasyBuildError("Unexpected error occurred when trying to download %s to %s: %s", url, path, err)
raise EasyBuildError(
"Unexpected error occurred when trying to download %s to %s: %s", url, path,
err, exit_code=status_code)

if not downloaded and attempt_cnt < max_attempts:
_log.info("Attempt %d of downloading %s to %s failed, trying again..." % (attempt_cnt, url, path))
Expand Down Expand Up @@ -1041,7 +1043,7 @@ def locate_files(files, paths, ignore_subdirs=None):
if files_to_find:
filenames = ', '.join([f for (_, f) in files_to_find])
paths = ', '.join(paths)
raise EasyBuildError("One or more files not found: %s (search paths: %s)", filenames, paths)
raise EasyBuildError("One or more files not found: %s (search paths: %s)", filenames, paths, exit_code=3)

return [os.path.abspath(f) for f in files]

Expand Down
2 changes: 1 addition & 1 deletion easybuild/tools/robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def raise_error_missing_deps(missing_deps, extra_msg=None):
error_msg = "Missing dependencies: %s" % mod_names
if extra_msg:
error_msg += ' (%s)' % extra_msg
raise EasyBuildError(error_msg)
raise EasyBuildError(error_msg, exit_code=7)


def resolve_dependencies(easyconfigs, modtool, retain_all_deps=False, raise_error_missing_ecs=True):
Expand Down
Loading