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 | ||
