* [PATCH] utils: Improve better_exec traceback handling
@ 2013-05-08 17:14 Richard Purdie
0 siblings, 0 replies; only message in thread
From: Richard Purdie @ 2013-05-08 17:14 UTC (permalink / raw)
To: bitbake-devel
The current bitbake tracebacks are hard to read/confusing and sometimes
incomplete. This patch attempts to do better by:
* Moving the note about the exact exception to the end to make things
read in sequence
* Merged the initial stack trace to become part of the code dump
* Added handling for "/xxxx" file paths since we can load these files
and include the data as part of the trace
* Dropped the ERROR: prefix to every line, allowing the error messages to
be spacially accosicated in the UIs
* Moved the "From:" line to the top of each code block and ensured its present
consistently
With the complexity now in this funciton, I've added try/except wrapping around
it to ensure we catch exceptions in the exception handler too.
Example before:
"""
ERROR: Error executing a python function in /media/build1/poky/meta/recipes-core/eglibc/eglibc-initial_2.17.bb:
TypeError: 'filter' object is not subscriptable
ERROR: The stack trace of python calls that resulted in this exception/failure was:
ERROR: File "do_populate_lic", line 13, in <module>
ERROR:
ERROR: File "do_populate_lic", line 6, in do_populate_lic
ERROR:
ERROR: File "license.bbclass", line 99, in find_license_files
ERROR:
ERROR: File "/media/build1/poky/meta/lib/oe/license.py", line 38, in visit_string
ERROR: if pos > 0 and license_pattern.match(elements[pos-1]):
ERROR:
ERROR: The code that was being executed was:
ERROR: 0009: destdir = os.path.join(d.getVar('LICSSTATEDIR', True), d.getVar('PN', True))
ERROR: 0010: copy_license_files(lic_files_paths, destdir)
ERROR: 0011:
ERROR: 0012:
ERROR: *** 0013:do_populate_lic(d)
ERROR: 0014:
ERROR: [From file: 'do_populate_lic', lineno: 13, function: <module>]
ERROR: 0002:def do_populate_lic(d):
ERROR: 0003: """
ERROR: 0004: Populate LICENSE_DIRECTORY with licenses.
ERROR: 0005: """
ERROR: *** 0006: lic_files_paths = find_license_files(d)
ERROR: 0007:
ERROR: 0008: # The base directory we wrangle licenses to
ERROR: 0009: destdir = os.path.join(d.getVar('LICSSTATEDIR', True), d.getVar('PN', True))
ERROR: 0010: copy_license_files(lic_files_paths, destdir)
ERROR: [From file: 'do_populate_lic', lineno: 6, function: do_populate_lic]
ERROR: 0095: lic_files_paths.append((os.path.basename(path), srclicfile))
ERROR: 0096:
ERROR: 0097: v = FindVisitor()
ERROR: 0098: try:
ERROR: *** 0099: v.visit_string(license_types)
ERROR: 0100: except oe.license.InvalidLicense as exc:
ERROR: 0101: bb.fatal('%s: %s' % (d.getVar('PF', True), exc))
ERROR: 0102: except SyntaxError:
ERROR: 0103: bb.warn("%s: Failed to parse it's LICENSE field." % (d.getVar('PF', True)))
ERROR: [From file: 'license.bbclass', lineno: 99, function: find_license_files]
ERROR: Function failed: do_populate_lic
ERROR: Logfile of failure stored in: /media/build1/poky/build/tmp/work/i586-poky-linux/eglibc-initial/2.17-r3/temp/log.do_populate_lic.17442
"""
Example after:
"""
ERROR: Error executing a python function in /media/build1/poky/meta/recipes-core/eglibc/eglibc-initial_2.17.bb:
The stack trace of python calls that resulted in this exception/failure was:
File: 'do_populate_lic', lineno: 13, function: <module>
0009: destdir = os.path.join(d.getVar('LICSSTATEDIR', True), d.getVar('PN', True))
0010: copy_license_files(lic_files_paths, destdir)
0011:
0012:
*** 0013:do_populate_lic(d)
0014:
File: 'do_populate_lic', lineno: 6, function: do_populate_lic
0002:def do_populate_lic(d):
0003: """
0004: Populate LICENSE_DIRECTORY with licenses.
0005: """
*** 0006: lic_files_paths = find_license_files(d)
0007:
0008: # The base directory we wrangle licenses to
0009: destdir = os.path.join(d.getVar('LICSSTATEDIR', True), d.getVar('PN', True))
0010: copy_license_files(lic_files_paths, destdir)
File: 'license.bbclass', lineno: 99, function: find_license_files
0095: lic_files_paths.append((os.path.basename(path), srclicfile))
0096:
0097: v = FindVisitor()
0098: try:
*** 0099: v.visit_string(license_types)
0100: except oe.license.InvalidLicense as exc:
0101: bb.fatal('%s: %s' % (d.getVar('PF', True), exc))
0102: except SyntaxError:
0103: bb.warn("%s: Failed to parse it's LICENSE field." % (d.getVar('PF', True)))
File: '/media/build1/poky/meta/lib/oe/license.py', lineno: 38, function: visit_string
0034: new_elements = []
0035: elements = filter(lambda x: x.strip(), license_operator.split(licensestr))
0036: for pos, element in enumerate(elements):
0037: if license_pattern.match(element):
*** 0038: if pos > 0 and license_pattern.match(elements[pos-1]):
0039: new_elements.append('&')
0040: element = '"' + element + '"'
0041: elif not license_operator.match(element):
0042: raise InvalidLicense(element)
Exception: TypeError: 'filter' object is not subscriptable
ERROR: Function failed: do_populate_lic
ERROR: Logfile of failure stored in: /media/build1/poky/build/tmp/work/i586-poky-linux/eglibc-initial/2.17-r3/temp/log.do_populate_lic.3275
ERROR: Task 9 (/media/build1/poky/meta/recipes-core/eglibc/eglibc-initial_2.17.bb, do_populate_lic) failed with exit code '1
"""
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
---
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
index d671f56..9d7a32f 100644
--- a/bitbake/lib/bb/utils.py
+++ b/bitbake/lib/bb/utils.py
@@ -236,14 +236,16 @@ def _print_trace(body, line):
"""
Print the Environment of a Text Body
"""
+ error = []
# print the environment of the method
min_line = max(1, line-4)
max_line = min(line + 4, len(body))
- for i in xrange(min_line, max_line + 1):
+ for i in range(min_line, max_line + 1):
if line == i:
- logger.error(' *** %.4d:%s', i, body[i-1])
+ error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
else:
- logger.error(' %.4d:%s', i, body[i-1])
+ error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
+ return error
def better_compile(text, file, realfile, mode = "exec"):
"""
@@ -260,7 +262,7 @@ def better_compile(text, file, realfile, mode = "exec"):
if e.lineno:
logger.error("The lines leading to this error were:")
logger.error("\t%d:%s:'%s'", e.lineno, e.__class__.__name__, body[e.lineno-1])
- _print_trace(body, e.lineno)
+ logger.error("\n".join(_print_trace(body, e.lineno)))
else:
logger.error("The function causing this error was:")
for line in body:
@@ -269,68 +271,84 @@ def better_compile(text, file, realfile, mode = "exec"):
e = bb.BBHandledException(e)
raise e
-def better_exec(code, context, text = None, realfile = "<code>"):
- """
- Similiar to better_compile, better_exec will
- print the lines that are responsible for the
- error.
- """
- import bb.parse
- if not text:
- text = code
- if not hasattr(code, "co_filename"):
- code = better_compile(code, realfile, realfile)
+def _print_exception(t, value, tb, realfile, text, context):
+ error = []
try:
- exec(code, _context, context)
- except Exception as e:
- (t, value, tb) = sys.exc_info()
-
- if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
- raise
-
import traceback
exception = traceback.format_exception_only(t, value)
- logger.error('Error executing a python function in %s:\n%s',
- realfile, ''.join(exception))
+ error.append('Error executing a python function in %s:\n' % realfile)
# Strip 'us' from the stack (better_exec call)
tb = tb.tb_next
textarray = text.split('\n')
- linefailed = traceback.tb_lineno(tb)
- tbextract = traceback.extract_tb(tb)
- tbformat = "\n".join(traceback.format_list(tbextract))
- logger.error("The stack trace of python calls that resulted in this exception/failure was:")
- for line in tbformat.split('\n'):
- logger.error(line)
+ linefailed = tb.tb_lineno
- logger.error("The code that was being executed was:")
- _print_trace(textarray, linefailed)
- logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[0][0], tbextract[0][1], tbextract[0][2])
+ tbextract = traceback.extract_tb(tb)
+ tbformat = traceback.format_list(tbextract)
+ error.append("The stack trace of python calls that resulted in this exception/failure was:")
+ error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
+ error.extend(_print_trace(textarray, linefailed))
# See if this is a function we constructed and has calls back into other functions in
# "text". If so, try and improve the context of the error by diving down the trace
level = 0
nexttb = tb.tb_next
while nexttb is not None and (level+1) < len(tbextract):
+ error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
- _print_trace(textarray, tbextract[level+1][1])
- logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2])
+ # The code was possibly in the string we compiled ourselves
+ error.extend(_print_trace(textarray, tbextract[level+1][1]))
+ elif tbextract[level+1][0].startswith("/"):
+ # The code looks like it might be in a file, try and load it
+ try:
+ with open(tbextract[level+1][0], "r") as f:
+ text = f.readlines()
+ error.extend(_print_trace(text, tbextract[level+1][1]))
+ except:
+ error.append(tbformat[level+1])
elif "d" in context and tbextract[level+1][2]:
+ # Try and find the code in the datastore based on the functionname
d = context["d"]
functionname = tbextract[level+1][2]
text = d.getVar(functionname, True)
if text:
- _print_trace(text.split('\n'), tbextract[level+1][1])
- logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2])
+ error.extend(_print_trace(text.split('\n'), tbextract[level+1][1]))
else:
- break
+ error.append(tbformat[level+1])
else:
- break
+ error.append(tbformat[level+1])
nexttb = tb.tb_next
level = level + 1
+ error.append("Exception: %s" % ''.join(exception))
+ finally:
+ logger.error("\n".join(error))
+
+def better_exec(code, context, text = None, realfile = "<code>"):
+ """
+ Similiar to better_compile, better_exec will
+ print the lines that are responsible for the
+ error.
+ """
+ import bb.parse
+ if not text:
+ text = code
+ if not hasattr(code, "co_filename"):
+ code = better_compile(code, realfile, realfile)
+ try:
+ exec(code, _context, context)
+ except Exception as e:
+ (t, value, tb) = sys.exc_info()
+
+ if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
+ raise
+ try:
+ _print_exception(t, value, tb, realfile, text, context)
+ except Exception as e:
+ logger.error("Exception handler error: %s" % str(e))
+
e = bb.BBHandledException(e)
raise e
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2013-05-08 17:33 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-05-08 17:14 [PATCH] utils: Improve better_exec traceback handling Richard Purdie
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.