From a5750901c6845c304aa219647b2314b205043097 Mon Sep 17 00:00:00 2001 From: Felix Moessbauer Date: Thu, 4 May 2023 04:50:17 +0200 Subject: [PATCH] use custom exceptions to improve error handling This patch adds the KasUserError exception class to distinguish between internal kas exceptions and user or configuration errors. Exceptions previously raised on user errors are ported over by deriving KasUserError. In case of user errors, only the exception message is shown, but no stacktrace. This makes it easier for users to locate the issue as the reason is now stated in the last line of the output. Kas internal exceptions are not subject to this change to help the developers to find the root cause more easily. Signed-off-by: Felix Moessbauer Signed-off-by: Jan Kiszka --- docs/devguide.rst | 6 ++++++ kas/includehandler.py | 5 +++-- kas/kas.py | 8 +++++++- kas/kasusererror.py | 45 +++++++++++++++++++++++++++++++++++++++++++ kas/repos.py | 19 ++++++++++++++++-- 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 kas/kasusererror.py diff --git a/docs/devguide.rst b/docs/devguide.rst index d254cbe..ef27a0f 100644 --- a/docs/devguide.rst +++ b/docs/devguide.rst @@ -101,6 +101,12 @@ Class reference documentation .. automodule:: kas.includehandler :members: +``kas.kasusererror`` Module +............................. + +.. automodule:: kas.kasusererror + :members: + ``kas.plugins`` Module ...................... diff --git a/kas/includehandler.py b/kas/includehandler.py index 8d1d274..a0920b6 100644 --- a/kas/includehandler.py +++ b/kas/includehandler.py @@ -34,6 +34,7 @@ import logging from jsonschema.validators import Draft4Validator +from .kasusererror import KasUserError from . import __file_version__, __compatible_file_version__ from . import CONFIGSCHEMA @@ -41,7 +42,7 @@ __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017-2021' -class LoadConfigException(Exception): +class LoadConfigException(KasUserError): """ Class for exceptions that appear while loading a configuration file. """ @@ -101,7 +102,7 @@ def load_config(filename): return config -class IncludeException(Exception): +class IncludeException(KasUserError): """ Class for exceptions that appear in the include mechanism. """ diff --git a/kas/kas.py b/kas/kas.py index 4a07e5e..4d7edbf 100644 --- a/kas/kas.py +++ b/kas/kas.py @@ -21,7 +21,9 @@ # SOFTWARE. """ This module is the main entry point for kas, setup tool for bitbake based - projects + projects. In case of user errors (e.g. invalid configuration, repo fetch + failure) KAS exits with error code 2, while exiting with 1 for internal + errors. For details on error handling, see :mod:`kas.kasusererror`. """ import argparse @@ -32,6 +34,7 @@ import logging import signal import sys import os +from .kasusererror import KasUserError try: import colorlog @@ -173,6 +176,9 @@ def main(): try: kas(sys.argv[1:]) + except KasUserError as err: + logging.error('%s', err) + sys.exit(2) except Exception as err: logging.error('%s', err) traceback.print_exc() diff --git a/kas/kasusererror.py b/kas/kasusererror.py new file mode 100644 index 0000000..c9518d4 --- /dev/null +++ b/kas/kasusererror.py @@ -0,0 +1,45 @@ +# kas - setup tool for bitbake based projects +# +# Copyright (c) Siemens AG, 2017-2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" + This module provides a common base class for all exceptions + which are related to user or configuration errors. These exceptions + should be caught and reported to the user using a meaningful message + instead of a stacktrace. + + When handling errors in KAS, never return directly using `sys.exit`, + but instead throw an exception derived from :class:`KasUserError` (for user + errors), or one derived from `Exception` for internal errors. These + are then handled centrally, mapped to correct return codes and pretty + printed. +""" + + +__license__ = 'MIT' +__copyright__ = 'Copyright (c) Siemens AG, 2023' + + +class KasUserError(Exception): + """ + User or input error. Derive all user error exceptions from this class. + """ + pass diff --git a/kas/repos.py b/kas/repos.py index 410e290..7b4af5d 100644 --- a/kas/repos.py +++ b/kas/repos.py @@ -31,11 +31,26 @@ from urllib.parse import urlparse from tempfile import TemporaryDirectory from .context import get_context from .libkas import run_cmd_async, run_cmd +from .kasusererror import KasUserError __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017-2018' +class UnsupportedRepoTypeError(KasUserError, NotImplementedError): + """ + Exception for unsupported / not implemented repository types + """ + pass + + +class PatchFileNotFound(KasUserError, FileNotFoundError): + """ + The requested patch file was not found + """ + pass + + class Repo: """ Represents a repository in the kas configuration. @@ -151,7 +166,7 @@ class Repo: if typ == 'hg': return MercurialRepo(name, url, path, refspec, layers, patches, disable_operations) - raise NotImplementedError('Repo type "%s" not supported.' % typ) + raise UnsupportedRepoTypeError('Repo type "%s" not supported.' % typ) @staticmethod def get_root_path(path, fallback=True): @@ -324,7 +339,7 @@ class RepoImpl(Repo): if os.path.isfile(p): my_patches.append((p, patch['id'])) else: - raise FileNotFoundError(p) + raise PatchFileNotFound(p) else: logging.error('Could not find patch. ' '(patch path: %s, repo: %s, patch entry: %s)',