summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorChris Larson <chris_larson@mentor.com>2011-06-02 14:20:26 -0700
committerChris Larson <chris_larson@mentor.com>2011-06-02 14:20:26 -0700
commit610f649abdcdaaee282ed75a83eab838e4b4f0b0 (patch)
treee3bd008179035035a0e46043f87c3060e8234c47 /lib
parent67434496108efc3aba9cb1e3640bc712658b1408 (diff)
parentee48d628ee038bd72e1cd94aa75f5ccbacbcee4c (diff)
downloadbitbake-610f649abdcdaaee282ed75a83eab838e4b4f0b0.tar.gz
Merge remote branch 'github/exceptions'
* github/exceptions: Shift exception formatting into the UI cooker: don't show a traceback for ParseError cooker: don't show a useless traceback for SyntaxError cooker: use logger.exception for config file parse errors cooker: pass traceback back from parsing thread cooker: show a useful message for ParsingFailure bb.exceptions: don't show a repr of 'self' codeparser.py: fix syntax error in exception handling codeparser.py: Ignore incomplete cache files bb.exceptions: handle tb entries without context bb.exceptions: add to_string convenience function bb.exceptions: add code to create pickleable traceback entries bb.namedtuple_with_abc: add useful util from activestate
Diffstat (limited to 'lib')
-rw-r--r--lib/bb/cooker.py29
-rw-r--r--lib/bb/event.py6
-rw-r--r--lib/bb/exceptions.py81
-rw-r--r--lib/bb/msg.py10
-rw-r--r--lib/bb/namedtuple_with_abc.py255
5 files changed, 372 insertions, 9 deletions
diff --git a/lib/bb/cooker.py b/lib/bb/cooker.py
index 2440f6456..db6009568 100644
--- a/lib/bb/cooker.py
+++ b/lib/bb/cooker.py
@@ -33,7 +33,7 @@ import threading
from cStringIO import StringIO
from contextlib import closing
from functools import wraps
-import bb
+import bb, bb.exceptions
from bb import utils, data, parse, event, cache, providers, taskdata, command, runqueue
logger = logging.getLogger("BitBake")
@@ -75,7 +75,13 @@ class BBCooker:
bb.data.inheritFromOS(self.configuration.data)
- self.parseConfigurationFiles(self.configuration.file)
+ try:
+ self.parseConfigurationFiles(self.configuration.file)
+ except SyntaxError:
+ sys.exit(1)
+ except Exception:
+ logger.exception("Error parsing configuration files")
+ sys.exit(1)
if not self.configuration.cmd:
self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data, True) or "build"
@@ -1044,15 +1050,16 @@ class ParsingFailure(Exception):
def __init__(self, realexception, recipe):
self.realexception = realexception
self.recipe = recipe
- Exception.__init__(self, "Failure when parsing %s" % recipe)
- self.args = (realexception, recipe)
+ Exception.__init__(self, realexception, recipe)
def parse_file(task):
filename, appends = task
try:
return True, bb.cache.Cache.parse(filename, appends, parse_file.cfg)
except Exception, exc:
+ tb = sys.exc_info()[2]
exc.recipe = filename
+ exc.traceback = list(bb.exceptions.extract_traceback(tb, context=3))
raise exc
# Need to turn BaseExceptions into Exceptions here so we gracefully shutdown
# and for example a worker thread doesn't just exit on its own in response to
@@ -1140,9 +1147,21 @@ class CookerParser(object):
except StopIteration:
self.shutdown()
return False
+ except ParsingFailure as exc:
+ self.shutdown(clean=False)
+ bb.fatal('Unable to parse %s: %s' %
+ (exc.recipe, bb.exceptions.to_string(exc.realexception)))
+ except bb.parse.ParseError as exc:
+ bb.fatal(str(exc))
+ except SyntaxError as exc:
+ logger.error('Unable to parse %s', exc.recipe)
+ sys.exit(1)
except Exception as exc:
+ etype, value, tb = sys.exc_info()
+ logger.error('Unable to parse %s', value.recipe,
+ exc_info=(etype, value, exc.traceback))
self.shutdown(clean=False)
- bb.fatal('Error parsing %s: %s' % (exc.recipe, exc))
+ sys.exit(1)
self.current += 1
self.virtuals += len(result)
diff --git a/lib/bb/event.py b/lib/bb/event.py
index daca0a770..16d936c1b 100644
--- a/lib/bb/event.py
+++ b/lib/bb/event.py
@@ -422,8 +422,10 @@ class LogHandler(logging.Handler):
def emit(self, record):
if record.exc_info:
- lines = traceback.format_exception(*record.exc_info, limit=5)
- record.msg += '\n%s' % ''.join(lines)
+ etype, value, tb = record.exc_info
+ if hasattr(tb, 'tb_next'):
+ tb = list(bb.exceptions.extract_traceback(tb, context=3))
+ record.bb_exc_info = (etype, value, tb)
record.exc_info = None
fire(record, None)
diff --git a/lib/bb/exceptions.py b/lib/bb/exceptions.py
new file mode 100644
index 000000000..62d62cd4d
--- /dev/null
+++ b/lib/bb/exceptions.py
@@ -0,0 +1,81 @@
+from __future__ import absolute_import
+import inspect
+import traceback
+import bb.namedtuple_with_abc
+from collections import namedtuple
+
+
+class TracebackEntry(namedtuple.abc):
+ """Pickleable representation of a traceback entry"""
+ _fields = 'filename lineno function args code_context index'
+ _header = ' File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'
+
+ def format(self, formatter=None):
+ if not self.code_context:
+ return self._header.format(self) + '\n'
+
+ formatted = [self._header.format(self) + ':\n']
+
+ for lineindex, line in enumerate(self.code_context):
+ if formatter:
+ line = formatter(line)
+
+ if lineindex == self.index:
+ formatted.append(' >%s' % line)
+ else:
+ formatted.append(' %s' % line)
+ return formatted
+
+ def __str__(self):
+ return ''.join(self.format())
+
+def _get_frame_args(frame):
+ """Get the formatted arguments and class (if available) for a frame"""
+ arginfo = inspect.getargvalues(frame)
+ firstarg = arginfo.args[0]
+ if firstarg == 'self':
+ self = arginfo.locals['self']
+ cls = self.__class__.__name__
+
+ arginfo.args.pop(0)
+ del arginfo.locals['self']
+ else:
+ cls = None
+
+ formatted = inspect.formatargvalues(*arginfo)
+ return formatted, cls
+
+def extract_traceback(tb, context=1):
+ frames = inspect.getinnerframes(tb, context)
+ for frame, filename, lineno, function, code_context, index in frames:
+ formatted_args, cls = _get_frame_args(frame)
+ if cls:
+ function = '%s.%s' % (cls, function)
+ yield TracebackEntry(filename, lineno, function, formatted_args,
+ code_context, index)
+
+def format_extracted(extracted, formatter=None, limit=None):
+ if limit:
+ extracted = extracted[-limit:]
+
+ formatted = []
+ for tracebackinfo in extracted:
+ formatted.extend(tracebackinfo.format(formatter))
+ return formatted
+
+
+def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
+ formatted = ['Traceback (most recent call last):\n']
+
+ if hasattr(tb, 'tb_next'):
+ tb = extract_traceback(tb, context)
+
+ formatted.extend(format_extracted(tb, formatter, limit))
+ formatted.extend(traceback.format_exception_only(etype, value))
+ return formatted
+
+def to_string(exc):
+ if isinstance(exc, SystemExit):
+ if not isinstance(exc.code, basestring):
+ return 'Exited with "%d"' % exc.code
+ return str(exc)
diff --git a/lib/bb/msg.py b/lib/bb/msg.py
index a7ac85079..12d19ff8e 100644
--- a/lib/bb/msg.py
+++ b/lib/bb/msg.py
@@ -65,9 +65,15 @@ class BBLogFormatter(logging.Formatter):
def format(self, record):
record.levelname = self.getLevelName(record.levelno)
if record.levelno == self.PLAIN:
- return record.getMessage()
+ msg = record.getMessage()
else:
- return logging.Formatter.format(self, record)
+ msg = logging.Formatter.format(self, record)
+
+ if hasattr(record, 'bb_exc_info'):
+ etype, value, tb = record.bb_exc_info
+ formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
+ msg += '\n' + ''.join(formatted)
+ return msg
class Loggers(dict):
def __getitem__(self, key):
diff --git a/lib/bb/namedtuple_with_abc.py b/lib/bb/namedtuple_with_abc.py
new file mode 100644
index 000000000..f5e0a3f3d
--- /dev/null
+++ b/lib/bb/namedtuple_with_abc.py
@@ -0,0 +1,255 @@
+# http://code.activestate.com/recipes/577629-namedtupleabc-abstract-base-class-mix-in-for-named/
+#!/usr/bin/env python
+# Copyright (c) 2011 Jan Kaliszewski (zuo). Available under the MIT License.
+
+"""
+namedtuple_with_abc.py:
+* named tuple mix-in + ABC (abstract base class) recipe,
+* works under Python 2.6, 2.7 as well as 3.x.
+
+Import this module to patch collections.namedtuple() factory function
+-- enriching it with the 'abc' attribute (an abstract base class + mix-in
+for named tuples) and decorating it with a wrapper that registers each
+newly created named tuple as a subclass of namedtuple.abc.
+
+How to import:
+ import collections, namedtuple_with_abc
+or:
+ import namedtuple_with_abc
+ from collections import namedtuple
+ # ^ in this variant you must import namedtuple function
+ # *after* importing namedtuple_with_abc module
+or simply:
+ from namedtuple_with_abc import namedtuple
+
+Simple usage example:
+ class Credentials(namedtuple.abc):
+ _fields = 'username password'
+ def __str__(self):
+ return ('{0.__class__.__name__}'
+ '(username={0.username}, password=...)'.format(self))
+ print(Credentials("alice", "Alice's password"))
+
+For more advanced examples -- see below the "if __name__ == '__main__':".
+"""
+
+import collections
+from abc import ABCMeta, abstractproperty
+from functools import wraps
+from sys import version_info
+
+__all__ = ('namedtuple',)
+_namedtuple = collections.namedtuple
+
+
+class _NamedTupleABCMeta(ABCMeta):
+ '''The metaclass for the abstract base class + mix-in for named tuples.'''
+ def __new__(mcls, name, bases, namespace):
+ fields = namespace.get('_fields')
+ for base in bases:
+ if fields is not None:
+ break
+ fields = getattr(base, '_fields', None)
+ if not isinstance(fields, abstractproperty):
+ basetuple = _namedtuple(name, fields)
+ bases = (basetuple,) + bases
+ namespace.pop('_fields', None)
+ namespace.setdefault('__doc__', basetuple.__doc__)
+ namespace.setdefault('__slots__', ())
+ return ABCMeta.__new__(mcls, name, bases, namespace)
+
+
+exec(
+ # Python 2.x metaclass declaration syntax
+ """class _NamedTupleABC(object):
+ '''The abstract base class + mix-in for named tuples.'''
+ __metaclass__ = _NamedTupleABCMeta
+ _fields = abstractproperty()""" if version_info[0] < 3 else
+ # Python 3.x metaclass declaration syntax
+ """class _NamedTupleABC(metaclass=_NamedTupleABCMeta):
+ '''The abstract base class + mix-in for named tuples.'''
+ _fields = abstractproperty()"""
+)
+
+
+_namedtuple.abc = _NamedTupleABC
+#_NamedTupleABC.register(type(version_info)) # (and similar, in the future...)
+
+@wraps(_namedtuple)
+def namedtuple(*args, **kwargs):
+ '''Named tuple factory with namedtuple.abc subclass registration.'''
+ cls = _namedtuple(*args, **kwargs)
+ _NamedTupleABC.register(cls)
+ return cls
+
+collections.namedtuple = namedtuple
+
+
+
+
+if __name__ == '__main__':
+
+ '''Examples and explanations'''
+
+ # Simple usage
+
+ class MyRecord(namedtuple.abc):
+ _fields = 'x y z' # such form will be transformed into ('x', 'y', 'z')
+ def _my_custom_method(self):
+ return list(self._asdict().items())
+ # (the '_fields' attribute belongs to the named tuple public API anyway)
+
+ rec = MyRecord(1, 2, 3)
+ print(rec)
+ print(rec._my_custom_method())
+ print(rec._replace(y=222))
+ print(rec._replace(y=222)._my_custom_method())
+
+ # Custom abstract classes...
+
+ class MyAbstractRecord(namedtuple.abc):
+ def _my_custom_method(self):
+ return list(self._asdict().items())
+
+ try:
+ MyAbstractRecord() # (abstract classes cannot be instantiated)
+ except TypeError as exc:
+ print(exc)
+
+ class AnotherAbstractRecord(MyAbstractRecord):
+ def __str__(self):
+ return '<<<{0}>>>'.format(super(AnotherAbstractRecord,
+ self).__str__())
+
+ # ...and their non-abstract subclasses
+
+ class MyRecord2(MyAbstractRecord):
+ _fields = 'a, b'
+
+ class MyRecord3(AnotherAbstractRecord):
+ _fields = 'p', 'q', 'r'
+
+ rec2 = MyRecord2('foo', 'bar')
+ print(rec2)
+ print(rec2._my_custom_method())
+ print(rec2._replace(b=222))
+ print(rec2._replace(b=222)._my_custom_method())
+
+ rec3 = MyRecord3('foo', 'bar', 'baz')
+ print(rec3)
+ print(rec3._my_custom_method())
+ print(rec3._replace(q=222))
+ print(rec3._replace(q=222)._my_custom_method())
+
+ # You can also subclass non-abstract ones...
+
+ class MyRecord33(MyRecord3):
+ def __str__(self):
+ return '< {0!r}, ..., {0!r} >'.format(self.p, self.r)
+
+ rec33 = MyRecord33('foo', 'bar', 'baz')
+ print(rec33)
+ print(rec33._my_custom_method())
+ print(rec33._replace(q=222))
+ print(rec33._replace(q=222)._my_custom_method())
+
+ # ...and even override the magic '_fields' attribute again
+
+ class MyRecord345(MyRecord3):
+ _fields = 'e f g h i j k'
+
+ rec345 = MyRecord345(1, 2, 3, 4, 3, 2, 1)
+ print(rec345)
+ print(rec345._my_custom_method())
+ print(rec345._replace(f=222))
+ print(rec345._replace(f=222)._my_custom_method())
+
+ # Mixing-in some other classes is also possible:
+
+ class MyMixIn(object):
+ def method(self):
+ return "MyMixIn.method() called"
+ def _my_custom_method(self):
+ return "MyMixIn._my_custom_method() called"
+ def count(self, item):
+ return "MyMixIn.count({0}) called".format(item)
+ def _asdict(self): # (cannot override a namedtuple method, see below)
+ return "MyMixIn._asdict() called"
+
+ class MyRecord4(MyRecord33, MyMixIn): # mix-in on the right
+ _fields = 'j k l x'
+
+ class MyRecord5(MyMixIn, MyRecord33): # mix-in on the left
+ _fields = 'j k l x y'
+
+ rec4 = MyRecord4(1, 2, 3, 2)
+ print(rec4)
+ print(rec4.method())
+ print(rec4._my_custom_method()) # MyRecord33's
+ print(rec4.count(2)) # tuple's
+ print(rec4._replace(k=222))
+ print(rec4._replace(k=222).method())
+ print(rec4._replace(k=222)._my_custom_method()) # MyRecord33's
+ print(rec4._replace(k=222).count(8)) # tuple's
+
+ rec5 = MyRecord5(1, 2, 3, 2, 1)
+ print(rec5)
+ print(rec5.method())
+ print(rec5._my_custom_method()) # MyMixIn's
+ print(rec5.count(2)) # MyMixIn's
+ print(rec5._replace(k=222))
+ print(rec5._replace(k=222).method())
+ print(rec5._replace(k=222)._my_custom_method()) # MyMixIn's
+ print(rec5._replace(k=222).count(2)) # MyMixIn's
+
+ # None that behavior: the standard namedtuple methods cannot be
+ # overriden by a foreign mix-in -- even if the mix-in is declared
+ # as the leftmost base class (but, obviously, you can override them
+ # in the defined class or its subclasses):
+
+ print(rec4._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
+ print(rec5._asdict()) # (returns a dict, not "MyMixIn._asdict() called")
+
+ class MyRecord6(MyRecord33):
+ _fields = 'j k l x y z'
+ def _asdict(self):
+ return "MyRecord6._asdict() called"
+ rec6 = MyRecord6(1, 2, 3, 1, 2, 3)
+ print(rec6._asdict()) # (this returns "MyRecord6._asdict() called")
+
+ # All that record classes are real subclasses of namedtuple.abc:
+
+ assert issubclass(MyRecord, namedtuple.abc)
+ assert issubclass(MyAbstractRecord, namedtuple.abc)
+ assert issubclass(AnotherAbstractRecord, namedtuple.abc)
+ assert issubclass(MyRecord2, namedtuple.abc)
+ assert issubclass(MyRecord3, namedtuple.abc)
+ assert issubclass(MyRecord33, namedtuple.abc)
+ assert issubclass(MyRecord345, namedtuple.abc)
+ assert issubclass(MyRecord4, namedtuple.abc)
+ assert issubclass(MyRecord5, namedtuple.abc)
+ assert issubclass(MyRecord6, namedtuple.abc)
+
+ # ...but abstract ones are not subclasses of tuple
+ # (and this is what you probably want):
+
+ assert not issubclass(MyAbstractRecord, tuple)
+ assert not issubclass(AnotherAbstractRecord, tuple)
+
+ assert issubclass(MyRecord, tuple)
+ assert issubclass(MyRecord2, tuple)
+ assert issubclass(MyRecord3, tuple)
+ assert issubclass(MyRecord33, tuple)
+ assert issubclass(MyRecord345, tuple)
+ assert issubclass(MyRecord4, tuple)
+ assert issubclass(MyRecord5, tuple)
+ assert issubclass(MyRecord6, tuple)
+
+ # Named tuple classes created with namedtuple() factory function
+ # (in the "traditional" way) are registered as "virtual" subclasses
+ # of namedtuple.abc:
+
+ MyTuple = namedtuple('MyTuple', 'a b c')
+ mt = MyTuple(1, 2, 3)
+ assert issubclass(MyTuple, namedtuple.abc)
+ assert isinstance(mt, namedtuple.abc)