1#
2# Copyright BitBake Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import inspect
8import traceback
9import bb.namedtuple_with_abc
10from collections import namedtuple
11
12
13class TracebackEntry(namedtuple.abc):
14    """Pickleable representation of a traceback entry"""
15    _fields = 'filename lineno function args code_context index'
16    _header = '  File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'
17
18    def format(self, formatter=None):
19        if not self.code_context:
20            return self._header.format(self) + '\n'
21
22        formatted = [self._header.format(self) + ':\n']
23
24        for lineindex, line in enumerate(self.code_context):
25            if formatter:
26                line = formatter(line)
27
28            if lineindex == self.index:
29                formatted.append('    >%s' % line)
30            else:
31                formatted.append('     %s' % line)
32        return formatted
33
34    def __str__(self):
35        return ''.join(self.format())
36
37def _get_frame_args(frame):
38    """Get the formatted arguments and class (if available) for a frame"""
39    arginfo = inspect.getargvalues(frame)
40
41    try:
42        if not arginfo.args:
43            return '', None
44    # There have been reports from the field of python 2.6 which doesn't
45    # return a namedtuple here but simply a tuple so fallback gracefully if
46    # args isn't present.
47    except AttributeError:
48        return '', None
49
50    firstarg = arginfo.args[0]
51    if firstarg == 'self':
52        self = arginfo.locals['self']
53        cls = self.__class__.__name__
54
55        arginfo.args.pop(0)
56        del arginfo.locals['self']
57    else:
58        cls = None
59
60    formatted = inspect.formatargvalues(*arginfo)
61    return formatted, cls
62
63def extract_traceback(tb, context=1):
64    frames = inspect.getinnerframes(tb, context)
65    for frame, filename, lineno, function, code_context, index in frames:
66        formatted_args, cls = _get_frame_args(frame)
67        if cls:
68            function = '%s.%s' % (cls, function)
69        yield TracebackEntry(filename, lineno, function, formatted_args,
70                             code_context, index)
71
72def format_extracted(extracted, formatter=None, limit=None):
73    if limit:
74        extracted = extracted[-limit:]
75
76    formatted = []
77    for tracebackinfo in extracted:
78        formatted.extend(tracebackinfo.format(formatter))
79    return formatted
80
81
82def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
83    formatted = ['Traceback (most recent call last):\n']
84
85    if hasattr(tb, 'tb_next'):
86        tb = extract_traceback(tb, context)
87
88    formatted.extend(format_extracted(tb, formatter, limit))
89    formatted.extend(traceback.format_exception_only(etype, value))
90    return formatted
91
92def to_string(exc):
93    if isinstance(exc, SystemExit):
94        if not isinstance(exc.code, str):
95            return 'Exited with "%d"' % exc.code
96    return str(exc)
97