diff options
author | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-05-08 18:14:53 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-05-09 14:06:52 +0100 |
commit | bc95ddec6da0d2ae5da2637f2d065b89594ed041 (patch) | |
tree | adf3c30ffd592b0a074c311646e0abb5ae20f337 | |
parent | f58e82b9e9cab8e497b70920c77fcac4f355bb91 (diff) | |
download | poky-bc95ddec6da0d2ae5da2637f2d065b89594ed041.tar.gz |
bitbake: utils: Improve better_exec traceback handling
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
"""
(Bitbake rev: c5de66b870406d9bd1161a9b7e2b04fe6eb065fe)
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | bitbake/lib/bb/utils.py | 96 |
1 files changed, 57 insertions, 39 deletions
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py index d671f56b50..9d7a32fb25 100644 --- a/bitbake/lib/bb/utils.py +++ b/bitbake/lib/bb/utils.py | |||
@@ -236,14 +236,16 @@ def _print_trace(body, line): | |||
236 | """ | 236 | """ |
237 | Print the Environment of a Text Body | 237 | Print the Environment of a Text Body |
238 | """ | 238 | """ |
239 | error = [] | ||
239 | # print the environment of the method | 240 | # print the environment of the method |
240 | min_line = max(1, line-4) | 241 | min_line = max(1, line-4) |
241 | max_line = min(line + 4, len(body)) | 242 | max_line = min(line + 4, len(body)) |
242 | for i in xrange(min_line, max_line + 1): | 243 | for i in range(min_line, max_line + 1): |
243 | if line == i: | 244 | if line == i: |
244 | logger.error(' *** %.4d:%s', i, body[i-1]) | 245 | error.append(' *** %.4d:%s' % (i, body[i-1].rstrip())) |
245 | else: | 246 | else: |
246 | logger.error(' %.4d:%s', i, body[i-1]) | 247 | error.append(' %.4d:%s' % (i, body[i-1].rstrip())) |
248 | return error | ||
247 | 249 | ||
248 | def better_compile(text, file, realfile, mode = "exec"): | 250 | def better_compile(text, file, realfile, mode = "exec"): |
249 | """ | 251 | """ |
@@ -260,7 +262,7 @@ def better_compile(text, file, realfile, mode = "exec"): | |||
260 | if e.lineno: | 262 | if e.lineno: |
261 | logger.error("The lines leading to this error were:") | 263 | logger.error("The lines leading to this error were:") |
262 | logger.error("\t%d:%s:'%s'", e.lineno, e.__class__.__name__, body[e.lineno-1]) | 264 | logger.error("\t%d:%s:'%s'", e.lineno, e.__class__.__name__, body[e.lineno-1]) |
263 | _print_trace(body, e.lineno) | 265 | logger.error("\n".join(_print_trace(body, e.lineno))) |
264 | else: | 266 | else: |
265 | logger.error("The function causing this error was:") | 267 | logger.error("The function causing this error was:") |
266 | for line in body: | 268 | for line in body: |
@@ -269,68 +271,84 @@ def better_compile(text, file, realfile, mode = "exec"): | |||
269 | e = bb.BBHandledException(e) | 271 | e = bb.BBHandledException(e) |
270 | raise e | 272 | raise e |
271 | 273 | ||
272 | def better_exec(code, context, text = None, realfile = "<code>"): | 274 | def _print_exception(t, value, tb, realfile, text, context): |
273 | """ | 275 | error = [] |
274 | Similiar to better_compile, better_exec will | ||
275 | print the lines that are responsible for the | ||
276 | error. | ||
277 | """ | ||
278 | import bb.parse | ||
279 | if not text: | ||
280 | text = code | ||
281 | if not hasattr(code, "co_filename"): | ||
282 | code = better_compile(code, realfile, realfile) | ||
283 | try: | 276 | try: |
284 | exec(code, _context, context) | ||
285 | except Exception as e: | ||
286 | (t, value, tb) = sys.exc_info() | ||
287 | |||
288 | if t in [bb.parse.SkipPackage, bb.build.FuncFailed]: | ||
289 | raise | ||
290 | |||
291 | import traceback | 277 | import traceback |
292 | exception = traceback.format_exception_only(t, value) | 278 | exception = traceback.format_exception_only(t, value) |
293 | logger.error('Error executing a python function in %s:\n%s', | 279 | error.append('Error executing a python function in %s:\n' % realfile) |
294 | realfile, ''.join(exception)) | ||
295 | 280 | ||
296 | # Strip 'us' from the stack (better_exec call) | 281 | # Strip 'us' from the stack (better_exec call) |
297 | tb = tb.tb_next | 282 | tb = tb.tb_next |
298 | 283 | ||
299 | textarray = text.split('\n') | 284 | textarray = text.split('\n') |
300 | linefailed = traceback.tb_lineno(tb) | ||
301 | 285 | ||
302 | tbextract = traceback.extract_tb(tb) | 286 | linefailed = tb.tb_lineno |
303 | tbformat = "\n".join(traceback.format_list(tbextract)) | ||
304 | logger.error("The stack trace of python calls that resulted in this exception/failure was:") | ||
305 | for line in tbformat.split('\n'): | ||
306 | logger.error(line) | ||
307 | 287 | ||
308 | logger.error("The code that was being executed was:") | 288 | tbextract = traceback.extract_tb(tb) |
309 | _print_trace(textarray, linefailed) | 289 | tbformat = traceback.format_list(tbextract) |
310 | logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[0][0], tbextract[0][1], tbextract[0][2]) | 290 | error.append("The stack trace of python calls that resulted in this exception/failure was:") |
291 | error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2])) | ||
292 | error.extend(_print_trace(textarray, linefailed)) | ||
311 | 293 | ||
312 | # See if this is a function we constructed and has calls back into other functions in | 294 | # See if this is a function we constructed and has calls back into other functions in |
313 | # "text". If so, try and improve the context of the error by diving down the trace | 295 | # "text". If so, try and improve the context of the error by diving down the trace |
314 | level = 0 | 296 | level = 0 |
315 | nexttb = tb.tb_next | 297 | nexttb = tb.tb_next |
316 | while nexttb is not None and (level+1) < len(tbextract): | 298 | while nexttb is not None and (level+1) < len(tbextract): |
299 | error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2])) | ||
317 | if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]: | 300 | if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]: |
318 | _print_trace(textarray, tbextract[level+1][1]) | 301 | # The code was possibly in the string we compiled ourselves |
319 | logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]) | 302 | error.extend(_print_trace(textarray, tbextract[level+1][1])) |
303 | elif tbextract[level+1][0].startswith("/"): | ||
304 | # The code looks like it might be in a file, try and load it | ||
305 | try: | ||
306 | with open(tbextract[level+1][0], "r") as f: | ||
307 | text = f.readlines() | ||
308 | error.extend(_print_trace(text, tbextract[level+1][1])) | ||
309 | except: | ||
310 | error.append(tbformat[level+1]) | ||
320 | elif "d" in context and tbextract[level+1][2]: | 311 | elif "d" in context and tbextract[level+1][2]: |
312 | # Try and find the code in the datastore based on the functionname | ||
321 | d = context["d"] | 313 | d = context["d"] |
322 | functionname = tbextract[level+1][2] | 314 | functionname = tbextract[level+1][2] |
323 | text = d.getVar(functionname, True) | 315 | text = d.getVar(functionname, True) |
324 | if text: | 316 | if text: |
325 | _print_trace(text.split('\n'), tbextract[level+1][1]) | 317 | error.extend(_print_trace(text.split('\n'), tbextract[level+1][1])) |
326 | logger.error("[From file: '%s', lineno: %s, function: %s]", tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]) | ||
327 | else: | 318 | else: |
328 | break | 319 | error.append(tbformat[level+1]) |
329 | else: | 320 | else: |
330 | break | 321 | error.append(tbformat[level+1]) |
331 | nexttb = tb.tb_next | 322 | nexttb = tb.tb_next |
332 | level = level + 1 | 323 | level = level + 1 |
333 | 324 | ||
325 | error.append("Exception: %s" % ''.join(exception)) | ||
326 | finally: | ||
327 | logger.error("\n".join(error)) | ||
328 | |||
329 | def better_exec(code, context, text = None, realfile = "<code>"): | ||
330 | """ | ||
331 | Similiar to better_compile, better_exec will | ||
332 | print the lines that are responsible for the | ||
333 | error. | ||
334 | """ | ||
335 | import bb.parse | ||
336 | if not text: | ||
337 | text = code | ||
338 | if not hasattr(code, "co_filename"): | ||
339 | code = better_compile(code, realfile, realfile) | ||
340 | try: | ||
341 | exec(code, _context, context) | ||
342 | except Exception as e: | ||
343 | (t, value, tb) = sys.exc_info() | ||
344 | |||
345 | if t in [bb.parse.SkipPackage, bb.build.FuncFailed]: | ||
346 | raise | ||
347 | try: | ||
348 | _print_exception(t, value, tb, realfile, text, context) | ||
349 | except Exception as e: | ||
350 | logger.error("Exception handler error: %s" % str(e)) | ||
351 | |||
334 | e = bb.BBHandledException(e) | 352 | e = bb.BBHandledException(e) |
335 | raise e | 353 | raise e |
336 | 354 | ||