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