diff options
Diffstat (limited to 'src/buildstream/_exceptions.py')
-rw-r--r-- | src/buildstream/_exceptions.py | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/buildstream/_exceptions.py b/src/buildstream/_exceptions.py new file mode 100644 index 000000000..f2d34bcba --- /dev/null +++ b/src/buildstream/_exceptions.py @@ -0,0 +1,370 @@ +# +# Copyright (C) 2018 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> +# Tiago Gomes <tiago.gomes@codethink.co.uk> + +from enum import Enum +import os + +# Disable pylint warnings for whole file here: +# pylint: disable=global-statement + +# The last raised exception, this is used in test cases only +_last_exception = None +_last_task_error_domain = None +_last_task_error_reason = None + + +# get_last_exception() +# +# Fetches the last exception from the main process +# +# Used by regression tests +# +def get_last_exception(): + global _last_exception + + le = _last_exception + _last_exception = None + return le + + +# get_last_task_error() +# +# Fetches the last exception from a task +# +# Used by regression tests +# +def get_last_task_error(): + if 'BST_TEST_SUITE' not in os.environ: + raise BstError("Getting the last task error is only supported when running tests") + + global _last_task_error_domain + global _last_task_error_reason + + d = _last_task_error_domain + r = _last_task_error_reason + _last_task_error_domain = _last_task_error_reason = None + return (d, r) + + +# set_last_task_error() +# +# Sets the last exception of a task +# +# This is set by some internals to inform regression +# tests about how things failed in a machine readable way +# +def set_last_task_error(domain, reason): + if 'BST_TEST_SUITE' in os.environ: + global _last_task_error_domain + global _last_task_error_reason + + _last_task_error_domain = domain + _last_task_error_reason = reason + + +class ErrorDomain(Enum): + PLUGIN = 1 + LOAD = 2 + IMPL = 3 + PLATFORM = 4 + SANDBOX = 5 + ARTIFACT = 6 + PIPELINE = 7 + OSTREE = 8 + UTIL = 9 + PROG_NOT_FOUND = 12 + SOURCE = 10 + ELEMENT = 11 + APP = 12 + STREAM = 13 + VIRTUAL_FS = 14 + CAS = 15 + + +# BstError is an internal base exception class for BuildSream +# exceptions. +# +# The sole purpose of using the base class is to add additional +# context to exceptions raised by plugins in child tasks, this +# context can then be communicated back to the main process. +# +class BstError(Exception): + + def __init__(self, message, *, detail=None, domain=None, reason=None, temporary=False): + global _last_exception + + super().__init__(message) + + # Additional error detail, these are used to construct detail + # portions of the logging messages when encountered. + # + self.detail = detail + + # A sandbox can be created to debug this error + self.sandbox = False + + # When this exception occurred during the handling of a job, indicate + # whether or not there is any point retrying the job. + # + self.temporary = temporary + + # Error domain and reason + # + self.domain = domain + self.reason = reason + + # Hold on to the last raised exception for testing purposes + if 'BST_TEST_SUITE' in os.environ: + _last_exception = self + + +# PluginError +# +# Raised on plugin related errors. +# +# This exception is raised either by the plugin loading process, +# or by the base :class:`.Plugin` element itself. +# +class PluginError(BstError): + def __init__(self, message, reason=None, temporary=False): + super().__init__(message, domain=ErrorDomain.PLUGIN, reason=reason, temporary=False) + + +# LoadErrorReason +# +# Describes the reason why a :class:`.LoadError` was raised. +# +class LoadErrorReason(Enum): + + # A file was not found. + MISSING_FILE = 1 + + # The parsed data was not valid YAML. + INVALID_YAML = 2 + + # Data was malformed, a value was not of the expected type, etc + INVALID_DATA = 3 + + # An error occurred during YAML dictionary composition. + # + # This can happen by overriding a value with a new differently typed + # value, or by overwriting some named value when that was not allowed. + ILLEGAL_COMPOSITE = 4 + + # An circular dependency chain was detected + CIRCULAR_DEPENDENCY = 5 + + # A variable could not be resolved. This can happen if your project + # has cyclic dependencies in variable declarations, or, when substituting + # a string which refers to an undefined variable. + UNRESOLVED_VARIABLE = 6 + + # BuildStream does not support the required project format version + UNSUPPORTED_PROJECT = 7 + + # Project requires a newer version of a plugin than the one which was loaded + UNSUPPORTED_PLUGIN = 8 + + # A conditional expression failed to resolve + EXPRESSION_FAILED = 9 + + # An assertion was intentionally encoded into project YAML + USER_ASSERTION = 10 + + # A list composition directive did not apply to any underlying list + TRAILING_LIST_DIRECTIVE = 11 + + # Conflicting junctions in subprojects + CONFLICTING_JUNCTION = 12 + + # Failure to load a project from a specified junction + INVALID_JUNCTION = 13 + + # Subproject needs to be fetched + SUBPROJECT_FETCH_NEEDED = 14 + + # Subproject has no ref + SUBPROJECT_INCONSISTENT = 15 + + # An invalid symbol name was encountered + INVALID_SYMBOL_NAME = 16 + + # A project.conf file was missing + MISSING_PROJECT_CONF = 17 + + # Try to load a directory not a yaml file + LOADING_DIRECTORY = 18 + + # A project path leads outside of the project directory + PROJ_PATH_INVALID = 19 + + # A project path points to a file of the not right kind (e.g. a + # socket) + PROJ_PATH_INVALID_KIND = 20 + + # A recursive include has been encountered. + RECURSIVE_INCLUDE = 21 + + # A recursive variable has been encountered + RECURSIVE_VARIABLE = 22 + + # An attempt so set the value of a protected variable + PROTECTED_VARIABLE_REDEFINED = 23 + + +# LoadError +# +# Raised while loading some YAML. +# +# Args: +# reason (LoadErrorReason): machine readable error reason +# message (str): human readable error explanation +# +# This exception is raised when loading or parsing YAML, or when +# interpreting project YAML +# +class LoadError(BstError): + def __init__(self, reason, message, *, detail=None): + super().__init__(message, detail=detail, domain=ErrorDomain.LOAD, reason=reason) + + +# ImplError +# +# Raised when a :class:`.Source` or :class:`.Element` plugin fails to +# implement a mandatory method +# +class ImplError(BstError): + def __init__(self, message, reason=None): + super().__init__(message, domain=ErrorDomain.IMPL, reason=reason) + + +# PlatformError +# +# Raised if the current platform is not supported. +class PlatformError(BstError): + def __init__(self, message, reason=None): + super().__init__(message, domain=ErrorDomain.PLATFORM, reason=reason) + + +# SandboxError +# +# Raised when errors are encountered by the sandbox implementation +# +class SandboxError(BstError): + def __init__(self, message, detail=None, reason=None): + super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason) + + +# SourceCacheError +# +# Raised when errors are encountered in the source caches +# +class SourceCacheError(BstError): + def __init__(self, message, detail=None, reason=None): + super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason) + + +# ArtifactError +# +# Raised when errors are encountered in the artifact caches +# +class ArtifactError(BstError): + def __init__(self, message, *, detail=None, reason=None, temporary=False): + super().__init__(message, detail=detail, domain=ErrorDomain.ARTIFACT, reason=reason, temporary=True) + + +# CASError +# +# Raised when errors are encountered in the CAS +# +class CASError(BstError): + def __init__(self, message, *, detail=None, reason=None, temporary=False): + super().__init__(message, detail=detail, domain=ErrorDomain.CAS, reason=reason, temporary=True) + + +# CASRemoteError +# +# Raised when errors are encountered in the remote CAS +class CASRemoteError(CASError): + pass + + +# CASCacheError +# +# Raised when errors are encountered in the local CASCacheError +# +class CASCacheError(CASError): + pass + + +# PipelineError +# +# Raised from pipeline operations +# +class PipelineError(BstError): + + def __init__(self, message, *, detail=None, reason=None): + super().__init__(message, detail=detail, domain=ErrorDomain.PIPELINE, reason=reason) + + +# StreamError +# +# Raised when a stream operation fails +# +class StreamError(BstError): + + def __init__(self, message=None, *, detail=None, reason=None, terminated=False): + + # The empty string should never appear to a user, + # this only allows us to treat this internal error as + # a BstError from the frontend. + if message is None: + message = "" + + super().__init__(message, detail=detail, domain=ErrorDomain.STREAM, reason=reason) + + self.terminated = terminated + + +# AppError +# +# Raised from the frontend App directly +# +class AppError(BstError): + def __init__(self, message, detail=None, reason=None): + super().__init__(message, detail=detail, domain=ErrorDomain.APP, reason=reason) + + +# SkipJob +# +# Raised from a child process within a job when the job should be +# considered skipped by the parent process. +# +class SkipJob(Exception): + pass + + +# ArtifactElementError +# +# Raised when errors are encountered by artifact elements +# +class ArtifactElementError(BstError): + def __init__(self, message, *, detail=None, reason=None): + super().__init__(message, detail=detail, domain=ErrorDomain.ELEMENT, reason=reason) |