1""" 2 Exceptions raised and handled in Conan server. 3 These exceptions are mapped between server (as an HTTP response) and client 4 through the REST API. When an error happens in server its translated to an HTTP 5 error code that its sent to client. Client reads the server code and raise the 6 matching exception. 7 8 see return_plugin.py 9 10""" 11from contextlib import contextmanager 12from subprocess import CalledProcessError 13 14from conans.util.env_reader import get_env 15from conans.util.files import decode_text 16 17 18class CalledProcessErrorWithStderr(CalledProcessError): 19 def __str__(self): 20 ret = super(CalledProcessErrorWithStderr, self).__str__() 21 if self.output: 22 ret += "\n" + decode_text(self.output) 23 return ret 24 25 26@contextmanager 27def conanfile_exception_formatter(conanfile_name, func_name): 28 """ 29 Decorator to throw an exception formatted with the line of the conanfile where the error ocurrs. 30 :param reference: Reference of the conanfile 31 :return: 32 """ 33 try: 34 yield 35 # TODO: Move ConanInvalidSystemRequirements, ConanInvalidConfiguration from here? 36 except ConanInvalidSystemRequirements as exc: 37 msg = "{}: Invalid system requirements: {}".format(conanfile_name, exc) 38 raise ConanInvalidSystemRequirements(msg) 39 except ConanInvalidConfiguration as exc: 40 msg = "{}: Invalid configuration: {}".format(conanfile_name, exc) 41 raise ConanInvalidConfiguration(msg) 42 except Exception as exc: 43 msg = _format_conanfile_exception(conanfile_name, func_name, exc) 44 raise ConanExceptionInUserConanfileMethod(msg) 45 46 47def _format_conanfile_exception(scope, method, exception): 48 """ 49 It will iterate the traceback lines, when it finds that the source code is inside the users 50 conanfile it "start recording" the messages, when the trace exits the conanfile we return 51 the traces. 52 """ 53 import sys 54 import traceback 55 if get_env("CONAN_VERBOSE_TRACEBACK", False): 56 return traceback.format_exc() 57 try: 58 conanfile_reached = False 59 tb = sys.exc_info()[2] 60 index = 0 61 content_lines = [] 62 63 while True: # If out of index will raise and will be captured later 64 # 40 levels of nested functions max, get the latest 65 filepath, line, name, contents = traceback.extract_tb(tb, 40)[index] 66 if "conanfile.py" not in filepath: # Avoid show trace from internal conan source code 67 if conanfile_reached: # The error goes to internal code, exit print 68 break 69 else: 70 if not conanfile_reached: # First line 71 msg = "%s: Error in %s() method" % (scope, method) 72 msg += ", line %d\n\t%s" % (line, contents) 73 else: 74 msg = ("while calling '%s', line %d\n\t%s" % (name, line, contents) 75 if line else "\n\t%s" % contents) 76 content_lines.append(msg) 77 conanfile_reached = True 78 index += 1 79 except Exception: 80 pass 81 ret = "\n".join(content_lines) 82 ret += "\n\t%s: %s" % (exception.__class__.__name__, str(exception)) 83 return ret 84 85 86class ConanException(Exception): 87 """ 88 Generic conans exception 89 """ 90 def __init__(self, *args, **kwargs): 91 self.info = None 92 self.remote = kwargs.pop("remote", None) 93 super(ConanException, self).__init__(*args, **kwargs) 94 95 def remote_message(self): 96 if self.remote: 97 return " [Remote: {}]".format(self.remote.name) 98 return "" 99 100 def __str__(self): 101 from conans.util.files import exception_message_safe 102 msg = super(ConanException, self).__str__() 103 if self.remote: 104 return "{}.{}".format(exception_message_safe(msg), self.remote_message()) 105 106 return exception_message_safe(msg) 107 108 109class ConanV2Exception(ConanException): 110 def __str__(self): 111 msg = super(ConanV2Exception, self).__str__() 112 # TODO: Add a link to a public webpage with Conan roadmap to v2 113 return "Conan v2 incompatible: {}".format(msg) 114 115 116class OnlyV2Available(ConanException): 117 118 def __init__(self, remote_url): 119 msg = "The remote at '%s' only works with revisions enabled. " \ 120 "Set CONAN_REVISIONS_ENABLED=1 " \ 121 "or set 'general.revisions_enabled = 1' at the 'conan.conf'" % remote_url 122 super(OnlyV2Available, self).__init__(msg) 123 124 125class NoRestV2Available(ConanException): 126 pass 127 128 129class NoRemoteAvailable(ConanException): 130 """ No default remote configured or the specified remote do not exists 131 """ 132 pass 133 134 135class InvalidNameException(ConanException): 136 pass 137 138 139class ConanConnectionError(ConanException): 140 pass 141 142 143class ConanOutdatedClient(ConanException): 144 pass 145 146 147class ConanExceptionInUserConanfileMethod(ConanException): 148 pass 149 150 151class ConanInvalidSystemRequirements(ConanException): 152 pass 153 154 155class ConanInvalidConfiguration(ConanExceptionInUserConanfileMethod): 156 pass 157 158 159class ConanMigrationError(ConanException): 160 pass 161 162 163# Remote exceptions # 164class InternalErrorException(ConanException): 165 """ 166 Generic 500 error 167 """ 168 pass 169 170 171class RequestErrorException(ConanException): 172 """ 173 Generic 400 error 174 """ 175 pass 176 177 178class AuthenticationException(ConanException): # 401 179 """ 180 401 error 181 """ 182 pass 183 184 185class ForbiddenException(ConanException): # 403 186 """ 187 403 error 188 """ 189 pass 190 191 192class NotFoundException(ConanException): # 404 193 """ 194 404 error 195 """ 196 197 def __init__(self, *args, **kwargs): 198 self.remote = kwargs.pop("remote", None) 199 super(NotFoundException, self).__init__(*args, **kwargs) 200 201 202class RecipeNotFoundException(NotFoundException): 203 204 def __init__(self, ref, remote=None, print_rev=False): 205 from conans.model.ref import ConanFileReference 206 assert isinstance(ref, ConanFileReference), "RecipeNotFoundException requires a " \ 207 "ConanFileReference" 208 self.ref = ref 209 self.print_rev = print_rev 210 super(RecipeNotFoundException, self).__init__(remote=remote) 211 212 def __str__(self): 213 tmp = self.ref.full_str() if self.print_rev else str(self.ref) 214 return "Recipe not found: '{}'".format(tmp, self.remote_message()) 215 216 217class PackageNotFoundException(NotFoundException): 218 219 def __init__(self, pref, remote=None, print_rev=False): 220 from conans.model.ref import PackageReference 221 assert isinstance(pref, PackageReference), "PackageNotFoundException requires a " \ 222 "PackageReference" 223 self.pref = pref 224 self.print_rev = print_rev 225 226 super(PackageNotFoundException, self).__init__(remote=remote) 227 228 def __str__(self): 229 tmp = self.pref.full_str() if self.print_rev else str(self.pref) 230 return "Binary package not found: '{}'{}".format(tmp, self.remote_message()) 231 232 233class UserInterfaceErrorException(RequestErrorException): 234 """ 235 420 error 236 """ 237 pass 238 239 240EXCEPTION_CODE_MAPPING = {InternalErrorException: 500, 241 RequestErrorException: 400, 242 AuthenticationException: 401, 243 ForbiddenException: 403, 244 NotFoundException: 404, 245 RecipeNotFoundException: 404, 246 PackageNotFoundException: 404, 247 UserInterfaceErrorException: 420} 248