1# 2# Copyright (C) 2018 Codethink Limited 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU Lesser General Public 6# License as published by the Free Software Foundation; either 7# version 2 of the License, or (at your option) any later version. 8# 9# This library is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12# Lesser General Public License for more details. 13# 14# You should have received a copy of the GNU Lesser General Public 15# License along with this library. If not, see <http://www.gnu.org/licenses/>. 16# 17# Authors: 18# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> 19# Tiago Gomes <tiago.gomes@codethink.co.uk> 20 21from enum import Enum 22 23# Disable pylint warnings for whole file here: 24# pylint: disable=global-statement 25 26# The last raised exception, this is used in test cases only 27_last_exception = None 28_last_task_error_domain = None 29_last_task_error_reason = None 30 31 32# get_last_exception() 33# 34# Fetches the last exception from the main process 35# 36# Used by regression tests 37# 38def get_last_exception(): 39 global _last_exception 40 41 le = _last_exception 42 _last_exception = None 43 return le 44 45 46# get_last_task_error() 47# 48# Fetches the last exception from a task 49# 50# Used by regression tests 51# 52def get_last_task_error(): 53 global _last_task_error_domain 54 global _last_task_error_reason 55 56 d = _last_task_error_domain 57 r = _last_task_error_reason 58 _last_task_error_domain = _last_task_error_reason = None 59 return (d, r) 60 61 62# set_last_task_error() 63# 64# Sets the last exception of a task 65# 66# This is set by some internals to inform regression 67# tests about how things failed in a machine readable way 68# 69def set_last_task_error(domain, reason): 70 global _last_task_error_domain 71 global _last_task_error_reason 72 73 _last_task_error_domain = domain 74 _last_task_error_reason = reason 75 76 77class ErrorDomain(Enum): 78 PLUGIN = 1 79 LOAD = 2 80 IMPL = 3 81 PLATFORM = 4 82 SANDBOX = 5 83 ARTIFACT = 6 84 PIPELINE = 7 85 OSTREE = 8 86 UTIL = 9 87 PROG_NOT_FOUND = 12 88 SOURCE = 10 89 ELEMENT = 11 90 APP = 12 91 STREAM = 13 92 93 94# BstError is an internal base exception class for BuildSream 95# exceptions. 96# 97# The sole purpose of using the base class is to add additional 98# context to exceptions raised by plugins in child tasks, this 99# context can then be communicated back to the main process. 100# 101class BstError(Exception): 102 103 def __init__(self, message, *, detail=None, domain=None, reason=None, temporary=False): 104 global _last_exception 105 106 super().__init__(message) 107 108 # Additional error detail, these are used to construct detail 109 # portions of the logging messages when encountered. 110 # 111 self.detail = detail 112 113 # The build sandbox in which the error occurred, if the 114 # error occurred at element assembly time. 115 # 116 self.sandbox = None 117 118 # When this exception occurred during the handling of a job, indicate 119 # whether or not there is any point retrying the job. 120 # 121 self.temporary = temporary 122 123 # Error domain and reason 124 # 125 self.domain = domain 126 self.reason = reason 127 128 # Hold on to the last raised exception for testing purposes 129 _last_exception = self 130 131 132# PluginError 133# 134# Raised on plugin related errors. 135# 136# This exception is raised either by the plugin loading process, 137# or by the base :class:`.Plugin` element itself. 138# 139class PluginError(BstError): 140 def __init__(self, message, reason=None, temporary=False): 141 super().__init__(message, domain=ErrorDomain.PLUGIN, reason=reason, temporary=False) 142 143 144# LoadErrorReason 145# 146# Describes the reason why a :class:`.LoadError` was raised. 147# 148class LoadErrorReason(Enum): 149 150 # A file was not found. 151 MISSING_FILE = 1 152 153 # The parsed data was not valid YAML. 154 INVALID_YAML = 2 155 156 # Data was malformed, a value was not of the expected type, etc 157 INVALID_DATA = 3 158 159 # An error occurred during YAML dictionary composition. 160 # 161 # This can happen by overriding a value with a new differently typed 162 # value, or by overwriting some named value when that was not allowed. 163 ILLEGAL_COMPOSITE = 4 164 165 # An circular dependency chain was detected 166 CIRCULAR_DEPENDENCY = 5 167 168 # A variable could not be resolved. This can happen if your project 169 # has cyclic dependencies in variable declarations, or, when substituting 170 # a string which refers to an undefined variable. 171 UNRESOLVED_VARIABLE = 6 172 173 # BuildStream does not support the required project format version 174 UNSUPPORTED_PROJECT = 7 175 176 # Project requires a newer version of a plugin than the one which was loaded 177 UNSUPPORTED_PLUGIN = 8 178 179 # A conditional expression failed to resolve 180 EXPRESSION_FAILED = 9 181 182 # An assertion was intentionally encoded into project YAML 183 USER_ASSERTION = 10 184 185 # A list composition directive did not apply to any underlying list 186 TRAILING_LIST_DIRECTIVE = 11 187 188 # Conflicting junctions in subprojects 189 CONFLICTING_JUNCTION = 12 190 191 # Failure to load a project from a specified junction 192 INVALID_JUNCTION = 13 193 194 # Subproject needs to be fetched 195 SUBPROJECT_FETCH_NEEDED = 14 196 197 # Subproject has no ref 198 SUBPROJECT_INCONSISTENT = 15 199 200 # An invalid symbol name was encountered 201 INVALID_SYMBOL_NAME = 16 202 203 # A project.conf file was missing 204 MISSING_PROJECT_CONF = 17 205 206 # Try to load a directory not a yaml file 207 LOADING_DIRECTORY = 18 208 209 # A project path leads outside of the project directory 210 PROJ_PATH_INVALID = 19 211 212 # A project path points to a file of the not right kind (e.g. a 213 # socket) 214 PROJ_PATH_INVALID_KIND = 20 215 216 # A recursive include has been encountered. 217 RECURSIVE_INCLUDE = 21 218 219 # A recursive variable has been encountered 220 RECURSIVE_VARIABLE = 22 221 222 # An attempt so set the value of a protected variable 223 PROTECTED_VARIABLE_REDEFINED = 23 224 225 226# LoadError 227# 228# Raised while loading some YAML. 229# 230# Args: 231# reason (LoadErrorReason): machine readable error reason 232# message (str): human readable error explanation 233# 234# This exception is raised when loading or parsing YAML, or when 235# interpreting project YAML 236# 237class LoadError(BstError): 238 def __init__(self, reason, message, *, detail=None): 239 super().__init__(message, detail=detail, domain=ErrorDomain.LOAD, reason=reason) 240 241 242# ImplError 243# 244# Raised when a :class:`.Source` or :class:`.Element` plugin fails to 245# implement a mandatory method 246# 247class ImplError(BstError): 248 def __init__(self, message, reason=None): 249 super().__init__(message, domain=ErrorDomain.IMPL, reason=reason) 250 251 252# PlatformError 253# 254# Raised if the current platform is not supported. 255class PlatformError(BstError): 256 def __init__(self, message, reason=None): 257 super().__init__(message, domain=ErrorDomain.PLATFORM, reason=reason) 258 259 260# SandboxError 261# 262# Raised when errors are encountered by the sandbox implementation 263# 264class SandboxError(BstError): 265 def __init__(self, message, reason=None): 266 super().__init__(message, domain=ErrorDomain.SANDBOX, reason=reason) 267 268 269# ArtifactError 270# 271# Raised when errors are encountered in the artifact caches 272# 273class ArtifactError(BstError): 274 def __init__(self, message, *, detail=None, reason=None, temporary=False): 275 super().__init__(message, detail=detail, domain=ErrorDomain.ARTIFACT, reason=reason, temporary=True) 276 277 278# PipelineError 279# 280# Raised from pipeline operations 281# 282class PipelineError(BstError): 283 284 def __init__(self, message, *, detail=None, reason=None): 285 super().__init__(message, detail=detail, domain=ErrorDomain.PIPELINE, reason=reason) 286 287 288# StreamError 289# 290# Raised when a stream operation fails 291# 292class StreamError(BstError): 293 294 def __init__(self, message=None, *, detail=None, reason=None, terminated=False): 295 296 # The empty string should never appear to a user, 297 # this only allows us to treat this internal error as 298 # a BstError from the frontend. 299 if message is None: 300 message = "" 301 302 super().__init__(message, detail=detail, domain=ErrorDomain.STREAM, reason=reason) 303 304 self.terminated = terminated 305 306 307# AppError 308# 309# Raised from the frontend App directly 310# 311class AppError(BstError): 312 def __init__(self, message, detail=None, reason=None): 313 super().__init__(message, detail=detail, domain=ErrorDomain.APP, reason=reason) 314 315 316# SkipJob 317# 318# Raised from a child process within a job when the job should be 319# considered skipped by the parent process. 320# 321class SkipJob(Exception): 322 pass 323