diff --git a/lib/spack/spack/util/error.py b/lib/spack/spack/util/error.py index cd979839f2d..0d0d3103ca7 100644 --- a/lib/spack/spack/util/error.py +++ b/lib/spack/spack/util/error.py @@ -3,6 +3,86 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import inspect +import sys + +import llnl.util.tty as tty + +#: at what level we should write stack traces or short error messages +#: this is module-scoped because it needs to be set very early +debug = 0 + class UtilityError(Exception): - """Base error for all errors from the utility package""" + """This is the superclass for all Spack errors. + Subclasses can be found in the modules they have to do with. + """ + + def __init__(self, message, long_message=None): + super().__init__() + self.message = message + self._long_message = long_message + + # for exceptions raised from child build processes, we save the + # traceback as a string and print it in the parent. + self.traceback = None + + # we allow exceptions to print debug info via print_context() + # before they are caught at the top level. If they *haven't* + # printed context early, we do it by default when die() is + # called, so we need to remember whether it's been called. + self.printed = False + + @property + def long_message(self): + return self._long_message + + def print_context(self): + """Print extended debug information about this exception. + + This is usually printed when the top-level Spack error handler + calls ``die()``, but it can be called separately beforehand if a + lower-level error handler needs to print error context and + continue without raising the exception to the top level. + """ + if self.printed: + return + + # basic debug message + tty.error(self.message) + if self.long_message: + sys.stderr.write(self.long_message) + sys.stderr.write("\n") + + # stack trace, etc. in debug mode. + if debug: + if self.traceback: + # exception came from a build child, already got + # traceback in child, so print it. + sys.stderr.write(self.traceback) + else: + # run parent exception hook. + sys.excepthook(*sys.exc_info()) + + sys.stderr.flush() + self.printed = True + + def die(self): + self.print_context() + sys.exit(1) + + def __str__(self): + msg = self.message + if self._long_message: + msg += "\n %s" % self._long_message + return msg + + def __repr__(self): + args = [repr(self.message), repr(self.long_message)] + args = ",".join(args) + qualified_name = inspect.getmodule(self).__name__ + "." + type(self).__name__ + return qualified_name + "(" + args + ")" + + def __reduce__(self): + return type(self), (self.message, self.long_message) + diff --git a/lib/spack/spack/util/spack_json.py b/lib/spack/spack/util/spack_json.py index 579d0469851..89e07b44a6a 100644 --- a/lib/spack/spack/util/spack_json.py +++ b/lib/spack/spack/util/spack_json.py @@ -7,7 +7,7 @@ import json from typing import Any, Dict, Optional -import spack.error +from .error import UtilityError __all__ = ["load", "dump", "SpackJSONError"] @@ -29,7 +29,7 @@ def dump(data: Dict, stream: Optional[Any] = None) -> Optional[str]: return None -class SpackJSONError(spack.error.SpackError): +class SpackJSONError(UtilityError): """Raised when there are issues with JSON parsing.""" def __init__(self, msg: str, json_error: BaseException): diff --git a/lib/spack/spack/util/spack_yaml.py b/lib/spack/spack/util/spack_yaml.py index 2ca9acbed73..e581f50db10 100644 --- a/lib/spack/spack/util/spack_yaml.py +++ b/lib/spack/spack/util/spack_yaml.py @@ -27,7 +27,7 @@ from llnl.util.tty.color import cextra, clen, colorize -import spack.error +from .error import UtilityError # Only export load and dump __all__ = ["load", "dump", "SpackYAMLError"] @@ -493,7 +493,7 @@ def name_mark(name): return error.StringMark(name, None, None, None, None, None) -class SpackYAMLError(spack.error.SpackError): +class SpackYAMLError(UtilityError): """Raised when there are issues with YAML parsing.""" def __init__(self, msg, yaml_error):